特定の IAM ロールのみアクセスできる S3 バケットをaws:PrincipalArnを用いて実装した

aws:PrincipalArnを用いたBucketPolicyの記載はわかりやすいし、簡単です。
2023.07.07

はじめに

好物はインフラとフロントエンドのかじわらゆたかです。

特定のIAM Roleからのみアクセスを受け付けるS3バケットを書こうとする場合、 以下のブログを参考にIAM Roleに紐づいているaws:userIdを用いる方法を行っていましたが、aws:PrincipalArnを用いることで同様のことができました。

aws:PrincipalArn ってなに?

AWS グローバル条件コンテキストキーの一つです。 ドキュメントに以下のが記載されております。

このキーを使用して、リクエストを行ったプリンシパルの Amazon リソースネーム (ARN) をポリシーで指定したARN と比較します。IAM ロールの場合、リクエストコンテキストは、ロールを引き受けたユーザーの ARN ではなく、ロールの ARN を返します。

AWS グローバル条件コンテキストキー - AWS Identity and Access Management

確かにロールのARNを指定することが可能に見えるので、確認してみました。

検証用Cloudformation テンプレート

s3-bucket-acces-to-a-specific-role-use-principal-arn.yaml

上記のテンプレートを動かして検証しました。 上記のテンプレートを作成すると以下のリソースが作成されます。

  • アクセスが許可されたIAM Role
  • アクセスが許可されていないIAM Role
  • アクセスが許可されたIAM Roleからアクセス可能な検証用のS3バケット
  • SFTPプロトコルで待ち受けるTransferFamilyのサーバー
  • アクセスが許可されたIAM Roleと紐づいているSFTPサーバーのユーザー
  • アクセスが許可されていないIAM Roleと紐づいているSFTPサーバーのユーザー
  • アクセスが許可されたIAM Roleと紐づいているS3にアクセスるするためのLambda
  • アクセスが許可されていないIAM Roleと紐づいているS3にアクセスるするためのLambda

S3のBucketPolicyは以下のようになっています

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "MultiRestrictPolicy",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": [
        "arn:aws:s3:::{検証用のS3バケット}",
        "arn:aws:s3:::{検証用のS3バケット}/*"
      ],
      "Condition": {
        "StringNotEquals": {
          "aws:PrincipalArn": "{アクセスが許可されたIAM RoleのARN}",
          "aws:CalledVia": "cloudformation.amazonaws.com"
        }
      }
    }
  ]
}

なお、SFTPユーザー用の公開鍵は作成してパラメータストアに登録しておく必要があります。 公開鍵の作成と登録は以下のようなシェルを作りました。

ssh-keygen -t rsa -b 4096 -C "s3-access-check-key" -f ./s3-access-check-key
aws ssm put-parameter \
    --name s3-access-check-key \
    --value \"$(cat ./s3-access-check-key.pub)\" \
    --type String

検証してみた

IAM Roleでアクセスしてみた

以下のようなaws/configを作成し、Assume元をIAM User/IAM Roleで確認しました。

[profile check-access-allow-from-role]
output = json
role_arn = {アクセスが許可されたIAM Role}
source_profile = {Assume元のIAM Role}
region = ap-northeast-1

[profile check-access-allow-from-user]
output = json
role_arn = {アクセスが許可されたIAM Role}
source_profile = {Assume元のIAM User}
region = ap-northeast-1

[profile check-access-deny-from-role]
output = json
role_arn = {アクセスが許可されてないIAM Role}
source_profile = {Assume元のIAM Role}
region = ap-northeast-1

[profile check-access-deny-from-user]
output = json
role_arn = {アクセスが許可されていないIAM Role}
source_profile = {Assume元のIAM User}
region = ap-northeast-1

想定通りアクセス用のIAM Roleの権限でアクセス制御ができています。

$ aws s3 cp ./Happy20thanniversarytoClassMethod.txt s3://cm-kajiwara-access-check --profile check-access-allow-from-role
upload: ./Happy20thanniversarytoClassMethod.txt to s3://cm-kajiwara-access-check/Happy20thanniversarytoClassMethod.txt

$ aws s3 ls cm-kajiwara-access-check --profile check-access-allow-from-role
2023-07-07 14:20:51          0 Happy20thanniversarytoClassMethod.txt

