カテゴリー
【AWS Lambda開発者向け】AWS EFSのアクセスポイントのPOSIX所有権を深堀してみる
※ 当ページには【広告/PR】を含む場合があります。
2024/01/19

EFSアクセスポイントをより良く使うことで、例えば以下のような構成図のように、EFSのファイルシステムへの細やかなアクセス制御を構造化し、セキュリティを強化することが可能になります。
982x728

...かといって、EFSにもLambdaにも不慣れな時分には、この構成図の意味はピンと理解が難しいはずです。
今回はEFSアクセスポイントをより深く理解する上で欠かせない、
本記事で解説する内容の要点をまとめると、
1. EFSアクセスポイントのPosixUserを指定した場合、Lambda側のPOSIXは無視される
2. PosixUserを指定しなかった場合はLambda側のPOSIXでEFSにアクセスすることになり、
ルートディレクトリ所有権次第では、操作が行えない可能性がある。
3. PosixUserをルートユーザー"0:0(root:root)"に指定した場合、すべての操作が許可される反面、
その後、ルートユーザーでしかアクセスできないリソースが発生するリスクがある
4. 特にアプリケーション別の目的が無い限りは、PosixUserもルートディレクトリ設定も
"1001:1001"等で固定のユーザーを作るほうが使いやすい
5. 固定ユーザー専用のディレクトリとして読み書きしたいならアクセス権限"770/760"、
固定ユーザー以外もアクセス許可したいなら"777/766"
6. 書き込みはせずに読み出し・閲覧専用でいいなら、
固定ユーザー専用の権限で"750/740"、他のユーザーまで含めるなら"755/744"
それでは、より詳しい説明を以降で順次進めていきます。
EFSのPOSIXとはなにか?
まずは実際の検証の話の前に、
Linux全般における「root」のPOSIXについて
Linux開発の歴史として、
「ユーザーID: 0」
「root」
そこから、特権ユーザーのことを
すべてのLinuxで、rootユーザーのUID/GIDが0でなければならないわけでもないようですが、各ディストリビューションの互換性を保つために、Linux標準規格・LSB(Linux Standard Base)を定めたうえで、ほとんどのLinuxでこのルールが準拠している状況です。
AWS EFSもこの規約に従っており、
"0:0" = "root:root"
Amazon EFSは、アクセスポイントで「0」に設定されたユーザーまたはグループ ID
をルートユーザーとして扱います。
ということで、EFSアクセスポイントに
"0:0"
EFSアクセスポイントのLambdaでの利用方法
以下の技術ブログ記事では、EC2インスタンスからのEFSアクセスポイントを使ったルートディレクトリのマウントルールに関して詳しく解説されています。
そちらでも色々と参考になりますが、殊、アクセスポイントからLambdaへマウントする際には、Lambda独自のルールの兼ね合いから、もう少し理解を進める必要があります。
LambdaのPOSIXルール① 〜 アクセスポイントによるPOSIXユーザーの強制
アクセスポイントでPOSIXを指定した場合、Lambda側のPOSIXを無視することができます。
POSIXユーザーの指定が有効になったEFSアクセスポイントは、そこへアクセスする操作のアイデンティティ(POSIX)をすべて指定されたPOSIXへ置き換えます。
+ 新しいファイル・ディレクトリの所有権は、
アクセスポイントのユーザーID・グループ IDに設定される
+ ルートディレクトリ以下のアクセス許可の評価はすべて
アクセスポイントのユーザーID・グループID・セカンダリグループIDに考慮され、
アクセス元のクライアントのIDは無視される
逆に、
当然、アクセス権限のないPOSIXがEFS(のルートディレクトリ)にあるリソースを使うと、期待した操作が行えないことになります。
LambdaのPOSIXルール② 〜 ルートディレクトリに所有権とアクセス許可を指定しない場合
通常、アクセスポイントのルートディレクトリがまだ存在しない場合、EFSによって、アクセスポイントで指定された所有権とアクセス許可を使用して、そのルートディレクトリが自動的に作成されることになっています。
このルートディレクトリの作成自動化の仕組みにより、EC2へのマウント設定を明示にすることなく、特定のユーザーまたはアプリケーションに対して、EFSへのアクセスを可能としています。
ルートディレクトリの作成には、アクセスポイント作成時に以下の属性を設定する必要があります。
OwnerUid:
ルートディレクトリの所有者として使用するPOSIXユーザーID
OwnerGiD:
ルートディレクトリの所有者グループとして使用するPOSIXグループID
Permissions:
アクセス許可を指定する。
ルートディレクトリのUnixモード値("755"等)
というのが表向きの説明ですが、ここでのEFSアクセスポイントを利用する上で、重要なルールとして、
つまり、ルートディレクトリの初期設定を指定した場合にのみ、アクセスポイントのルートディレクトリが作成される仕組みになっています。
このルールが手動でマウントする方法のないLambdaにとっては致命的で、ルートディレクトリが存在しないアクセスポイントを使用してマウントしようとすると、マウントが失敗します。
つまりは、アクセスポイントの設定項目欄で、
"Option"
LambdaのPOSIXルール③ 〜 既存のルートディレクトリの所有権に注意
先ほどのルールはルートディレクトリがまだ無い場合の話でしたが、複数のアプリケーションで一つのルートディレクトリを共有したい場合には、最初にルートディレクトリを初期化したPOSIXの所有権に考慮する必要があります。
なぜなら、
すでに存在しているルートディレクトリの所有権に注意していないと、作成したPOSIXなら読み書きできるけれど、他のPOSIXユーザーではファイルが読めない・書けない、という状況に陥るかもしれません。
もしルートディレクトリの権限をリセットしたい場合、実行権限のあるユーザー側で
chmod
NodejsランタイムのPOSIXとEFSアクセスを検証する
ここからは、いくつかの実例を挙げながら、Lambda特有のEFSアクセスポイントの利用ルールを評価していきます。
事前準備〜稼働中のLambda(nodejsランタイム)から内部コマンドを呼び出してPOSIXを調べる
まず、Lambdaの中でJavascriptからシェルプロセスを呼び出すための基本の実装は以下のようにハンドラ内で
child_process
//内部でシェルコマンドを利用する
import { execSync } from 'child_process';
//...
あとは、Lambdaからのレスポンスに処理結果を渡す工程をハンドラの適切な箇所に追加するだけです。
プロセス処理がアクティブになっているPOSIXユーザーをリストで確認するコマンドは色々とありますが、ここでは手っ取り早く
/etc/passwd
ハンドラーへの実装のイメージだと以下のようなものです。
//...
//Lambdaのレスポンスボディに処理結果を貼り付け
const stdout = execSync('cat /etc/passwd');
res.body.pswd = stdout.toString();
Lambdaから帰ってくるレスポンス結果をいい感じに整形しておくと、おおよそ以下のようなアクティブなPOSIXユーザーが存在することが分かります。
root:x:0:0:root:/root:/bin/bash
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
ec2-user:x:996:993::/home/ec2-user:/bin/bash
この際のシェルプロセスは、通常のEC2インスタンス同様、
ec2-user
また、Nodeのプロセス実行時に割り当てられる
sbx_user
sbx_user1051:x:993:990::/home/sbx_user1051:/sbin/nologin
sbx_user1052:x:992:989::/home/sbx_user1052:/sbin/nologin
sbx_user1053:x:991:988::/home/sbx_user1053:/sbin/nologin
...
sbx_user1176:x:868:865::/home/sbx_user1176:/sbin/nologin
処理中のNode自体のプロセスがあれば、このどれかのPOSIXが利用される方式(おそらく
sbx_user1051
Lambdaのランタイムごとにプロセスに割り振られるPOSIXの方式が異なるかもしれませんが、nodeランタイムを使うユーザーに限っていえば、
EFSへアクセスできるパターン①〜ルートで問答無用のアクセス
例えば、新規・既存に関わらずどこかのルートディレクトリへアクセスする場合、そのフォルダ所有権がどうなっているのか細かく調べるのが面倒なので、問答無用でrootユーザーからアクセスするやり方です。
一見、アクセスできてるならそれで全部それでいいじゃん、と思いますが、当然強すぎる権限を与えられた弊害もあります。
EFSAccessPoint:
PosixUser:
Uid: "0"
Gid: "0"
RootDirectory:
Path: "/hoge"
CreationInfo:
OwnerGid: "1001"
OwnerUid: "1001"
Permissions: "700"
この場合、ルート権限でEFSの全てリソースにアクセス・操作が可能となります。
例えば、Lambdaの内部情報を探るために、以下のシェルプロセスを走らせた結果をハンドラから返してみましょう。
//...
//Lambda側のマウントパス
const rootDir = '/mnt/lambda';
//LambdaのプロセスPOSIXの確認
execSync(`echo "上書き!" > ${rootDir}/first.txt`);
const stdout1 = execSync(`cat ${rootDir}/first.txt`);
//LambdaのプロセスPOSIXの確認
const stdout2 = execSync('id -a');
//フォルダ・ファイルの所有権の確認
const stdout3 = execSync(`ls -la ${rootDir}`);
//レスポンスボディに処理結果を貼り付け
res.body.firsttext = stdout1.toString();
res.body.posixusrs = stdout2.toString();
res.body.filepermission = stdout3.toString();
Lambdaから帰ってきたレスポンスを標準出力風に整形すると以下になります。
$ echo "上書き!" > /mnt/lambda/first.txt
$ cat /mnt/lambda/first.txt
上書き!
$ id -a
uid=993(sbx_user1051) gid=990 groups=990
$ ls -la
total 12
drwxr-x--- 2 1001 1001 6144 Jan 16 11:37 .
drwxr-xr-x 3 root root 4096 Jan 16 12:07 ..
-rwx------ 1 root root 52 Jan 16 12:07 first.txt
これを見ると、EFSのルートディレクトリ
/hoge
1001:1001
"750"
ちなみにEFSの
「本当のルートディレクトリ(/)」
root:root
"755"
注目は、プロセスで作成したファイル
first.txt
このファイルは、rootユーザーが作成し、アクセス権限
"700"
こうなってしまうと、これ以降の処理では、ルート権限をもったPOSIXユーザー以外がアクセスしても、このファイルを見ることも、編集することもできなくなってしまいます。
また、あえてルートディレクトリのアクセス許可設定を
"700"
特定の目的や理由が無い限りは、EFSデフォルトの
"755"
EFSへアクセスできないパターン①〜ルートディレクトリのアクセス権を正しく理解していない
ネットで検索しますと、公式にしろ、他の技術ブログにしろ、ルートディレクトリのアクセス権を
"755"
"750"
とりあえず最初にEFSのアクセスポイントを使ってみたいエンジニアの方は、おそらく何かしらの呪文程度に、紹介されるがままこの値を設定してしまうことでしょう(著者がそうだったように...)。
よくよく考えると、例えば
「755」 = ルートユーザーは読み書き実行全部できて、自分(=フォルダ所有者)と他のユーザーは読みと実行のみ
ならば、以下のようなアクセスポイントに設定したとしましょう。
PosixUser:
Uid: "1001"
Gid: "1001"
RootDirectory:
Path: "/hoge"
CreationInfo:
OwnerGid: "1001"
OwnerUid: "1001"
Permissions: "755"
たしかに、マウント先のルートディレクトリは読み出すことが可能ですが、編集したり書き込みを行うような操作では、
$ echo "上書き!" > /mnt/lambda/first.txt
Command failed: echo \"上書き!\" >> /mnt/lambda/first.txt
/bin/sh: line 1: /mnt/lambda/first.txt: Permission denied
となり、マウント先の書き込みは許可されずに失敗してしまいます。
外部からのクライアントをアクセスさせるユースケースならば、おいそれと内部のリソースを書き換えてしまないようにすることは正しい設定ではありますが、プライベートだったり開発目的で使うLambdaにとっては、書き込み操作ができないのは頭が痛い問題です。
ということで、アクセスポイントを使うユーザーがちゃんと読み書き実行を行えるようにするためには、例えば以下にすると良いでしょう。
PosixUser:
Uid: "1001"
Gid: "1001"
RootDirectory:
Path: "/hoge"
CreationInfo:
OwnerGid: "1001"
OwnerUid: "1001"
Permissions: "770"
ここで大切なことは、EFSのルートディレクトリをどのように利用したいか、目的をはっきりさせてアクセスポイントを作成することを心がけましょう。
EFSへアクセスできないパターン②〜既存のルートディレクトリに権限がない
LambdaからEFSアクセスポイントを使うときに気づきにくいのは、既にEFSのファイルシステム上に存在しているルートディレクトリに与えられたアクセス権がどうなっているかという点にあります。
たとえばEFSアクセスポイントの設定を以下のようにしたとしましょう。
PosixUser:
Uid: "1001"
Gid: "1001"
RootDirectory:
Path: "/hoge"
CreationInfo:
OwnerGid: "1001"
OwnerUid: "1001"
Permissions: "760"
一見、アクセスするPOSIX・
"1001:1001"
/hoge
でもなぜか、「ファイルやフォルダの中身が読めない・書けない」ということで悩んでしまう方もいらっしゃるでしょう。
ということで、ハンドラーにシェルコマンド実行し、ルートディレクトリのアクセス権を覗いてみますと、
$ ls -la /mnt/lambda
total 12
drwx------ 2 1001 1001 6144 Jan 16 11:37 .
#...省略
※ Lambda側の
/hoge
/mnt/lambda
この場合、ルートディレクトリ
/hoge
1001
700
これは極端な一例でしたが、実際にはVPC内のエコシステムの構成が複雑化してくるほど分かりにくいものになり、過去に設定したルートディレクトリが削除されることなく残ってしまったり、ルート権限あるユーザーが
chmod
ということで、期待通りでない挙動が見つかった場合には、前述したLambdaのPOSIXルールの一つ・
EFSへアクセスできるパターン②〜ルートディレクトリが新規で作成される場合
EFSのファイルシステム内にまだマウント先の対象となるルートディレクトリが存在していない場合、アクセスポイントの設定に従って、ルートディレクトリが新規作成されます。
RootDirectory:
Path: "/piyo"
CreationInfo:
OwnerGid: "1002"
OwnerUid: "1002"
Permissions: "764"
この場合、ルートディレクトリの
/piyo
"764"
ハンドラー内部でシェルコマンド実行し、ルートディレクトリのアクセス権を覗いてみますと、
$ ls -la /mnt/lambda
total 12
drwxrw-r-- 2 1002 1002 6144 Jan 16 11:37 .
#...省略
※ Lambda側の
/piyo
/mnt/lambda
と期待通りに設定されています。
前述したようにアクセスポイントに
PosixUser
このアクセスポイントを使えば、
PosixUser
1002
/piyo
このアクセスポイントからルートディレクトリに書き込みを行う操作をすると、
$ echo "上書き!" > /mnt/lambda/first.txt
Command failed: echo \"上書き!\" >> /mnt/lambda/first.txt
/bin/sh: line 1: /mnt/lambda/first.txt: Permission denied
となり、Lambdaはステータスコード・
400
開発途中で、やっぱりこのアクセスポイントを利用するアプリケーションすべてに対して書き込みをさせたい、となればPOSIX・
1002
PosixUser:
Uid: "1002"
Gid: "1002"
RootDirectory:
Path: "/piyo"
CreationInfo:
OwnerGid: "1002"
OwnerUid: "1002"
Permissions: "764"
と修正・再適用することで、すべてのLambdaからのアクセスでも、
/piyo
$ echo "上書き!" > /mnt/lambda/first.txt
$ cat /mnt/lambda/first.txt
上書き!
$ ls -la
total 12
-rwxrw-r-- 1 1002 1002 14 Jan 16 16:17 first.txt
#...
こちらの方法に従えば、先に説明していた
PosixUser
"0:0"
この意味でも、EFSのアクセスポイントの
PosixUser
EFSへアクセスできないパターン③〜ルートディレクトリに所有権とアクセス許可を指定していない
前述したようにLambdaのPOSIXルール・
どういうことか怖いもの見たさで検証してみましょう。
ここではあえて、ルートディレクトリの所有者情報をあえて与えないようにしてアクセスポイントを作成します。
PosixUser:
Uid: "1003"
Gid: "1003"
RootDirectory:
Path: "/fuga"
#👇作成時の所有権と権限を指定しない
# CreationInfo:
# OwnerGid: "1003"
# OwnerUid: "1003"
# Permissions: "770"
このような設定でもエラーが起こらずにアクセスポイント自体はちゃんと作成されますが、いざLambda側からアクセスを試みると...
Error:
The function couldn't mount the Amazon EFS file system with access point arn:aws:elasticfilesystem:ap-northeast-1:*********:access-point/fsap-************.
error Command failed with exit code 1.
公式のご説明の通り、所有者設定のないルートディレクトリはLambda側にマウントすらされないことが確認できました。
まとめ
以上、再度今回の要点をまとめると、
1. EFSアクセスポイントのPosixUserを指定した場合、Lambda側のPOSIXは無視される
2. PosixUserを指定しなかった場合はLambda側のPOSIXでEFSにアクセスすることになり、
ルートディレクトリ所有権次第では、操作が行えない可能性がある。
3. PosixUserをルートユーザー"0:0(root:root)"に指定した場合、すべての操作が許可される反面、
その後、ルートユーザーでしかアクセスできないリソースが発生するリスクがある
4. 特にアプリケーション別の目的が無い限りは、PosixUserもルートディレクトリ設定も
"1001:1001"等で固定のユーザーを作るほうが使いやすい
5. 固定ユーザー専用のディレクトリとして読み書きしたいならアクセス権限"770/760"、
固定ユーザー以外もアクセス許可したいなら"777/766"
6. 書き込みはせずに読み出し・閲覧専用でいいなら、
固定ユーザー専用の権限で"750/740"、他のユーザーまで含めるなら"755/744"
になります。
記事を書いた人
ナンデモ系エンジニア
主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。
カテゴリー