特定の IAM ロールのみアクセスできる S3 バケットを実装する際に検討したあれこれ
今回は S3 バケットへのアクセスを特定 IAM ロールからのみに限定して利用する機会がありましたので、設定方法と検討したあれこれをご紹介します。
やりたいこと
構成図はこんな感じ
前提条件
- IAM ロールと S3 バケットは同一アカウントに存在する
- IAM ロールには S3 を管理する権限がアタッチされている
- 今回は AmazonS3FullAccess ポリシーをアタッチしています
NotPrincipal でやってみる
「特定 IAM ロール以外は制限する」という考え方でパッと思いつくのは、以下のような NotPrincipal
で制限する方法かと思います。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "NotPrincipal": { "AWS": "arn:aws:iam::xxxxxxxxxxxx:role/cm-marumo.atsushi" }, "Action": "s3:*", "Resource": "arn:aws:s3:::cm-marumo-test/*" } ] }
cm-marumo.atsushi
ロール以外は Deny されるcm-marumo.atsushi
には AmazonS3FullAccess の権限がある
一見、これで出来そうに思います。実際に DeleteObject
を試してみましょう。
$ aws s3 rm s3://cm-marumo-test/test.txt delete failed: s3://cm-marumo-test/test.txt An error occurred (AccessDenied) when calling the DeleteObject operation: Access Denied
出来ません、、、。
なぜ NotPrincipal
では出来ないのでしょう?
assumed-role とセッション
IAM ユーザーからのスイッチロールや、AWS サービスにロールをアタッチして利用している場合、内部的には sts:AssumeRole
が発行されて一時的なクレデンシャルを取得してセッションを確立しています。
つまり、NotPrincipal
で特定 IAM ロールを除外する場合、STS で発行された以下のような assumed-role
の ARN も含めて除外してあげなければなりません。
arn:aws:sts::<アカウントID>:assumed-role/<ロール名>/<セッション名>
しかし、ここで厄介になるのがセッション名です。
セッション名の部分が固定されているのであれば話が早いのですが、ランダムに割り当てられて固定化することができないケースも多いです。たとえば、今回のようなスイッチロールではセッションの都度、変わってしまいます。
逆に固定されているケースとしては、以前にご紹介した Lambda にアタッチしているロールです。Lambda の場合、セッション名部分には関数名が入りますので固定化することが出来ます。(詳細は以前のブログを参照ください)
じゃぁワイルドカードで・・・
と思いますよね。私もそう考えました。
しかし、NotPrincipal
の指定でセッション部分をワイルドカードにすることは出来ません。
NotPrincipal 要素で引き受けたロールセッションを指定する場合、ワイルドカード (*) を使用して「すべてのセッション」を意味することはできません。プリンシパルは常に特定のセッションに名前を付ける必要があります。
(AWS JSON ポリシーの要素: NotPrincipal)
どうやら、セッション名が固定出来ない場合に NotPrincipal
で実現するのは難しそうです。
ということで次に、実装した代替案を紹介します。
(結論)aws:userId を使った代替案
前置きが長くなりましたが、NotPrincipal
では実現できそうにないということが判ったので、代替案として利用したのが aws:userId
の Condition
で絞り込む方法です。
aws:userId
「ロールでやりたかったんじゃないの?」
という声が聞こえてきそうですが、assume-role しているセッションの aws:userId
にはロール ID が含まれています。
以下の sts get-caller-identity
コマンドで確認すると確認することが出来ます。
$ aws sts get-caller-identity Account: 'xxxxxxxxxxxx' Arn: arn:aws:sts::xxxxxxxxxxxx:assumed-role/cm-marumo.atsushi/1583xxxxxxxxxxxxxxx UserId: AROAxxxxxxxxxxxxxxVAI:1583xxxxxxxxxxxxxxx
UserId
の AROA
で始まり :
の部分まではロール ID となっており、:
より後ろがセッション名です。
iam get-role
コマンドでロール ID を確認すると合致しているのが判りますね。
$ aws iam get-role --role-name cm-marumo.atsushi Role: Arn: arn:aws:iam::xxxxxxxxxxxx:role/cm-marumo.atsushi AssumeRolePolicyDocument: Statement: - Action: sts:AssumeRole Condition: {} Effect: Allow Principal: AWS: arn:aws:iam::xxxxxxxxxxxx:user/cm-marumo.atsushi Version: '2012-10-17' CreateDate: '2018-08-09T08:37:00+00:00' MaxSessionDuration: 3600 Path: / RoleId: AROAxxxxxxxxxxxxxxVAI RoleLastUsed: LastUsedDate: '2020-03-10T08:49:00+00:00' Region: us-east-1 RoleName: cm-marumo.atsushi
バケットポリシーをこう書く
Condition
の aws:userId
ではセション名にワイルドカードを指定することが出来ますので、バケットポリシーを以下のように書くことができます。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Deny", "Principal": "*", "Action": "s3:*", "Resource": "arn:aws:s3:::cm-marumo-test/*", "Condition": { "StringNotLike": { "aws:userId": [ "AROAxxxxxxxxxxxxxxVAI:*" ] } } } ] }
注意いただきたいのは ロール ID の後ろの :*
です。この指定がないと、条件にマッチしません。
動作確認
この状態で先程と同じように DeleteObject
してみましょう。
$ aws s3 rm s3://cm-marumo-text/test.txt delete: s3://cm-marumo-test/test.txt
ということで紆余曲折ありましたが、Condition
と aws:userID
を使って特定の IAM ロールのみにアクセスを限定させることが出来ました!
さいごに
ちょっと凝ったバケットポリシーを書こうとしたとき、いつも思うのですが
「バケットポリシーは奥が深い」
毎度、毎度、「あぁ・・、俺は雰囲気でバケットポリシー使ってたのか・・・」と反省します。
特定の IAM ロールのみにバケットアクセスを許可させいたケースはよくある要望かと思いますので、同じような要件がありましたら、是非、参考にしてください。
以上!大阪オフィスの丸毛(@marumo1981)でした!