$ aws s3 ls cm-kajiwara-access-check --profile check-access-allow-from-user
2023-07-07 14:20:51          0 Happy20thanniversarytoClassMethod.txt

$ aws s3 ls cm-kajiwara-access-check --profile check-access-deny-from-user

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

$ aws s3 ls cm-kajiwara-access-check --profile check-access-deny-from-role

An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

アクセスが許可されたIAM RoleにAssume Roleをした後にアクセスができていることと、 アクセスが許可されてないIAM RoleにAssume Roleをしたあとにアクセスができていないことが確認できました。

このような形でSession名を変更しても問題ありません

[profile check-access-allow-from-role-chenge-session-name]
role_arn =  {アクセスが許可されたIAM Role}
role_session_name = anniversary_to_classmethod
source_profile = {Assume元のIAM Role}
$ aws s3 ls cm-kajiwara-access-check --profile check-access-allow-from-role-chenge-session-name
2023-07-07 14:20:51          0 Happy20thanniversarytoClassMethod.txt

Lambdaでアクセスしてみた

実装したLambdaを実行してみました こちらも割り当てた権限通りの動きをしています。

$ aws lambda invoke --function-name {アクセスが許可されたIAM Roleと紐づいているS3にアクセスるするためのLambda}  out --log-type Tail --query 'LogResult' --output text |  base64 -d
START RequestId: dd5bfc27-f6d1-49e1-9c0e-8d009bcbfbd4 Version: $LATEST
END RequestId: dd5bfc27-f6d1-49e1-9c0e-8d009bcbfbd4
REPORT RequestId: dd5bfc27-f6d1-49e1-9c0e-8d009bcbfbd4	Duration: 2426.35 ms	Billed Duration: 2427 ms	Memory Size: 128 MB	Max Memory Used: 77 MB	Init Duration: 272.55 ms
$ aws lambda invoke --function-name {アクセスが許可されてないIAM Roleと紐づいているS3にアクセスるするためのLambda} out --log-type Tail --query 'LogResult' --output text |  base64 -d
START RequestId: 94804667-3dd5-4988-8bff-3e54867e9ad4 Version: $LATEST
[ERROR] ClientError: An error occurred (403) when calling the HeadObject operation: Forbidden
Traceback (most recent call last):
  File "/var/task/index.py", line 6, in lambda_handler
    s3.download_file("cm-kajiwara-access-check", 'Happy20thanniversarytoClassMethod.txt', '/tmp/Happy20thanniversarytoClassMethod.txt')
(中略)
REPORT RequestId: 94804667-3dd5-4988-8bff-3e54867e9ad4	Duration: 338.34 ms	Billed Duration: 339 ms	Memory Size: 128 MB	Max Memory Used: 79 MB

TransferFamilyでアクセスしてみた

こちらも権限の想定通りの動きをしています

$ sftp -i ./s3-access-check-key {アクセスが許可されたIAM Roleと紐づいているSFTPサーバーのユーザー}@{SFTPプロトコルで待ち受けるTransferFamilyのサーバー}
Connected to {SFTPプロトコルで待ち受けるTransferFamilyのサーバー}.
sftp> cd cm-kajiwara-access-check
sftp> ls
Happy20thanniversarytoClassMethod.txt
$ sftp -i ./s3-access-check-key  {アクセスが許可されてないIAM Roleと紐づいているSFTPサーバーのユーザー}@{SFTPプロトコルで待ち受けるTransferFamilyのサーバー}
Connected to {SFTPプロトコルで待ち受けるTransferFamilyのサーバー}.
sftp>  cd cm-kajiwara-access-check
sftp> ls
Couldn't read directory: Permission denied

まとめ

特定のIAM RoleからのみアクセスできるS3バケットとそのバケットポリシーをaws:PrincipalArnを用いる形で記載することができました。

今回様々なケースで検証しましたが、どのケースでも想定通りの動きをしてくれました。 この方法だとバケットポリシーを見直したときも、 aws:userIdを用いていたときに比べIAM RoleのARNが確認できることになります。

特定のIAM Roleからのみアクセスしたいと言ったとき、この方法を採用していただければと思います。

補足

BucketPolicyは非常に強い制約となり、間違えて記載するとAdministrator Policyが割当たっているIAMであっても書き換えが行えなくなります。 ですので、直接書き換えるのではなくCloudformationから記載をすることをおすすめいたします。 こちらの詳細については以下のブログが参考になります。