はじめに
外部アカウント(別のAWSアカウント)からS3バケットにアクセスする構成は、クロスアカウント連携でよくあるパターンです。
バケットポリシー、アクセス元のIAMロールで許可を出していれば問題なくアクセスできると思いきや、バケットがカスタマー管理型SSE-KMSで暗号化されている場合は、それだけでは403 Access Deniedになるという落とし穴があります。(私はハマりました。。。)
こんなことがありました...
今までのS3運用ではSSE-S3を利用しバケットの暗号化を行っていましたが、突如セキュリティチームより「今後はS3にはSSE-KMSを利用しなさい」というお達しが。
当時ちょうど外部アクセスされるS3バケットの作成を予定しており、いつも通りS3バケットを作成し、暗号化はプロジェクトで利用している既存のKMSで使っているのKMSキーを設定し疎通確認をおこなったところ繋がらん。。という事態になりました。
※KMSを新規作成した時ならほぼ確実に気づけます。
結論
SSE-S3 で暗号化されたバケットは、バケットポリシー/IAMの許可だけで外部アカウントからアクセスできる。
SSE-KMS で暗号化されたバケットは、バケットポリシーに加えて KMSキーポリシー側にも外部アカウントへの許可 が必要。
つまり「暗号化方式によって、必要な許可設定の数が変わる」というのがポイントです。
なぜ違いが生まれるのか
| 暗号化方式 | 鍵の管理者 | 暗号化/復号の主体 | 必要な許可 |
|---|---|---|---|
| SSE-S3 | AWS(マネージド) | S3サービス内部で完結 | バケットポリシー/IAMのみ |
| SSE-KMS | KMSカスタマー管理キー | KMSが鍵を使って暗号化/復号 | バケットポリシー/IAM + KMSキーポリシー |
SSE-S3はAWSが内部で鍵を管理しており、ユーザー側からは鍵の存在を意識する必要がありません。そのため、S3オブジェクトへのアクセス可否はバケットポリシーやIAMポリシーだけで決まります。
一方SSE-KMSは、暗号化・復号のたびにKMSキーへのアクセスが発生します。S3はオブジェクトを返す前に裏側でKMSの kms:Decrypt(アップロード時は kms:GenerateDataKey)を呼び出しており、呼び出し元(外部アカウントのIAMロール等)がそのKMSキーに対する権限を持っていなければ拒否されます。バケットポリシーで許可していても、KMSキー側の許可がなければアクセスは失敗します。
つまり、SSE-KMSの場合は 「バケットポリシー」と「KMSキーポリシー」の両方の許可を満たす必要がある という点が最大の落とし穴です。
※AWSマネージド型KMSキーはKMSキーポリシーの編集ができないため外部からのアクセスはできません。
SSE-S3とSSE-KMS、それぞれのメリット・デメリット
「じゃあクロスアカウントで面倒なSSE-KMSなんて使わずSSE-S3でいいのでは?」と思うかもしれませんが、それぞれにメリット・デメリットがあるため、要件に応じて選ぶ必要があります。
SSE-S3
メリット
- 設定が不要で、デフォルトでも有効になっている(追加コストもなし)
- 鍵の管理を一切意識しなくてよく、運用がシンプル
- クロスアカウントアクセスもバケットポリシー/IAMだけで完結する(今回の落とし穴がそもそも発生しない)
デメリット
- 鍵の利用ログ(誰がいつ復号したか)を個別に追跡できない。CloudTrailでKMSのAPI呼び出しログを見る、といった監査ができない
- 鍵のローテーションや無効化など、鍵自体の制御がユーザー側にできない
- 鍵単位でのアクセス制御(特定のロールだけ復号を許可、など)ができない。アクセス制御の粒度はバケット/オブジェクト単位に限られる
SSE-KMS
メリット
- 鍵単位でアクセス制御できる(同じバケットでも、鍵ポリシーを使って復号できる人/ロールを細かく制限できる)
- CloudTrailで
kms:Decrypt/kms:GenerateDataKeyの呼び出しが記録されるため、誰がいつ鍵を使ったかを監査できる - カスタマー管理キーであれば、鍵のローテーション、無効化、削除のスケジューリングなど鍵のライフサイクルを自分で制御できる
- コンプライアンス要件(鍵の管理主体を自社にする必要がある、など)に対応しやすい
デメリット
- KMSのAPIコール(暗号化・復号のたび)に対して課金が発生する(リクエスト数に応じた従量課金)
- カスタマー管理キーは1ヶ月あたりの固定費がかかる(AWS管理キー
aws/s3は無料だが、前述の通りキーポリシーを編集できないためクロスアカウント共有には使えない) - クロスアカウントやクロスサービル連携の際に、バケットポリシーとKMSキーポリシーの両方を設定する必要があり、設定漏れによる
AccessDeniedが起きやすい(まさに本記事の落とし穴) - KMSのAPIリクエスト制限(スロットリング)があるため、大量のオブジェクトに高頻度でアクセスするワークロードでは考慮が必要
どっちがいいの?
- 鍵の利用監査や、鍵単位でのアクセス制御が不要 → SSE-S3 でシンプルに済ませる
- 監査要件がある、鍵のライフサイクルを自社で管理したい、特定のロールだけに復号を許可したい → SSE-KMS
実際に起きる症状
- バケットポリシーで外部アカウント(または特定のIAMロール)に
s3:GetObjectを許可した - 外部アカウント側でもそのバケットへの
s3:GetObjectをIAMポリシーで許可した
この状態でファイルアップロードなどの操作を行うとエラーになります。(エラーメッセージを見ればわかりますね)
例:
[root@ip-10-0-0-1 tmp]# aws s3 cp ./test.txt s3://s3-bucket
upload failed: ./test.txt to s3://s3-bucket/test.txt An error occurred (AccessDenied) when calling the PutObject operation: User: arn:aws:sts::xxxxxxxxxxxxxx:assumed-role/ec2-role/i-0d26084946ed30b03 is not authorized to perform: kms:GenerateDataKey on resource:arn:aws:kms:ap-northeast-1:xxxxxxxxxxxxxx:key/xxxxxxxxxx because no identity-based policy allows the kms:GenerateDataKey action
[root@ip-ip-10-0-0-1 tmp]#
解決策
KMS操作を許可する外部アカウントのポリシーとS3に設定しているKMSキーポリシーにステートメントを追加します。
外部アカウントのポリシー:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowS3Access",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::YOUR_BUCKET_NAME",
"arn:aws:s3:::YOUR_BUCKET_NAME/*"
]
},
------この部分のポリシーが追加で必要です------
{
"Sid": "AllowKMSForSSEKMS",
"Effect": "Allow",
"Action": [
"kms:Decrypt",
"kms:GenerateDataKey"
],
"Resource": "arn:aws:kms:ap-northeast-1:ACCOUNT_A_ID:key/KMS_KEY_ID"
}
------------------------------------------
]
}
KMSキーポリシー:
{
"Id": "key-consolepolicy-3",
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Enable IAM User Permissions",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::xxxxxxxxxxxxxx:root"
},
"Action": "kms:*",
"Resource": "*"
},
{
"Sid": "Allow access for Key Administrators",
"Effect": "Allow",
"Principal": {
"AWS":
"arn:aws:iam::xxxxxxxxxxxxxx:role/Administrator-Role"
},
"Action": [
"kms:Create*",
"kms:Describe*",
"kms:Enable*",
"kms:List*",
"kms:Put*",
"kms:Update*",
"kms:Revoke*",
"kms:Disable*",
"kms:Get*",
"kms:Delete*",
"kms:TagResource",
"kms:UntagResource",
"kms:ScheduleKeyDeletion",
"kms:CancelKeyDeletion",
"kms:RotateKeyOnDemand"
],
"Resource": "*"
},
{
"Sid": "Allow use of the key",
"Effect": "Allow",
"Principal": {
"AWS":
"arn:aws:iam::xxxxxxxxxxxxxx:role/Administrator-Role"
},
"Action": [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
],
"Resource": "*"
},
{
"Sid": "Allow attachment of persistent resources",
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::xxxxxxxxxxxxxx:role/Administrator-Role",
------この部分のポリシーが追加で必要です------
"外部アカウントのロールArn"
-------------------------------------------
]
},
"Action": [
"kms:CreateGrant",
"kms:ListGrants",
"kms:RevokeGrant"
],
"Resource": "*",
"Condition": {
"Bool": {
"kms:GrantIsForAWSResource": "true"
}
}
}
]
}
※外部アカウントのキーユーザはコンソールからは見つからないため以下の画面から直接ポリシーを修正する必要が在ります。

まとめ
- SSE-S3とSSE-KMSでは、外部アカウントへのアクセス許可に必要な設定箇所が異なる
- SSE-KMSはバケットポリシーだけでなく、KMSキーポリシー側の許可も必須
- AWS管理キー(
aws/s3)はキーポリシーを編集できないため、クロスアカウント共有が必要な場合はカスタマー管理キーを使う
以上です!最後まで読んでいただきありがとうございました。