上一篇已經完成共用測試環境的準備,在 Account B 建立 S3 Bucket,並在 Account A 建立 EC2 與 OrderApiRole,透過 Session Manager 連線後,也確認了 EC2 正在使用的 IAM 身分。
今天先實作第一種跨帳號存取方式:使用 Resource-based Policy,讓 Account A 的 OrderApiRole 直接讀取 Account B 的 S3 Bucket。
完成基本的 S3 讀取測試後,再將測試物件改用 SSE-KMS 加密,觀察相同的 IAM Role 在不同加密設定下,會遇到什麼權限問題。
本篇沿用上一篇建立的測試環境:
| 項目 | Account A:存取方 | Account B:資源擁有方 |
|---|---|---|
| Account ID | 111122223333 |
444455556666 |
| 來源 Role | OrderApiRole |
— |
| 目標 Role | — | ProductDataReadRole |
| S3 Bucket | — | shared-product-data-prod |
| Region | ap-northeast-1 |
ap-northeast-1 |
帳號 ID 與 Bucket 名稱都是範例,實作時需要換成自己的值。
先登入 Account A,透過 Session Manager 連線至上一篇建立的 EC2:
EC2 → Instances → 選擇 order-api-cross-account-test → Connect → Session Manager → Connect
進入 EC2 終端機後,先確認目前的 IAM 身分:
aws sts get-caller-identity
預期 ARN 類似:
arn:aws:sts::111122223333:assumed-role/OrderApiRole/i-0123456789abcdef0
接著,嘗試列出 Account B 的 S3 Bucket:
aws s3 ls s3://shared-product-data-prod/
由於目前尚未設定跨帳號存取權限,在沒有其他政策額外授權的情況下,預期會收到:
An error occurred (AccessDenied) when calling the ListObjectsV2 operation: Access Denied

這是因為目前 OrderApiRole 只有上一篇附加的 AmazonSSMManagedInstanceCore,尚未取得 Account B 的 S3 存取權限。
接下來會分別在 Account A 設定 Identity-based Policy,以及在 Account B 設定 Resource-based Policy,再重新執行相同指令,觀察授權前後的差異。
登入 Account A,進入 IAM → Roles → OrderApiRole → Permissions → Add permissions → Create inline policy。

