【AWS Lambda開発者向け】AWS EFSのアクセスポイントのPOSIX所有権を深堀してみる


※ 当ページには【広告/PR】を含む場合があります。
2024/01/19
Serverless FrameworkでもAWS で「APIGateway」→「VPC Lambda」→「EFS」を一発で構築してみる
蛸壺の技術ブログ|AWS EFSのアクセスポイントのPOSIX所有権を深堀してみる

前々回の記事で、サラリと「EFSのアクセスポイントの概要」を説明していました。

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

合同会社タコスキングダム|蛸壺の技術ブログ

...かといって、EFSにもLambdaにも不慣れな時分には、この構成図の意味はピンと理解が難しいはずです。

今回はEFSアクセスポイントをより深く理解する上で欠かせない、
「POSIXユーザー・グループの設定」をいくつかの実例を試しながら検証してみることにします。

本記事で解説する内容の要点をまとめると、

            
            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"
        
それでは、より詳しい説明を以降で順次進めていきます。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

EFSのPOSIXとはなにか?

まずは実際の検証の話の前に、「EFSのアクセスポイントのPOSIX」に関する基礎知識を復習しておきましょう。

Linux全般における「root」のPOSIXについて

Linux開発の歴史として、
「ユーザーID: 0」「root」というユーザー名が割り当てられます。

そこから、特権ユーザーのことを
「rootユーザー」と呼びます。

すべてのLinuxで、rootユーザーのUID/GIDが0でなければならないわけでもないようですが、各ディストリビューションの互換性を保つために、Linux標準規格・LSB(Linux Standard Base)を定めたうえで、ほとんどのLinuxでこのルールが準拠している状況です。

AWS EFSもこの規約に従っており、
"0:0" = "root:root"と解釈されます。

参考|Lambda 関数を使用してファイルシステムをマウントするための正しい EFS アクセスポイント構成を作成するにはどうすればよいですか?

            
            Amazon EFSは、アクセスポイントで「0」に設定されたユーザーまたはグループ ID
    をルートユーザーとして扱います。
        
ということで、EFSアクセスポイントに"0:0"と設定すると、ルート権限をもったユーザーとしてルートディレクトリにアクセスする、という意味になります。

EFSアクセスポイントのLambdaでの利用方法

以下の技術ブログ記事では、EC2インスタンスからのEFSアクセスポイントを使ったルートディレクトリのマウントルールに関して詳しく解説されています。

参考|EFSのマウントについて理解する。

そちらでも色々と参考になりますが、殊、アクセスポイントからLambdaへマウントする際には、Lambda独自のルールの兼ね合いから、もう少し理解を進める必要があります。

LambdaのPOSIXルール① 〜 アクセスポイントによるPOSIXユーザーの強制

アクセスポイントでPOSIXを指定した場合、Lambda側のPOSIXを無視することができます。

POSIXユーザーの指定が有効になったEFSアクセスポイントは、そこへアクセスする操作のアイデンティティ(POSIX)をすべて指定されたPOSIXへ置き換えます。

            
            + 新しいファイル・ディレクトリの所有権は、
    アクセスポイントのユーザーID・グループ IDに設定される

+ ルートディレクトリ以下のアクセス許可の評価はすべて
    アクセスポイントのユーザーID・グループID・セカンダリグループIDに考慮され、
    アクセス元のクライアントのIDは無視される
        
逆に、「POSIXユーザーを指定しなかった場合、Lambda側のPOSIXでEFS側へアクセスする」ことになります。

当然、アクセス権限のないPOSIXがEFS(のルートディレクトリ)にあるリソースを使うと、期待した操作が行えないことになります。

参考|アクセスポイントによるユーザーIDの強制

LambdaのPOSIXルール② 〜 ルートディレクトリに所有権とアクセス許可を指定しない場合

通常、アクセスポイントのルートディレクトリがまだ存在しない場合、EFSによって、アクセスポイントで指定された所有権とアクセス許可を使用して、そのルートディレクトリが自動的に作成されることになっています。

このルートディレクトリの作成自動化の仕組みにより、EC2へのマウント設定を明示にすることなく、特定のユーザーまたはアプリケーションに対して、EFSへのアクセスを可能としています。

ルートディレクトリの作成には、アクセスポイント作成時に以下の属性を設定する必要があります。

            
            OwnerUid:
    ルートディレクトリの所有者として使用するPOSIXユーザーID
