CT.S3.PV.5
コントロールの説明
S3 バケットへの HTTP(非暗号化)アクセスを禁止する。
本コントロールは RCP(リソースコントロールポリシー)により、S3 バケットへの HTTP アクセスをブロックする。Control Tower で対象の OU を指定して有効化すると、その OU 配下の全アカウントの全 S3 バケットに適用される。各バケットにバケットポリシーを設定する必要はない。ただし、本コントロールはバケットポリシーを自動追加するわけではないため、Security Hub [S3.5] は引き続き FAILED として検出される。
RCP テンプレートの Condition は aws:SecureTransport が false の場合に S3 API 操作を拒否する。つまり、HTTP(非暗号化)でのリクエストがブロックされ、HTTPS でのリクエストは許可される。
パラメータは以下の 1 つ。
ExemptedPrincipalArns(任意): RCP の拒否対象外にする IAM プリンシパルの ARN。レガシーシステムの IAM ロールがすぐに HTTPS へ移行できない場合に、移行期間中の互換性を維持するために使用する
S3 の HTTPS 強制には以下の方法がある。
| 方法 | 内容 | スコープ |
|---|---|---|
| Security Hub S3.5 | バケットポリシーに HTTPS 強制条件があるかチェック | 検出のみ(ブロックしない) |
| バケットポリシー(手動) | バケットごとに HTTP を拒否するポリシーを設定(補足1参照) | バケット単位 |
| CT.S3.PV.5(本コントロール) | OU 配下の全バケットで HTTP を自動的に拒否 | OU 単位 |
Security Hub [S3.5] はバケットポリシーの設定有無をチェックするだけで、HTTP アクセス自体はブロックしない。バケットポリシーによる手動設定はバケットごとに必要で、設定漏れのリスクがある。
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
検証の流れ
flowchart LR
A[1. 事前準備] --> B[2. HTTP/HTTPS<br>両方成功]
B --> C[3. Security Hub S3.5<br>で検出を確認]
C --> D[4. CT.S3.PV.5<br>有効化]
D --> E[5. 既存設定<br>維持を確認]
D --> F[6. Security Hub S3.5<br>FAILED のまま]
D --> G[7. HTTP 拒否<br>HTTPS 成功を確認]
E & F & G --> H[8. CT.S3.PV.5<br>無効化]
H --> I[9. HTTP アクセス<br>成功を確認]
結果
- コントロールの有効化前、HTTP・HTTPS の両方で S3 オブジェクトにアクセスできることを確認できた。
- Security Hub [S3.5] で「FAILED」として検出されることを確認できた。
- コントロールの有効化後、既存のバケット設定(バケットポリシーなし)は維持されることを確認できた。
- コントロール有効化後も Security Hub [S3.5] は FAILED のまま変化しなかった。RCP はバケットポリシーを追加するわけではないため、S3.5 の評価結果は変わらない。CT.EC2.PV.7(Declarative Policy)で Security Hub が PASSED に変化する動作とは対照的である。
- HTTP でのアクセス(GetObject、PutObject)が
explicit deny in a resource control policyで拒否されることを確認できた。 - HTTPS でのアクセスは正常に動作することを確認できた。
- コントロールの無効化後、再び HTTP アクセスが可能になることを確認できた。
- RCP の拒否対象である GetObject / PutObject は CloudTrail のデータイベントに分類されるため、デフォルトでは CloudTrail に記録されない。管理イベントとして記録される他コントロール(CT.SECRETSMANAGER.PV.1、CT.KMS.PV.7 等)とは異なる(補足2参照)。
1. 事前準備
テスト用の S3 バケットを作成する。
aws s3api create-bucket \
--bucket ct-s3-pv5-test \
--create-bucket-configuration LocationConstraint=ap-northeast-1{
"Location": "http://ct-s3-pv5-test.s3.amazonaws.com/",
"BucketArn": "arn:aws:s3:::ct-s3-pv5-test"
}テスト用オブジェクトをアップロードする。
echo "test data" | aws s3 cp - s3://ct-s3-pv5-test/test.txt
(出力なし)2. コントロール有効化前の確認
コントロール有効化前は、HTTP・HTTPS の両方でアクセスできることを確認する。
HTTPS でのアクセス(成功)
aws s3api get-object \
--bucket ct-s3-pv5-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": {}
}HTTP でのアクセス(成功)
aws s3api get-object \
--bucket ct-s3-pv5-test --key test.txt /dev/null \
--endpoint-url http://s3.ap-northeast-1.amazonaws.com{
"AcceptRanges": "bytes",
"LastModified": "<オブジェクト更新日時>",
"ContentLength": 10,
"ETag": "\"39a870a194a787550b6b5d1f49629236\"",
"ChecksumCRC64NVME": "lAOnrRKrjh8=",
"ChecksumType": "FULL_OBJECT",
"ContentType": "binary/octet-stream",
"ServerSideEncryption": "AES256",
"Metadata": {}
}HTTP でも正常にオブジェクトを取得できた。
3. Security Hub S3.5 での検出確認
Security Hub の [S3.5] コントロールで、テスト用の S3 バケットのバケットポリシーに SSL 強制条件がないことが検出されているか確認する。
aws securityhub get-findings \
--filters '{
"GeneratorId": [{"Value": "security-control/S3.5", "Comparison": "PREFIX"}],
"ResourceId": [{"Value": "arn:aws:s3:::ct-s3-pv5-test", "Comparison": "EQUALS"}]
}' \
--query 'Findings[0].{Compliance:Compliance.Status,Title:Title}'{
"Compliance": "FAILED",
"Title": "S3 general purpose buckets should require requests to use SSL"
}バケットポリシーに aws:SecureTransport 条件がないため、FAILED として検出されている。
4. CT.S3.PV.5 の有効化
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/7mo7a2h2ebsq71l8k6uzr96ou \
--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."
}5. 既存設定の維持確認
RCP はバケットポリシーを変更するのではなく、アクセス制御のレイヤーとして機能する。バケットポリシーが自動的に追加されていないことを確認する。
aws s3api get-bucket-policy \
--bucket ct-s3-pv5-testAn error occurred (NoSuchBucketPolicy) when calling the GetBucketPolicy operation: The bucket policy does not existバケットポリシーは追加されていない。RCP はバケットの設定を変更せず、独立したアクセス制御レイヤーとして HTTP アクセスをブロックする。
6. Security Hub S3.5 が FAILED のまま変化しないことの確認
RCP はバケットポリシーを追加するわけではないため、コントロール有効化後も Security Hub [S3.5] は FAILED のまま変化しない。
aws securityhub get-findings \
--filters '{
"GeneratorId": [{"Value": "security-control/S3.5", "Comparison": "PREFIX"}],
"ResourceId": [{"Value": "arn:aws:s3:::ct-s3-pv5-test", "Comparison": "EQUALS"}]
}' \
--query 'Findings[0].{Compliance:Compliance.Status,Title:Title}'{
"Compliance": "FAILED",
"Title": "S3 general purpose buckets should require requests to use SSL"
}FAILED のまま変化していない。CT.EC2.PV.7(Declarative Policy)では Block Public Access の強制により Security Hub が PASSED に変化したが、RCP はバケットの設定自体を変更しないため、S3.5 の評価結果は変わらない。
7. 拒否の確認
HTTP GetObject(拒否)
aws s3api get-object \
--bucket ct-s3-pv5-test --key test.txt /dev/null \
--endpoint-url http://s3.ap-northeast-1.amazonaws.comAn error occurred (AccessDenied) when calling the GetObject operation:
User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名>
is not authorized to perform: s3:GetObject on resource:
"arn:aws:s3:::ct-s3-pv5-test/test.txt" with an explicit deny in a resource control policyHTTP PutObject(拒否)
echo "test" | aws s3 cp - s3://ct-s3-pv5-test/test2.txt \
--endpoint-url http://s3.ap-northeast-1.amazonaws.comupload failed: - to s3://ct-s3-pv5-test/test2.txt An error occurred (AccessDenied) when calling the PutObject operation:
User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名>
is not authorized to perform: s3:PutObject on resource:
"arn:aws:s3:::ct-s3-pv5-test/test2.txt" with an explicit deny in a resource control policyHTTPS GetObject(成功)
aws s3api get-object \
--bucket ct-s3-pv5-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": {}
}HTTPS PutObject(成功)
echo "test https" | aws s3 cp - s3://ct-s3-pv5-test/test-https.txt
(出力なし)HTTP アクセスのみが拒否され、HTTPS アクセスは正常に動作している。エラーメッセージに explicit deny in a resource control policy と記載されており、RCP による拒否であることがわかる。
8. CT.S3.PV.5 の無効化
aws controltower disable-control \
--control-identifier arn:aws:controlcatalog:::control/7mo7a2h2ebsq71l8k6uzr96ou \
--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. 制限解除の確認
コントロール無効化後、HTTP アクセスが再び成功することを確認する。
aws s3api get-object \
--bucket ct-s3-pv5-test --key test.txt /dev/null \
--endpoint-url http://s3.ap-northeast-1.amazonaws.com{
"AcceptRanges": "bytes",
"LastModified": "<オブジェクト更新日時>",
"ContentLength": 10,
"ETag": "\"39a870a194a787550b6b5d1f49629236\"",
"ChecksumCRC64NVME": "lAOnrRKrjh8=",
"ChecksumType": "FULL_OBJECT",
"ContentType": "binary/octet-stream",
"ServerSideEncryption": "AES256",
"Metadata": {}
}HTTP アクセスが再び成功した。コントロールを無効化すると即座に制限が解除される。
10. クリーンアップ
aws s3 rm s3://ct-s3-pv5-test/ --recursive
aws s3api delete-bucket --bucket ct-s3-pv5-test補足1: バケットポリシーによる SSL 強制との比較
CT.S3.PV.5 を使わずにバケットポリシーで HTTP を拒否する場合は、以下のようなポリシーをバケットごとに設定する。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": [
"arn:aws:s3:::<バケット名>",
"arn:aws:s3:::<バケット名>/*"
],
"Condition": {
"Bool": {
"aws:SecureTransport": "false"
}
}
}
]
}バケットポリシーで拒否された場合と CT.S3.PV.5(RCP)で拒否された場合では、エラーメッセージが異なる。
| 拒否元 | エラーメッセージ |
|---|---|
| RCP(CT.S3.PV.5) | explicit deny in a resource control policy |
| バケットポリシー | explicit deny in a resource-based policy |
この違いにより、HTTP アクセスが拒否された際にどのレイヤーで拒否されたかを特定できる。
補足2: CloudTrail での確認について
SCP や RCP による拒否であっても、対象の API が管理イベントに分類される場合は CloudTrail に記録される。例えば、同じ RCP 型の CT.SECRETSMANAGER.PV.1 では GetSecretValue の拒否が管理イベントとして記録され、CloudTrail のイベント履歴や CloudWatch Logs から確認できた。
一方、CT.S3.PV.5(RCP)の拒否対象である GetObject / PutObject 等は CloudTrail のデータイベントに分類される。データイベントはデフォルトでは記録されず、確認するには証跡でデータイベントの記録を明示的に有効化する必要がある。
以下の手順で、RCP による拒否がデータイベントとして記録されることを確認できる。
データイベント記録用の証跡を作成
ログ配信先バケットを作成し、CloudTrail サービスプリンシパルに書き込みを許可する。
aws s3api create-bucket \
--bucket <ログ配信先バケット名> \
--create-bucket-configuration LocationConstraint=ap-northeast-1aws s3api put-bucket-policy \
--bucket <ログ配信先バケット名> \
--policy '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AWSCloudTrailAclCheck",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:GetBucketAcl",
"Resource": "arn:aws:s3:::<ログ配信先バケット名>"
},
{
"Sid": "AWSCloudTrailWrite",
"Effect": "Allow",
"Principal": {"Service": "cloudtrail.amazonaws.com"},
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<ログ配信先バケット名>/AWSLogs/<Workload のアカウント ID>/*",
"Condition": {"StringEquals": {"s3:x-amz-acl": "bucket-owner-full-control"}}
}
]
}'
(出力なし)証跡を作成し、対象バケットの S3 データイベントのみを記録するよう設定する。
aws cloudtrail create-trail \
--name <証跡名> \
--s3-bucket-name <ログ配信先バケット名>aws cloudtrail put-event-selectors \
--trail-name <証跡名> \
--event-selectors '[{
"ReadWriteType": "All",
"IncludeManagementEvents": false,
"DataResources": [{"Type": "AWS::S3::Object", "Values": ["arn:aws:s3:::<テスト用バケット名>/"]}]
}]'aws cloudtrail start-logging \
--name <証跡名>
(出力なし)拒否イベントの確認
CT.S3.PV.5 有効化中に HTTP で GetObject を実行して拒否された後、5〜15 分程度待つとログ配信先バケットにログファイルが配信される。
aws s3 ls s3://<ログ配信先バケット名>/ --recursiveログファイルを取得して中身を確認する。
aws s3 cp s3://<ログ配信先バケット名>/<ログファイルパス> /tmp/trail-log.json.gz
gunzip /tmp/trail-log.json.gz
cat /tmp/trail-log.json | jq '.Records[] | {eventName, errorCode, errorMessage}'{
"eventName": "GetObject",
"errorCode": "AccessDenied",
"errorMessage": "User: <プリンシパル ARN> is not authorized to perform: s3:GetObject on resource: \"arn:aws:s3:::<テスト用バケット名>/<オブジェクトキー>\" with an explicit deny in a resource control policy"
}errorMessage に explicit deny in a resource control policy が記録されており、RCP による拒否であることがわかる。
注意事項
cloudtrail lookup-eventsは管理イベントと Insights イベントのみを検索でき、データイベントは検索対象外である。データイベントの拒否を確認するには、ログ配信先の S3 バケットからログファイルを直接取得する必要がある。- Control Tower が自動作成する Organization Trail(
aws-controltower-BaselineCloudTrail)は管理イベントのみを記録する設定になっている。そのため、Organization Trail の S3 バケット(LogArchive アカウント)や CloudWatch Logs にもデータイベントは配信されない。データイベントを記録するには、上記のように別途証跡を作成する。Control Tower のランディングゾーン設定では Organization Trail をオプトアウトして自前の CloudTrail を管理することも可能であり、その場合は自前の Organization Trail でデータイベントを含めた設定を自由に行える。
eventCategory: "Data", managementEvent: false として記録されることを確認済み。