切換至 JSON 編輯器,貼上以下內容:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListProductDataBucket",
"Effect": "Allow",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::shared-product-data-prod"
},
{
"Sid": "ReadProductDataObjects",
"Effect": "Allow",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shared-product-data-prod/*"
}
]
}

Policy name 可以自行輸入,最後點選 Create policy。
這份 IAM Policy 允許 OrderApiRole 執行兩種操作:
| Action | 用途 | Resource |
|---|---|---|
s3:ListBucket |
列出 Bucket 中的物件 | Bucket ARN |
s3:GetObject |
讀取及下載物件 | Object ARN |
這裡要注意,s3:ListBucket 是 Bucket 層級的權限,因此 Resource 不需要加上 /*;s3:GetObject 則是物件層級的權限,需要在 Bucket ARN 後面加上 /*,表示允許讀取 Bucket 中的物件。
本次只授予列出與讀取權限,不包含 s3:PutObject 或 s3:DeleteObject,因此不會因為這份 Policy 而取得上傳及刪除物件的權限。
完成後,Account A 的 OrderApiRole 已經有了允許讀取目標 Bucket 的 IAM Policy,但目前仍然缺少 Account B 資源側的授權。
登入 Account B,進入 S3 → General purpose buckets → shared-product-data-prod → Permissions → Bucket policy → Edit。
貼上以下 JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOrderApiRoleListBucket",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/OrderApiRole"
},
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::shared-product-data-prod"
},
{
"Sid": "AllowOrderApiRoleReadObjects",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/OrderApiRole"
},
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::shared-product-data-prod/*"
}
]
}
如果 Bucket 已經有其他 Policy,請將上述兩個 Statement 合併至原本的 Statement 陣列,不要直接覆蓋既有的授權設定,設定完畢後點選右下角的Save changes。
這份 Bucket Policy 與前面 IAM Policy 的主要差異,在於多了 Principal:
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/OrderApiRole"
}
Principal 指定的是「誰可以存取這個資源」。
這裡填入 Account A 的 OrderApiRole ARN,表示 Account B 允許這個特定的 Role 讀取 Bucket,而不是開放給 Account A 的所有 IAM 身分。
本次實作也不需要修改 OrderApiRole 原本信任 EC2 的 Trust Policy,因為 EC2 不會切換至 Account B 的 Role,而是直接以 Account A 的身分存取 S3。
完成兩側授權後,回到 Account A 的 EC2,重新執行:
aws s3 ls s3://shared-product-data-prod/
如果權限設定正確,且沒有其他政策限制,預期可以成功列出 Bucket 中的物件。
接著,下載上一篇上傳的測試檔案。以下以 test.png 為例,請將檔名替換成你實際上傳的物件名稱:
aws s3 cp \
s3://shared-product-data-prod/test.png \
./test.png

如果要驗證本次設定的是唯讀權限,也可以嘗試將檔案上傳回 Bucket:
aws s3 cp \
./test.png \
s3://shared-product-data-prod/test-upload.png
在沒有其他政策額外授權的情況下,預期會收到 AccessDenied,因為本次沒有授予 s3:PutObject 權限。
最後,再次確認目前使用的 IAM 身分:
aws sts get-caller-identity
預期 ARN 仍然是:
arn:aws:sts::111122223333:assumed-role/OrderApiRole/i-0123456789abcdef0

可以看到,即使成功讀取了 Account B 的 S3 Bucket,EC2 使用的仍然是 Account A 的 OrderApiRole。
整個過程並沒有 AssumeRole 至 Account B,而是透過 Account B 的 Bucket Policy,直接授權 Account A 的 Role 存取資源。
到這裡,第一種跨帳號存取方式就完成了。
上一篇建立 S3 Bucket 時,預設加密方式使用的是 SSE-S3。
SSE-S3 由 Amazon S3 管理加密金鑰,當 IAM 權限允許讀取物件時,S3 會自動處理解密,不需要另外設定 KMS 權限。
但如果物件使用的是 SSE-KMS,除了 S3 的存取權限之外,還需要確認目前的 IAM 身分是否具有使用對應 KMS Key 的權限。
參考:AWS 官方文件|Using server-side encryption with AWS KMS keys
接下來沿用相同的 OrderApiRole,分別測試 AWS managed key 與 Customer managed key。
先登入 Account B,進入 S3 → General purpose buckets → shared-product-data-prod → Properties → Default encryption → Edit。

將加密方式修改為:
aws/s3)。
儲存設定後,重新上傳一個測試檔案。
這裡要注意,修改 Bucket 的預設加密設定,不會自動改變先前已上傳物件的加密方式。因此,這次另外上傳測試物件,確保它實際使用 SSE-KMS 加密。

上傳完成後,可以再點選剛才上傳的物件 → Properties → Server-side encryption settings,確認物件使用 SSE-KMS,且 KMS Key 為 aws/s3。
接著回到 Account A 的 EC2,以下以 test-aws-kms.png 為例,請將檔名替換成你實際上傳的物件名稱,執行:
aws s3 cp \
s3://shared-product-data-prod/test-aws-kms.png \
./test-aws-kms.png
預期會收到類似以下錯誤:
download failed: s3://shared-product-data-prod/test-aws-kms.png
An error occurred (AccessDenied) when calling the GetObject operation: Access Denied

這次的 AccessDenied 並不是因為缺少 S3 Bucket Policy,而是因為物件使用了 Account B 的 AWS managed KMS key。
AWS managed key 由 AWS 管理其 Key Policy,無法自行修改政策來授權其他 AWS 帳號的 IAM 身分使用,因此無法直接將使用 Account B 的 aws/s3 加密的物件分享給 Account A 讀取。
參考:AWS 官方文件|Allowing users in other accounts to use a KMS key
也就是說,即使 Account A 的 IAM Policy 和 Account B 的 Bucket Policy 都已經允許 s3:GetObject,仍然無法直接讀取使用 Account B 的 aws/s3 加密的物件。
如果要讓其他帳號讀取 SSE-KMS 加密的 S3 物件,就需要改用可以自行設定 Key Policy 的 Customer managed key。
接著登入 Account B,進入 Key Management Service(KMS)→ Customer managed keys → Create key。
設定如下:
| 項目 | 設定 |
|---|---|
| Key type | Symmetric |
| Key usage | Encrypt and decrypt |
| Key material origin | KMS |
| Alias | alias/product-data-cross-account |
| Region | ap-northeast-1 |
其他設定可以先保留預設值。
在 Define key administrative permissions 階段,選擇具有 KMS 管理權限的管理者。
在 Define key usage permissions 階段,可以先保留適當的 Account B 本地使用者或角色權限;後續再透過 Key Policy 明確授權 Account A 的 OrderApiRole。
建立完成後,進入該 KMS Key 的詳細資訊頁面,記錄 Key ARN。
範例如下:
arn:aws:kms:ap-northeast-1:444455556666:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
後續 IAM Policy 與 Key Policy 都會使用這個 ARN,實作時請替換成你實際建立的 Key ARN。

進入 KMS → Customer managed keys → 選擇剛才建立的 Key → Key policy。
切換至 Policy view,找到原有政策的 Statement 陣列,加入以下 Statement:
{
"Sid": "AllowAccountAOrderApiRoleDecrypt",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::111122223333:role/OrderApiRole"
},
"Action": "kms:Decrypt",
"Resource": "*"
}
這裡要注意,請保留原本 Key Policy 中的管理者權限及其他必要設定,不要直接用上述內容覆蓋整份 Key Policy。

這份授權代表 Account B 允許 Account A 的 OrderApiRole 使用這把 KMS Key 執行 kms:Decrypt。
由於本次只需要讀取 S3 物件,因此先授予 kms:Decrypt 即可,不需要額外授予 kms:Encrypt 或 kms:GenerateDataKey。
回到 Account A,進入 IAM → Roles → OrderApiRole → Permissions → Add permissions → Create inline policy。
貼上以下 JSON:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DecryptAccountBProductData",
"Effect": "Allow",
"Action": "kms:Decrypt",
"Resource": "arn:aws:kms:ap-northeast-1:444455556666:key/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
]
}

Policy name 可以自行輸入,最後點選 Create policy。
這裡與前面的跨帳號 S3 授權概念類似:Account A 的 IAM Policy 允許 OrderApiRole 使用指定 KMS Key,而 Account B 的 Key Policy 則允許該 Role 使用這把金鑰。
對於跨帳號使用 Customer managed key 的情境,僅設定 IAM Policy 或 Key Policy 其中一側並不足夠,需要同時具備適當授權。
參考:AWS 官方文件|Allowing users in other accounts to use a KMS key
完成 KMS 授權後,回到 Account B 的 S3 Bucket。
進入 Properties → Default encryption → Edit,將預設加密方式維持在 SSE-KMS,並改為選擇剛才建立的 Customer managed key。
儲存設定後,重新上傳一個新的測試物件。
上傳完成後,進入物件的 Properties,確認其加密方式為 SSE-KMS,且使用的 KMS Key ARN 與剛才建立的 Customer managed key 相同。

回到 Account A 的 EC2,以下以 test-aws-cmk.png 為例,請將檔名替換成你實際上傳的物件名稱,執行:
aws s3 cp \
s3://shared-product-data-prod/test-aws-cmk.png \
./test-aws-cmk.png
如果 S3 與 KMS 的授權都設定正確,且沒有其他政策限制,預期可以成功下載物件。
最後,再次執行:
aws sts get-caller-identity
預期仍然顯示:
arn:aws:sts::111122223333:assumed-role/OrderApiRole/i-0123456789abcdef0
這代表即使讀取的是 Account B 使用 Customer managed key 加密的 S3 物件,目前使用的仍然是 Account A 的 OrderApiRole。

經過前面的操作,可以將三種加密方式的跨帳號讀取結果整理如下:
| 加密方式 | 額外 KMS 授權 | 預期讀取結果 |
|---|---|---|
| SSE-S3 | 不需要 | 成功 |
| SSE-KMS(AWS managed key) | 無法跨帳號授權使用 aws/s3 |
AccessDenied |
| SSE-KMS(Customer managed key) | 需要設定 KMS Key Policy 與 IAM Policy | 授權後成功 |
本篇使用 IAM Policy 與 Bucket Policy,讓 Account A 的 OrderApiRole 直接讀取 Account B 的 S3 Bucket,權限分配如下圖:

這種做法需要由兩側分別授權:
OrderApiRole 讀取目標 S3 Bucket。OrderApiRole 存取 Bucket。當兩側都完成授權,且沒有其他政策限制時,就能直接跨帳號讀取 S3,不需要 AssumeRole 至 Account B。
如果物件使用 SSE-KMS 加密,則還需要確認是否具有對應的 KMS 解密權限。
今天完成了第一種跨帳號存取方式,透過 Account A 的 IAM Policy 與 Account B 的 Bucket Policy,讓 EC2 直接讀取另一個帳號的 S3 Bucket。
即使成功讀取物件,透過 aws sts get-caller-identity 仍然可以看到,目前使用的是 Account A 的 OrderApiRole。
下一篇會改用第二種方式:在 Account B 建立 ProductDataReadRole,讓 Account A 的 OrderApiRole 先透過 STS AssumeRole 取得 Account B 的暫時性身分,再使用該身分讀取相同的 S3 Bucket。