特定の IAM ロールのみアクセスできる S3 バケットを実装する際に検討したあれこれ

バケットポリシーは奥が深い
2020.03.11

今回は 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:userIdCondition で絞り込む方法です。

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

UserIdAROA で始まり : の部分まではロール 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

バケットポリシーをこう書く

Conditionaws: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

ということで紆余曲折ありましたが、Conditionaws:userID を使って特定の IAM ロールのみにアクセスを限定させることが出来ました!

さいごに

ちょっと凝ったバケットポリシーを書こうとしたとき、いつも思うのですが

「バケットポリシーは奥が深い」

毎度、毎度、「あぁ・・、俺は雰囲気でバケットポリシー使ってたのか・・・」と反省します。

特定の IAM ロールのみにバケットアクセスを許可させいたケースはよくある要望かと思いますので、同じような要件がありましたら、是非、参考にしてください。

以上!大阪オフィスの丸毛(@marumo1981)でした!

参考