OwnerGiD:
    ルートディレクトリの所有者グループとして使用するPOSIXグループID
Permissions:
    アクセス許可を指定する。
    ルートディレクトリのUnixモード値("755"等)
        
というのが表向きの説明ですが、ここでのEFSアクセスポイントを利用する上で、重要なルールとして、「作成時にディレクトリの所有権と権限を指定しない場合、EFSはルートディレクトリを作成しない」、というルールが存在しています。

つまり、ルートディレクトリの初期設定を指定した場合にのみ、アクセスポイントのルートディレクトリが作成される仕組みになっています。

このルールが手動でマウントする方法のないLambdaにとっては致命的で、ルートディレクトリが存在しないアクセスポイントを使用してマウントしようとすると、マウントが失敗します。

参考|アクセスポイントのルートディレクトリの作成

つまりは、アクセスポイントの設定項目欄で、
"Option"なんて軽く説明されていますが、ルートディレクトリの初期設定がないとLambdaからはEFSが使えませんので必ず指定しましょう。

LambdaのPOSIXルール③ 〜 既存のルートディレクトリの所有権に注意

先ほどのルールはルートディレクトリがまだ無い場合の話でしたが、複数のアプリケーションで一つのルートディレクトリを共有したい場合には、最初にルートディレクトリを初期化したPOSIXの所有権に考慮する必要があります。

なぜなら、
「アクセスポイントのルートディレクトリがマウント前にすでに存在している場合、既存の権限はアクセスポイントによって上書きされない」からです。

すでに存在しているルートディレクトリの所有権に注意していないと、作成したPOSIXなら読み書きできるけれど、他のPOSIXユーザーではファイルが読めない・書けない、という状況に陥るかもしれません。

もしルートディレクトリの権限をリセットしたい場合、実行権限のあるユーザー側で
chmodコマンドから変更するか、一度ルートディレクトリを削除し、再度アクセスポイントをマウントするようにします。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

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が126基スタンバイ状態になっていることも分かります。

            
            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ランタイムを使うユーザーに限っていえば、
「プロセスによってはsbx_userが異なるため、EFSにアクセスするための静的なPOSIXユーザーやグループを固定することができない」、ということを念頭においておきましょう。

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"でEFSのファイルシステム上に新規作成されます。

ハンドラー内部でシェルコマンド実行し、ルートディレクトリのアクセス権を覗いてみますと、

            
            $ ls -la /mnt/lambda
total 12
drwxrw-r-- 2 1002 1002 6144 Jan 16 11:37 .
#...省略
        
※ Lambda側の/piyoのマウント先が/mnt/lambdaの場合

と期待通りに設定されています。

前述したようにアクセスポイントに
PosixUserが指定されていない場合には、「POSIXユーザーを指定しなかった場合、Lambda側のPOSIXでEFS側へアクセスする」ルールが適用となります。

このアクセスポイントを使えば、
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をID強制を使って、

            
            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"にして、問答無用なアクセスさせた際に悩ましい問題であったファイルやフォルダのアクセス権がrootユーザー(0)が生成されてしまうことはないでしょう。

この意味でも、EFSのアクセスポイントの
PosixUser設定を正しく理解して使うことは重要になります。

EFSへアクセスできないパターン③〜ルートディレクトリに所有権とアクセス許可を指定していない

前述したようにLambdaのPOSIXルール・
「作成時にディレクトリの所有権と権限を指定しない場合、EFSはルートディレクトリを作成しない」を頭に入れていないと、LambdaはまともにEFSを使えない状況に陥ります。

どういうことか怖いもの見たさで検証してみましょう。

ここではあえて、ルートディレクトリの所有者情報をあえて与えないようにしてアクセスポイントを作成します。

            
            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側にマウントすらされないことが確認できました。


合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集

図解即戦力 Amazon Web Servicesのしくみと技術がこれ1冊でしっかりわかる教科書

まとめ

以上、再度今回の要点をまとめると、

            
            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"
        
になります。

記事を書いた人

記事の担当:taconocat

ナンデモ系エンジニア

主にAngularでフロントエンド開発することが多いです。 開発環境はLinuxメインで進めているので、シェルコマンドも多用しております。 コツコツとプログラミングするのが好きな人間です。

合同会社タコスキングダム|蛸壺の技術ブログ【AWS独習術】AWSをじっくり独学したい人のためのオススメ書籍&教材特集