CT.S3.PV.4
コントロールの説明
組織外の IAM プリンシパル(IAM ユーザー、IAM ロール)による S3 リソースへのアクセスを拒否する。
本コントロールは RCP(リソースコントロールポリシー)により、組織外の IAM プリンシパルからの S3 API 操作をブロックする。Control Tower で対象の OU を指定して有効化すると、その OU 配下の全アカウントの全 S3 リソースに適用される。
RCP テンプレートの Condition は以下の 2 つの条件を AND で評価し、両方を満たす場合に拒否する。
aws:PrincipalIsAWSServiceがfalse(AWS サービスプリンシパルでない)aws:PrincipalOrgIDが自組織の ID と一致しない(組織外のプリンシパルである)
したがって、以下のように動作する。
| プリンシパルの種類 | 拒否されるか | 理由 |
|---|---|---|
| 組織内の IAM ユーザー / ロール | されない | PrincipalOrgID が一致する |
| 組織外の IAM ユーザー / ロール | される | 両方の条件を満たす |
| AWS サービスプリンシパル(CloudTrail 等) | されない | PrincipalIsAWSService が true |
AWS サービスプリンシパルとは、cloudtrail.amazonaws.com や config.amazonaws.com のように AWS サービス自体がリソースに直接アクセスする場合のプリンシパルである。IAM ロールを引き受けてアクセスするサービスロールはサービスプリンシパルではなく IAM ロールとして扱われるため、組織外であれば拒否対象となる。サービスプリンシパルの詳細は AWS 公式ドキュメントを参照。
パラメータは以下の 1 つ。
ExemptedPrincipalArns(任意): RCP の拒否対象外にする IAM プリンシパルの ARN。組織外のパートナーアカウントや外部サービスのロール等、特定のプリンシパルにアクセスを許可する場合に使用する
本コントロールはバケットポリシーやリソースポリシーを変更するのではなく、独立したアクセス制御レイヤーとして機能する。バケットポリシーで組織外アカウントにアクセスを許可していても、本コントロールが有効な場合は RCP により拒否される。
S3 の組織外アクセス制限には以下の方法がある。
| 方法 | 内容 | スコープ |
|---|---|---|
| IAM Access Analyzer | 外部アクセスアナライザーで S3 の外部共有を検出 | 検出のみ(ブロックしない) |
| バケットポリシー(手動) | バケットごとにアクセス許可を管理 | バケット単位 |
| CT.S3.PV.4(本コントロール) | OU 配下の全バケットで組織外アクセスを自動的に拒否 | OU 単位 |
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
検証の流れ
flowchart LR
A[1. 事前準備] --> B[2. 組織内・組織外<br>両方成功]
B --> C[3. CT.S3.PV.4<br>有効化]
C --> D[4. 既存設定<br>維持を確認]
C --> E[5. 組織内アクセス<br>成功を確認]
C --> F[6. 組織外アクセス<br>拒否を確認]
C --> G[7. サービスプリンシパル<br>成功を確認]
D & E & F & G --> H[8. CT.S3.PV.4<br>無効化]
H --> I[9. 組織外アクセス<br>成功を確認]
結果
- コントロールの有効化前、組織内・組織外の両方から S3 オブジェクトにアクセスできることを確認できた。
- 有効化前に、組織外アカウントの CloudTrail 証跡から組織内バケットへの書き込み(サービスプリンシパル経由)が成功することを確認できた。
- コントロールの有効化後、既存のバケットポリシー(組織外アカウントへの許可)は維持されることを確認できた。
- 組織内プリンシパルからのアクセスは引き続き成功することを確認できた。
- 組織外プリンシパルからのアクセス(GetObject、PutObject)が拒否されることを確認できた。ただし、エラーメッセージは
Access Deniedのみで、CT.S3.PV.5 や CT.SECRETSMANAGER.PV.1 で表示されるexplicit deny in a resource control policyは含まれなかった(ステップ 6 参照)。 - 組織外アカウントの CloudTrail 証跡から組織内バケットへの書き込み(サービスプリンシパル経由)は、コントロール有効化後も成功することを確認できた(ステップ 7 参照)。RCP の
aws:PrincipalIsAWSService条件により、サービスプリンシパルは拒否対象外となる。 - コントロールの無効化後、組織外プリンシパルからのアクセスが再び成功することを確認できた。
- RCP の拒否対象である GetObject / PutObject は CloudTrail のデータイベントに分類されるため、デフォルトでは CloudTrail に記録されない(補足参照)。
1. 事前準備
テスト用の S3 バケットを作成する。
aws s3api create-bucket \
--bucket ct-s3-pv4-test \
--create-bucket-configuration LocationConstraint=ap-northeast-1{
"Location": "http://ct-s3-pv4-test.s3.amazonaws.com/",
"BucketArn": "arn:aws:s3:::ct-s3-pv4-test"
}テスト用オブジェクトをアップロードする。
echo "test data" | aws s3 cp - s3://ct-s3-pv4-test/test.txt
(出力なし)バケットポリシーで組織外アカウントと CloudTrail サービスプリンシパルにアクセスを許可する。
aws s3api put-bucket-policy \
--bucket ct-s3-pv4-test \
--policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountAccess",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<組織外アカウント ID>:user/<IAM ユーザー名>"},
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::ct-s3-pv4-test/*"
},
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::ct-s3-pv4-test",
"Condition": {
"StringEquals": {"aws:SourceAccount": "<組織外アカウント ID>"}
}
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::ct-s3-pv4-test/AWSLogs/<組織外アカウント ID>/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceAccount": "<組織外アカウント ID>"
}
}
}
]
}'
(出力なし)CloudTrail 用のステートメント(AWSCloudTrailAclCheck、AWSCloudTrailWrite)は、ステップ 7 で組織外アカウントの CloudTrail 証跡がこのバケットにログを書き込めるようにするためのものである。aws:SourceAccount 条件で組織外アカウントからの CloudTrail のみに限定している。
2. コントロール有効化前の確認
組織内アクセス(成功)
aws s3api get-object \
--bucket ct-s3-pv4-test --key test.txt /dev/null{
"AcceptRanges": "bytes",
"LastModified": "<オブジェクト更新日時>",
"ContentLength": 10,
"ETag": "\"39a870a194a787550b6b5d1f49629236\"",
"ChecksumCRC64NVME": "lAOnrRKrjh8=",
"ChecksumType": "FULL_OBJECT",
"ContentType": "binary/octet-stream",
"ServerSideEncryption": "AES256",
"Metadata": {}
}組織外アクセス(成功)
組織外アカウントの認証情報で、バケットポリシーにより許可されたアクセスが成功することを確認する。
aws s3api get-object \
--bucket ct-s3-pv4-test --key test.txt /dev/null \
--profile <組織外プロファイル名>{
"AcceptRanges": "bytes",
"LastModified": "<オブジェクト更新日時>",
"ContentLength": 10,
"ETag": "\"39a870a194a787550b6b5d1f49629236\"",
"ChecksumCRC64NVME": "lAOnrRKrjh8=",
"ChecksumType": "FULL_OBJECT",
"ContentType": "binary/octet-stream",
"ServerSideEncryption": "AES256",
"Metadata": {}
}バケットポリシーにより、組織外アカウントからもオブジェクトを取得できた。
組織外 CloudTrail 証跡の作成(成功)
組織外アカウントの CloudTrail 証跡を作成し、サービスプリンシパル(cloudtrail.amazonaws.com)が組織内バケットに書き込めることを確認する。
aws cloudtrail create-trail \
--name ct-s3-pv4-pre-trail \
--s3-bucket-name ct-s3-pv4-test \
--profile <組織外プロファイル名>{
"Name": "ct-s3-pv4-pre-trail",
"S3BucketName": "ct-s3-pv4-test",
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": false,
"TrailARN": "arn:aws:cloudtrail:ap-northeast-1:<組織外アカウント ID>:trail/ct-s3-pv4-pre-trail",
"LogFileValidationEnabled": false,
"IsOrganizationTrail": false
}バケットにオブジェクトが作成されたことを確認する。
aws s3 ls s3://ct-s3-pv4-test/ --recursive<オブジェクト更新日時> 0 AWSLogs/<組織外アカウント ID>/CloudTrail/
<オブジェクト更新日時> 10 test.txtcreate-trail の成功時に、CloudTrail サービスプリンシパルがバケットにディレクトリプレフィックス(AWSLogs/<組織外アカウント ID>/CloudTrail/)を作成している。サイズは 0 だが、サービスプリンシパルによる S3 への書き込み(PutObject)が成功したことを示している。
確認後、証跡を削除する。
aws cloudtrail delete-trail \
--name ct-s3-pv4-pre-trail \
--profile <組織外プロファイル名>
(出力なし)3. CT.S3.PV.4 の有効化
Control Tower の管理アカウントで、対象の OU にコントロールが有効になっていないことを確認する。
aws controltower list-enabled-controls \
--target-identifier <OU の ARN> \
--profile Master{
"enabledControls": []
}コントロールを有効化する。
aws controltower enable-control \
--control-identifier arn:aws:controlcatalog:::control/7oo9e9bcs8ilm6ekoxu322yb3 \
--target-identifier <OU の ARN> \
--profile Master{
"arn": "arn:aws:controltower:ap-northeast-1:<管理アカウント ID>:enabledcontrol/<enabledcontrol ID>",
"operationIdentifier": "<オペレーション ID>"
}有効化が完了するまで待機する。
aws controltower get-control-operation \
--operation-identifier <オペレーション ID> \
--query 'controlOperation.{operationType:operationType,status:status,statusMessage:statusMessage}' \
--profile Master{
"operationType": "ENABLE_CONTROL",
"status": "SUCCEEDED",
"statusMessage": "Operation was successful."
}4. 既存設定の維持確認
RCP はバケットポリシーを変更するのではなく、アクセス制御のレイヤーとして機能する。バケットポリシーが自動的に変更されていないことを確認する。
aws s3api get-bucket-policy \
--bucket ct-s3-pv4-test \
--query 'Policy' --output text | jq .{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowCrossAccountAccess",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<組織外アカウント ID>:user/<IAM ユーザー名>"
},
"Action": [
"s3:GetObject",
"s3:PutObject"
],
"Resource": "arn:aws:s3:::ct-s3-pv4-test/*"
},
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::ct-s3-pv4-test",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "<組織外アカウント ID>"
}
}
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {
"Service": "cloudtrail.amazonaws.com"
},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::ct-s3-pv4-test/AWSLogs/<組織外アカウント ID>/*",
"Condition": {
"StringEquals": {
"s3:x-amz-acl": "bucket-owner-full-control",
"aws:SourceAccount": "<組織外アカウント ID>"
}
}
}
]
}バケットポリシーは変更されていない。組織外アカウントへの許可が残っているが、RCP により IAM プリンシパルからの実際のアクセスは拒否される。
5. 組織内アクセスの確認
組織内プリンシパルからのアクセスは引き続き成功する。
aws s3api get-object \
--bucket ct-s3-pv4-test --key test.txt /dev/null{
"AcceptRanges": "bytes",
"LastModified": "<オブジェクト更新日時>",
"ContentLength": 10,
"ETag": "\"39a870a194a787550b6b5d1f49629236\"",
"ChecksumCRC64NVME": "lAOnrRKrjh8=",
"ChecksumType": "FULL_OBJECT",
"ContentType": "binary/octet-stream",
"ServerSideEncryption": "AES256",
"Metadata": {}
}6. 組織外アクセスの拒否確認
組織外 GetObject(拒否)
aws s3api get-object \
--bucket ct-s3-pv4-test --key test.txt /dev/null \
--profile <組織外プロファイル名>An error occurred (AccessDenied) when calling the GetObject operation: Access Denied組織外 PutObject(拒否)
echo "external test" | aws s3 cp - s3://ct-s3-pv4-test/external.txt \
--profile <組織外プロファイル名>upload failed: - to s3://ct-s3-pv4-test/external.txt An error occurred (AccessDenied) when calling the PutObject operation: Access Deniedバケットポリシーで許可されているにもかかわらず、組織外プリンシパルからのアクセスが拒否された。
CT.S3.PV.5 や CT.SECRETSMANAGER.PV.1、CT.KMS.PV.7 では組織外プリンシパルへの拒否時にも explicit deny in a resource control policy を含む詳細なエラーメッセージが返されたが、本コントロールでは Access Denied のみが返された。AWS 公式ブログによると、詳細なアクセス拒否メッセージ(ポリシー ARN 等)は同一アカウントまたは同一組織内からのリクエストにのみ返され、組織外からのクロスアカウントリクエストには汎用的なエラーメッセージが返される。CT.STS.PV.1 でも同様に汎用的な AccessDenied のみが返された。一方、Secrets Manager・KMS・SQS では組織外プリンシパルに対しても詳細なエラーメッセージが返されており、サービスごとにロールアウト状況が異なる。
7. サービスプリンシパルの確認
RCP の aws:PrincipalIsAWSService 条件により、AWS サービスプリンシパルは拒否対象外となる。組織外アカウントの CloudTrail 証跡を作成し、サービスプリンシパル(cloudtrail.amazonaws.com)が組織内バケットに書き込めることを確認する。
aws cloudtrail create-trail \
--name ct-s3-pv4-post-trail \
--s3-bucket-name ct-s3-pv4-test \
--profile <組織外プロファイル名>{
"Name": "ct-s3-pv4-post-trail",
"S3BucketName": "ct-s3-pv4-test",
"IncludeGlobalServiceEvents": true,
"IsMultiRegionTrail": false,
"TrailARN": "arn:aws:cloudtrail:ap-northeast-1:<組織外アカウント ID>:trail/ct-s3-pv4-post-trail",
"LogFileValidationEnabled": false,
"IsOrganizationTrail": false
}バケットにオブジェクトが作成されたことを確認する。
aws s3 ls s3://ct-s3-pv4-test/ --recursive<オブジェクト更新日時> 0 AWSLogs/<組織外アカウント ID>/CloudTrail/
<オブジェクト更新日時> 10 test.txtステップ 6 で組織外の IAM ユーザーからのアクセスは拒否されたが、CloudTrail サービスプリンシパルによる書き込みは成功した。create-trail 時にサービスプリンシパルがバケットにディレクトリプレフィックスを作成しており、サイズは 0 だが S3 への書き込み(PutObject)が成功したことを示している。これは RCP の aws:PrincipalIsAWSService 条件によりサービスプリンシパルが拒否対象外であるためである。
確認後、証跡を削除する。
aws cloudtrail delete-trail \
--name ct-s3-pv4-post-trail \
--profile <組織外プロファイル名>
(出力なし)8. CT.S3.PV.4 の無効化
aws controltower disable-control \
--control-identifier arn:aws:controlcatalog:::control/7oo9e9bcs8ilm6ekoxu322yb3 \
--target-identifier <OU の ARN> \
--profile Master{
"operationIdentifier": "<オペレーション ID>"
}aws controltower get-control-operation \
--operation-identifier <オペレーション ID> \
--query 'controlOperation.{operationType:operationType,status:status,statusMessage:statusMessage}' \
--profile Master{
"operationType": "DISABLE_CONTROL",
"status": "SUCCEEDED",
"statusMessage": "Operation was successful."
}9. 制限解除の確認
コントロール無効化後、組織外プリンシパルからのアクセスが再び成功することを確認する。
aws s3api get-object \
--bucket ct-s3-pv4-test --key test.txt /dev/null \
--profile <組織外プロファイル名>{
"AcceptRanges": "bytes",
"LastModified": "<オブジェクト更新日時>",
"ContentLength": 10,
"ETag": "\"39a870a194a787550b6b5d1f49629236\"",
"ChecksumCRC64NVME": "lAOnrRKrjh8=",
"ChecksumType": "FULL_OBJECT",
"ContentType": "binary/octet-stream",
"ServerSideEncryption": "AES256",
"Metadata": {}
}組織外プリンシパルからのアクセスが再び成功した。
10. クリーンアップ
aws s3 rm s3://ct-s3-pv4-test/ --recursive
aws s3api delete-bucket-policy --bucket ct-s3-pv4-test
aws s3api delete-bucket --bucket ct-s3-pv4-test補足: CloudTrail での確認について
RCP による拒否であっても、対象の API が管理イベントに分類される場合は CloudTrail に記録される。例えば、同じ RCP 型の CT.SECRETSMANAGER.PV.1 では GetSecretValue の拒否が管理イベントとして記録され、CloudTrail のイベント履歴や CloudWatch Logs から確認できた。
一方、本コントロールの拒否対象である GetObject / PutObject 等は CloudTrail のデータイベントに分類される。データイベントはデフォルトでは記録されず、確認するには Workload アカウントで独自の証跡を作成し、データイベントの記録を明示的に有効化する必要がある。データイベントの記録手順は CT.S3.PV.5 の補足2 を参照。