RDS Publicly Accessible
カスタム SCP の説明
背景: RDS DB インスタンスのパブリックアクセス
RDS DB インスタンスの PubliclyAccessible 設定が true の場合、RDS はパブリック DNS 名を解決可能にし、パブリックサブネット(IGW へのルートを持つサブネット)に配置されている場合はインターネットからアクセス可能になる。Security Hub CSPM の RDS.2 はこの設定をチェックし、PubliclyAccessible=true の場合に FAILED となる。
詳細は RDS.2 を参照。
Security Hub CSPM RDS.2 との関係
RDS DB インスタンスはデフォルトで保護されていない。PubliclyAccessible を明示せずに CreateDBInstance を呼び出した場合のデフォルト値は、DB サブネットグループ(DBSG)の指定と名前で決まる:
| DBSG 指定 | DBSG 名 | PubliclyAccessible のデフォルト |
|---|---|---|
| 指定なし | -(リージョンのデフォルト VPC に IGW あり) | true |
| 指定なし | -(リージョンのデフォルト VPC に IGW なし) | false |
| 指定あり | default | true |
| 指定あり | default 以外 | false |
default という名前の DBSG はデフォルト VPC のパブリックサブネットを含むため、結果として PubliclyAccessible=true になる。意図せずパブリック DB インスタンスを起動してしまうリスクがある。
このカスタム SCP の役割
Control Tower の標準予防コントロールには RDS のパブリックアクセスを直接制御するものが存在しない。代わりに VPC BPA(宣言型ポリシー) によるネットワーク層での実害ブロックが主軸となる。
ただし VPC BPA は finding を残したまま実害だけをブロックするため、設定値そのものを PubliclyAccessible=false に強制したい場合や、VPC BPA の Exclusion 対象 VPC でもパブリック化を防ぎたい場合の多層防御として、本カスタム SCP が選択肢となる。
rds:CreateDBInstance / rds:ModifyDBInstance API には rds:PubliclyAccessible 条件キーが提供されており、PubliclyAccessible=true を指定する操作のみを Deny できる。これにより、PubliclyAccessible=false の操作(パブリックアクセスを無効化する操作や、最初からプライベートで作成する操作)は引き続き許可される。
適用順序の注意
RDS DB インスタンスは作成時にパブリック化可能なため、SSM.7 のような「事前設定 → SCP 適用」の順序制約はない。SCP を先に適用しても問題ない。
ただし、本 SCP は予防コントロールであり、SCP 適用前から存在するパブリック RDS DB インスタンスは PubliclyAccessible=false に強制されない。SCP 適用前に Security Hub の RDS.2 finding で棚卸しし、既存の違反インスタンスを修正する運用フローを推奨する。
SCP の内容
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRDSPublicAccess",
"Effect": "Deny",
"Action": [
"rds:CreateDBInstance",
"rds:ModifyDBInstance"
],
"Resource": "*",
"Condition": {
"Bool": {
"rds:PubliclyAccessible": "true"
}
}
}
]
}Condition で rds:PubliclyAccessible: true の場合のみ Deny するため、PubliclyAccessible=false を指定する操作(または PubliclyAccessible を指定しない作成・変更操作)は引き続き許可される。
CreateDBCluster / ModifyDBCluster を含めない理由
Aurora DB クラスターは PubliclyAccessible パラメータをクラスターレベルでサポートしない。Aurora エンジン指定時は CreateDBCluster --publicly-accessible が InvalidParameterCombination で SCP 評価以前にバリデーションエラーになる(aurora-mysql / aurora-postgresql の両方で実機確認)。Aurora のパブリックアクセスはクラスターに属するインスタンスの CreateDBInstance / ModifyDBInstance で設定するため、この 2 API を Deny するだけでカバーできる。
Multi-AZ DB cluster(非 Aurora MySQL/PostgreSQL)の CreateDBCluster には --publicly-accessible パラメータが存在するが、Multi-AZ DB cluster の作成は内部で writer + 2 reader インスタンスを CreateDBInstance で作成する。PubliclyAccessible=true を指定するとインスタンス作成も PubliclyAccessible=true で要求されるため、本 SCP の rds:CreateDBInstance Deny で阻止される(MySQL / PostgreSQL の両方の Multi-AZ DB cluster で実機確認済み)。
ModifyDBCluster には PubliclyAccessible パラメータが存在しない(Multi-AZ DB cluster でも作成時のみ設定可能)。よって rds:ModifyDBCluster を SCP に含める必要はない。
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。Master アカウントでの操作には --profile Master を指定する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
検証時の AWS CLI バージョンは 2.27.50。modify-db-cluster --publicly-accessible フラグは公式の最新ドキュメントには記載があるが、本検証時のバージョンでは Unknown options エラーになり利用できなかった。本記事では Multi-AZ DB cluster の PubliclyAccessible 設定を create-db-cluster のみで検証している。
検証の流れ
flowchart LR
A[1. 事前準備<br>DB インスタンス作成<br>RDS.2 PASSED 確認] --> B[2. SCP 適用前<br>PubliclyAccessible=true に変更が成功]
B --> C[3. PubliclyAccessible=false に戻す]
C --> D[4. SCP 作成]
D --> E[5. OU にアタッチ]
E --> F[6. PubliclyAccessible=true への変更が<br>Deny されることを確認]
F --> G[7. PubliclyAccessible=false および<br>未指定の変更が許可されることを確認]
G --> H[8. SCP デタッチ]
H --> I[9. PubliclyAccessible=true への変更が<br>再び成功することを確認<br>RDS.2 FAILED 変化確認]
I --> J[10. クリーンアップ<br>DB インスタンス削除・SCP 削除]
結果
- SCP 適用前:
modify-db-instance --publicly-accessibleが成功することを確認した - SCP 適用後:
PubliclyAccessible=trueを指定したmodify-db-instance/create-db-instanceの 2 API がAccessDeniedで拒否されることを確認した。エラーメッセージにexplicit deny in a service control policyとポリシー ARN が含まれることから、SCP による拒否であることを確認した - Aurora インスタンスも Deny 対象: Aurora クラスターに属するインスタンスの
create-db-instance/modify-db-instanceも同様に Deny されることを確認した。CreateDBCluster/ModifyDBClusterは Aurora エンジンではPubliclyAccessibleパラメータをサポートしないため SCP 評価以前のバリデーションエラーになる - Multi-AZ DB cluster も Deny 対象:
create-db-cluster --publicly-accessibleを実行すると、内部のCreateDBInstance呼び出しで Deny されることを確認した。エラーメッセージはrds:CreateDBInstanceの Deny として返る(MySQL Multi-AZ DB cluster で実機確認) - 暗黙のデフォルトも Deny 対象:
--publicly-accessibleを指定しなくても、defaultという名前の DB サブネットグループを使う場合は実機検証でBool条件キーが Deny にマッチすることが確認できた。AWS 公式ドキュメントには明記されていないが、実測ではPubliclyAccessibleの暗黙のデフォルト値も SCP 評価対象になっていると考えられる(IGW なしのサブネットを持つ別名 DBSG では Deny されないことも確認) - SCP 適用中の false 指定・未指定の変更:
--no-publicly-accessibleを指定するmodify-db-instanceおよびPubliclyAccessibleを指定しないmodify-db-instance(例:--backup-retention-period)は SCP に阻まれず実行できることを確認した - SCP デタッチ後: 約 2 分のラグ後に
PubliclyAccessible=trueへの変更が成功することを確認した - Config CI 自動更新: MSK.4 と同様、
ModifyDBInstance完了後に Config の Configuration Item が自動更新されないケースを観測した。タグ追加(add-tags-to-resource)で CI 再キャプチャを誘発する必要があった - RDS.2 finding:
PubliclyAccessible=trueへの変更後に FAILED に変化することを確認した(評価ロジックが状態変化を正しく反映することの裏付け) - エラーコード:
AccessDeniedExceptionではなくAccessDenied(SSM.7/EMR.2 と同じ)。エラーメッセージ末尾にポリシー ARN が含まれる(MSK.4 とは異なる)
1. 事前準備(DB インスタンス作成)
デフォルト VPC のサブネットグループを確認
RDS DB インスタンス作成時に DB サブネットグループを指定する必要がある。デフォルト VPC には自動的に default という名前の DB サブネットグループが作成されている。
aws rds describe-db-subnet-groups \
--db-subnet-group-name default \
--query 'DBSubnetGroups[0].{Name:DBSubnetGroupName,VpcId:VpcId,SubnetIds:Subnets[].SubnetIdentifier}' \
--region ap-northeast-1{
"Name": "default",
"VpcId": "<VPC ID>",
"SubnetIds": [
"<サブネット ID 1>",
"<サブネット ID 2>",
"<サブネット ID 3>"
]
}DB インスタンス作成(PubliclyAccessible=false)
検証用 DB インスタンスを PubliclyAccessible=false で作成する。インスタンス作成には数分かかる。
aws rds create-db-instance \
--db-instance-identifier rds2-scp-test \
--db-instance-class db.t3.micro \
--engine mysql \
--master-username admin \
--master-user-password '<パスワード>' \
--allocated-storage 20 \
--no-publicly-accessible \
--db-subnet-group-name default \
--no-multi-az \
--query 'DBInstance.{DBInstanceIdentifier:DBInstanceIdentifier,DBInstanceStatus:DBInstanceStatus,PubliclyAccessible:PubliclyAccessible}' \
--region ap-northeast-1{
"DBInstanceIdentifier": "rds2-scp-test",
"DBInstanceStatus": "creating",
"PubliclyAccessible": false
}available 状態待機
while true; do
STATUS=$(aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].DBInstanceStatus' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') Status: $STATUS"
[ "$STATUS" = "available" ] && break
sleep 30
done<時刻> Status: creating
...
<時刻> Status: availableConfig リソース ID(DbiResourceId)を取得
RDS DB インスタンスの Config リソース ID は DB インスタンス識別子ではなく内部 ID(DbiResourceId)が使われる。db-XXXXXXXX 形式の ID で、後続のステップで Config 評価結果を絞り込むのに使う。
aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].DbiResourceId' --output text \
--region ap-northeast-1db-XXXXXXXXXXXXXXXXXXXXXXXXXXConfig ルール名のサフィックスを確認
aws configservice describe-config-rules \
--query 'ConfigRules[?contains(ConfigRuleName,`rds-instance-public-access-check`)].ConfigRuleName' \
--output text \
--region ap-northeast-1securityhub-rds-instance-public-access-check-<サフィックス>RDS.2 finding 確認(PASSED であること)
Config ルールを手動トリガーして即座に評価させる。
aws configservice start-config-rules-evaluation \
--config-rule-names securityhub-rds-instance-public-access-check-<サフィックス> \
--region ap-northeast-1(出力なし)1〜2 分待ってから評価結果を確認する。ResultRecordedTime がインスタンス作成完了時刻より後であることを確認し、再評価されたことを示す。
新規 DB インスタンス作成直後は Config がリソースを発見・記録するまでにラグがある場合がある。get-compliance-details-by-config-rule が空配列を返す場合は数分待ってから start-config-rules-evaluation を再実行する。
sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name securityhub-rds-instance-public-access-check-<サフィックス> \
--query "EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId=='<Config リソース ID>'].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}" \
--region ap-northeast-1[
{
"ResourceId": "<Config リソース ID>",
"ComplianceType": "COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]aws securityhub get-findings \
--filters '{
"ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "RDS.2"}],
"AwsAccountId": [{"Comparison": "EQUALS", "Value": "<アカウント ID>"}],
"RecordState": [{"Comparison": "EQUALS", "Value": "ACTIVE"}]
}' \
--query 'Findings[].{Status:Compliance.Status,ResourceId:Resources[0].Id,UpdatedAt:UpdatedAt}' \
--region ap-northeast-1[
{
"Status": "PASSED",
"ResourceId": "arn:aws:rds:ap-northeast-1:<アカウント ID>:db:rds2-scp-test",
"UpdatedAt": "<更新時刻>"
}
]RDS.2 が PASSED であることを確認する。これで検証の前提条件が成立する。
2. SCP 適用前のベースライン確認
メンバーアカウントの管理者として、PubliclyAccessible=true への変更ができることを確認する。
aws rds modify-db-instance \
--db-instance-identifier rds2-scp-test \
--publicly-accessible \
--apply-immediately \
--query 'DBInstance.{DBInstanceIdentifier:DBInstanceIdentifier,PubliclyAccessible:PubliclyAccessible}' \
--region ap-northeast-1{
"DBInstanceIdentifier": "rds2-scp-test",
"PubliclyAccessible": false
}modify-db-instance の即時レスポンスでは変更前の値が返る。反映には数分かかる。
反映確認
while true; do
PA=$(aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].PubliclyAccessible' --output text \
--region ap-northeast-1)
STATUS=$(aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].DBInstanceStatus' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') Status: $STATUS, PubliclyAccessible: $PA"
[ "$(echo $PA | tr A-Z a-z)" = "true" ] && [ "$STATUS" = "available" ] && break
sleep 30
done<時刻> Status: modifying, PubliclyAccessible: False
...
<時刻> Status: available, PubliclyAccessible: TrueSCP 適用前はパブリックアクセス有効化が成功する。
3. PubliclyAccessible=false に戻す
SCP 適用前に元の状態(パブリックアクセス無効)に戻す。SCP は PubliclyAccessible=true への変更を Deny するため、SCP 適用後は元に戻すことができない。先に戻しておく。
aws rds modify-db-instance \
--db-instance-identifier rds2-scp-test \
--no-publicly-accessible \
--apply-immediately \
--query 'DBInstance.{DBInstanceIdentifier:DBInstanceIdentifier,PubliclyAccessible:PubliclyAccessible}' \
--region ap-northeast-1{
"DBInstanceIdentifier": "rds2-scp-test",
"PubliclyAccessible": true
}modify-db-instance の即時レスポンスでは変更前の値(true)が返る点に注意。反映には数分かかる。
反映確認
while true; do
PA=$(aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].PubliclyAccessible' --output text \
--region ap-northeast-1)
STATUS=$(aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].DBInstanceStatus' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') Status: $STATUS, PubliclyAccessible: $PA"
[ "$(echo $PA | tr A-Z a-z)" = "false" ] && [ "$STATUS" = "available" ] && break
sleep 30
done<時刻> Status: modifying, PubliclyAccessible: True
...
<時刻> Status: available, PubliclyAccessible: False4. カスタム SCP 作成
ポリシー JSON ファイル作成
cat > /tmp/scp-rds-publicly-accessible.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRDSPublicAccess",
"Effect": "Deny",
"Action": [
"rds:CreateDBInstance",
"rds:ModifyDBInstance"
],
"Resource": "*",
"Condition": {
"Bool": {
"rds:PubliclyAccessible": "true"
}
}
}
]
}
EOFSCP 作成
aws organizations create-policy \
--name DenyRDSPublicAccess \
--description "Deny creating or modifying RDS instances with PubliclyAccessible=true" \
--type SERVICE_CONTROL_POLICY \
--content file:///tmp/scp-rds-publicly-accessible.json \
--query 'Policy.PolicySummary.{Id:Id,Name:Name,Type:Type}' \
--profile Master{
"Id": "<ポリシー ID>",
"Name": "DenyRDSPublicAccess",
"Type": "SERVICE_CONTROL_POLICY"
}5. OU にアタッチ
aws organizations attach-policy \
--policy-id <ポリシー ID> \
--target-id <OU ID> \
--profile Master(出力なし)アタッチを確認
aws organizations list-policies-for-target \
--target-id <OU ID> \
--filter SERVICE_CONTROL_POLICY \
--query 'Policies[?Name==`DenyRDSPublicAccess`]' \
--profile Master[
{
"Id": "<ポリシー ID>",
"Arn": "arn:aws:organizations::<Master アカウント ID>:policy/o-<組織 ID>/service_control_policy/<ポリシー ID>",
"Name": "DenyRDSPublicAccess",
"Description": "Deny creating or modifying RDS instances with PubliclyAccessible=true",
"Type": "SERVICE_CONTROL_POLICY",
"AwsManaged": false
}
]SCP の適用には数秒〜数分のラグがある。ステップ 6 の操作はアタッチ確認後、さらに数分待ってから実施すること。
6. PubliclyAccessible=true への変更が Deny されることを確認
aws rds modify-db-instance \
--db-instance-identifier rds2-scp-test \
--publicly-accessible \
--apply-immediately \
--region ap-northeast-1 2>&1An error occurred (AccessDenied) when calling the ModifyDBInstance operation: User: arn:aws:sts::<Workload アカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: rds:ModifyDBInstance on resource: arn:aws:rds:ap-northeast-1:<Workload アカウント ID>:db:rds2-scp-test with an explicit deny in a service control policy: arn:aws:organizations::<Master アカウント ID>:policy/o-<組織 ID>/service_control_policy/<ポリシー ID>explicit deny in a service control policy というメッセージが含まれることから、SCP による拒否であることを確認できる。
設定値が変わっていないことを確認
aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].{DBInstanceStatus:DBInstanceStatus,PubliclyAccessible:PubliclyAccessible}' \
--region ap-northeast-1{
"DBInstanceStatus": "available",
"PubliclyAccessible": false
}設定値は false のまま維持されている。
新規作成(PubliclyAccessible=true)も Deny されることを確認
ModifyDBInstance だけでなく CreateDBInstance も Deny されることを確認する。
aws rds create-db-instance \
--db-instance-identifier rds2-scp-test-public \
--db-instance-class db.t3.micro \
--engine mysql \
--master-username admin \
--master-user-password '<パスワード>' \
--allocated-storage 20 \
--publicly-accessible \
--db-subnet-group-name default \
--no-multi-az \
--region ap-northeast-1 2>&1An error occurred (AccessDenied) when calling the CreateDBInstance operation: User: arn:aws:sts::<Workload アカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: rds:CreateDBInstance on resource: arn:aws:rds:ap-northeast-1:<Workload アカウント ID>:db:rds2-scp-test-public with an explicit deny in a service control policy: arn:aws:organizations::<Master アカウント ID>:policy/o-<組織 ID>/service_control_policy/<ポリシー ID>PubliclyAccessible=true を指定した新規作成も Deny される。
暗黙のデフォルト(default 名 DBSG)も Deny されることを確認
--publicly-accessible を明示せずに、default という名前の DB サブネットグループを使って create-db-instance を実行する。本来 PubliclyAccessible の暗黙のデフォルトが true になるケース。
aws rds create-db-instance \
--db-instance-identifier rds2-scp-test-default \
--db-instance-class db.t3.micro \
--engine mysql \
--master-username admin \
--master-user-password '<パスワード>' \
--allocated-storage 20 \
--db-subnet-group-name default \
--no-multi-az \
--region ap-northeast-1 2>&1An error occurred (AccessDenied) when calling the CreateDBInstance operation: User: ... is not authorized to perform: rds:CreateDBInstance on resource: ... with an explicit deny in a service control policy: ...--publicly-accessible の指定がなくても、暗黙のデフォルト値が true に解決されるケースでは SCP の Bool 条件キーで Deny される。AWS 公式ドキュメントには明記されていないが、実機検証で確認できた挙動。
逆に、default 以外の名前の DB サブネットグループ(IGW なしのプライベートサブネットを含むもの)を指定して PubliclyAccessible 未指定で作成した場合は、暗黙のデフォルトが false に解決され、SCP に阻まれず作成が成功することも確認している。
Aurora クラスターレベルには PubliclyAccessible が存在しないことの確認
Aurora DB クラスターは PubliclyAccessible パラメータをクラスターレベルでサポートしない。Aurora エンジンを指定して --publicly-accessible で create-db-cluster を呼ぶと、SCP 評価以前に AWS RDS の API バリデーションでエラーになる。
aws rds create-db-cluster \
--db-cluster-identifier rds2-scp-test-aurora \
--engine aurora-mysql \
--master-username admin \
--master-user-password '<パスワード>' \
--publicly-accessible \
--db-subnet-group-name default \
--region ap-northeast-1 2>&1An error occurred (InvalidParameterCombination) when calling the CreateDBCluster operation: PubliclyAccessible isn't supported for DB engine aurora-mysql.aurora-postgresql でも同様のエラー(PubliclyAccessible isn't supported for DB engine aurora-postgresql.)が返ることを実機で確認済み。
Aurora クラスターに属するインスタンスのパブリックアクセスは CreateDBInstance / ModifyDBInstance で設定するため、SCP の rds:CreateDBInstance / rds:ModifyDBInstance Deny でカバーされる。
Multi-AZ DB cluster の作成も Deny されることを確認
Multi-AZ DB cluster(非 Aurora MySQL/PostgreSQL)は create-db-cluster --publicly-accessible でパブリック化が可能。ただし内部的に writer + 2 reader インスタンスを CreateDBInstance で作成するため、SCP の rds:CreateDBInstance Deny で阻止される。
aws rds create-db-cluster \
--db-cluster-identifier rds2-scp-multiaz-test \
--engine mysql --engine-version 8.0 \
--db-cluster-instance-class db.m6gd.large \
--master-username admin \
--master-user-password '<パスワード>' \
--allocated-storage 100 --storage-type io1 --iops 1000 \
--publicly-accessible \
--db-subnet-group-name default \
--region ap-northeast-1 2>&1An error occurred (AccessDenied) when calling the CreateDBCluster operation: User: ... is not authorized to perform: rds:CreateDBInstance on resource: arn:aws:rds:ap-northeast-1:<Workload アカウント ID>:db:rds2-scp-multiaz-test-instance-1 with an explicit deny in a service control policy: ...CreateDBCluster API を呼んでいるが、エラーは内部で呼ばれる rds:CreateDBInstance の Deny として返る。
検証は MySQL / PostgreSQL の両方の Multi-AZ DB cluster で実機実施し、両方で rds:CreateDBInstance の Deny として返ることを確認した。
ModifyDBCluster には PubliclyAccessible パラメータが存在しない(Multi-AZ DB cluster でも作成時のみ設定可能)。よって rds:ModifyDBCluster を SCP に含める必要はない。
7. PubliclyAccessible=false への変更は引き続き許可されることを確認
--no-publicly-accessible を含む API 呼び出しは SCP の Deny 対象外であることを確認する。設定値は false → false で変化しないが、API 呼び出し自体が成功すること(Deny されないこと)が検証の目的。
aws rds modify-db-instance \
--db-instance-identifier rds2-scp-test \
--no-publicly-accessible \
--apply-immediately \
--query 'DBInstance.{DBInstanceIdentifier:DBInstanceIdentifier,PubliclyAccessible:PubliclyAccessible}' \
--region ap-northeast-1{
"DBInstanceIdentifier": "rds2-scp-test",
"PubliclyAccessible": false
}PubliclyAccessible を指定しない変更も許可されることを確認
PubliclyAccessible と無関係な属性変更(例: バックアップ保持期間)が SCP に阻まれず実行できることを確認する。
aws rds modify-db-instance \
--db-instance-identifier rds2-scp-test \
--backup-retention-period 7 \
--apply-immediately \
--query 'DBInstance.{DBInstanceIdentifier:DBInstanceIdentifier,BackupRetentionPeriod:BackupRetentionPeriod}' \
--region ap-northeast-1{
"DBInstanceIdentifier": "rds2-scp-test",
"BackupRetentionPeriod": 7
}SCP の Condition が PubliclyAccessible: true 指定の操作のみを Deny し、それ以外の modify-db-instance 操作には影響しないことが確認できる。
8. SCP デタッチ
aws organizations detach-policy \
--policy-id <ポリシー ID> \
--target-id <OU ID> \
--profile Master(出力なし)9. PubliclyAccessible=true への変更が再び成功することを確認
SCP のデタッチには数分のラグがある場合があるため、Deny が続く場合は数分待ってから再試行する。
aws rds modify-db-instance \
--db-instance-identifier rds2-scp-test \
--publicly-accessible \
--apply-immediately \
--query 'DBInstance.{DBInstanceIdentifier:DBInstanceIdentifier,PubliclyAccessible:PubliclyAccessible}' \
--region ap-northeast-1{
"DBInstanceIdentifier": "rds2-scp-test",
"PubliclyAccessible": false
}反映確認
while true; do
PA=$(aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].PubliclyAccessible' --output text \
--region ap-northeast-1)
STATUS=$(aws rds describe-db-instances \
--db-instance-identifier rds2-scp-test \
--query 'DBInstances[0].DBInstanceStatus' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') Status: $STATUS, PubliclyAccessible: $PA"
[ "$(echo $PA | tr A-Z a-z)" = "true" ] && [ "$STATUS" = "available" ] && break
sleep 30
doneSCP デタッチ後は再びパブリックアクセスを有効化できる。
Security Hub RDS.2 finding 確認(FAILED に変化)
最終状態は PubliclyAccessible=true なので、RDS.2 は FAILED に変化するはず。これを確認することで、以下が裏付けられる:
- RDS.2 の評価ロジックが
PubliclyAccessible=trueを正しく FAILED と判定できる(ステップ 1 の PASSED が「壊れて常に PASSED」ではなく「正しく評価された結果の PASSED」だったことの裏付け) - Config の Configuration Item 更新と Security Hub finding への反映が正常に動作している
- 検証対象 DB インスタンスの状態変化が finding に正しく反映されている
RDS.2 の Config ルールは Configuration changes 型だが、ModifyDBInstance 完了後の Config 記録更新には十数分かかる場合がある。Config ルールを手動トリガーして評価を促す。
aws configservice start-config-rules-evaluation \
--config-rule-names securityhub-rds-instance-public-access-check-<サフィックス> \
--region ap-northeast-1(出力なし)1〜2 分待ってから評価結果を確認する。ResultRecordedTime が ModifyDBInstance の完了時刻より後であることを確認し、再評価されたことを示す。
sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name securityhub-rds-instance-public-access-check-<サフィックス> \
--query "EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId=='<Config リソース ID>'].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}" \
--region ap-northeast-1[
{
"ResourceId": "<Config リソース ID>",
"ComplianceType": "NON_COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]Configuration changes 型の Config ルールは、リソース変更時に Config が Configuration Item を再キャプチャしたタイミングで再評価される。ModifyDBInstance 完了後に Configuration Item が自動更新されない場合、Config の手動トリガーで評価しても古い CI が参照されて結果が変わらないことがある(MSK.4 検証 で観測)。
その場合は aws configservice get-resource-config-history --resource-type AWS::RDS::DBInstance --resource-id <Config リソース ID> で最新 CI の PubliclyAccessible 値を確認する。古い値(false)のままなら、タグ追加(aws rds add-tags-to-resource)で CI 再キャプチャを誘発してから再度 Config を手動トリガーする。
aws securityhub get-findings \
--filters '{
"ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "RDS.2"}],
"AwsAccountId": [{"Comparison": "EQUALS", "Value": "<アカウント ID>"}],
"RecordState": [{"Comparison": "EQUALS", "Value": "ACTIVE"}]
}' \
--query 'Findings[].{Status:Compliance.Status,ResourceId:Resources[0].Id,UpdatedAt:UpdatedAt}' \
--region ap-northeast-1[
{
"Status": "FAILED",
"ResourceId": "arn:aws:rds:ap-northeast-1:<アカウント ID>:db:rds2-scp-test",
"UpdatedAt": "<更新時刻>"
}
]RDS.2 が FAILED に変化した。これにより、評価ロジックが PubliclyAccessible=true を正しく FAILED 判定し、設定値変化が Config / Security Hub に伝播していることが確認できる。
10. クリーンアップ
DB インスタンス削除
aws rds delete-db-instance \
--db-instance-identifier rds2-scp-test \
--skip-final-snapshot \
--query 'DBInstance.{DBInstanceIdentifier:DBInstanceIdentifier,DBInstanceStatus:DBInstanceStatus}' \
--region ap-northeast-1{
"DBInstanceIdentifier": "rds2-scp-test",
"DBInstanceStatus": "deleting"
}削除完了を確認する。
while true; do
COUNT=$(aws rds describe-db-instances \
--query "length(DBInstances[?DBInstanceIdentifier=='rds2-scp-test'])" \
--output text --region ap-northeast-1 2>/dev/null || echo 0)
echo "$(date '+%H:%M:%S') 残存インスタンス数: $COUNT"
[ "$COUNT" = "0" ] && echo "削除完了" && break
sleep 60
doneSCP 削除
aws organizations delete-policy \
--policy-id <ポリシー ID> \
--profile Master(出力なし)付録: より厳格にガードしたい場合の SCP 拡張
本記事の 2 API SCP(CreateDBInstance / ModifyDBInstance)は、通常の業務オペレーションを阻害せずに PubliclyAccessible=true の操作を Deny する設計になっている。一方で、以下の API 経由でパブリック化される経路は塞げない:
| API | 用途 |
|---|---|
rds:CreateDBInstanceReadReplica | リードレプリカ作成 |
rds:RestoreDBInstanceFromDBSnapshot | スナップショットからの復元 |
rds:RestoreDBInstanceFromS3 | S3 からの移行 |
rds:RestoreDBInstanceToPointInTime | 時点復元 |
これらの API で --publicly-accessible を指定した場合、2 API SCP では Deny されないことを実機で確認済み(--publicly-accessible 指定後、リソース存在チェック等の API レベルのエラーが返るのみで、SCP の Deny は発生しない)。
実害の観点では、「意図的にパブリック化したリストア・レプリカ作成」は通常業務でほぼ発生しないため、2 API SCP でも実用上のカバレッジは十分とされる。ただし、より厳格にガードしたい場合は、以下のように Action を拡張できる:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyRDSPublicAccess",
"Effect": "Deny",
"Action": [
"rds:CreateDBInstance",
"rds:ModifyDBInstance",
"rds:CreateDBInstanceReadReplica",
"rds:RestoreDBInstanceFromDBSnapshot",
"rds:RestoreDBInstanceFromS3",
"rds:RestoreDBInstanceToPointInTime"
],
"Resource": "*",
"Condition": {
"Bool": {
"rds:PubliclyAccessible": "true"
}
}
}
]
}拡張版 SCP を適用した場合、各 Restore 系 API および CreateDBInstanceReadReplica に対して --publicly-accessible を指定すると AccessDenied で拒否されることを実機で確認済み。
拡張版 SCP のトレードオフ
拡張版を採用する場合、以下の運用上の注意がある:
- 緊急復旧時に
--no-publicly-accessibleの明示が必須になる: スナップショットからのリストアや PITR は、--publicly-accessibleを明示しない場合、元インスタンスの設定や DBSG の名前によっては暗黙のデフォルトがtrueに解決される可能性がある。本 SCP の Bool 条件キーは暗黙のデフォルト値も Deny 対象とするため、--no-publicly-accessibleを明示しないと復旧操作が阻まれることがある - Runbook への反映が必須: 緊急復旧手順書に必ず
--no-publicly-accessibleを含めるルール化が前提となる。手順書が古いと夜間のオンコール対応で事故るリスクがある - クラスター系 Restore(
RestoreDBClusterFromSnapshot等): 内部でCreateDBInstanceを呼ぶため、2 API SCP でも拡張版でもrds:CreateDBInstanceの Deny で阻止される(拡張版 SCP でRestoreDBClusterFromSnapshot --publicly-accessibleがrds:CreateDBInstanceの Deny として返ることを実機確認済み)
組織のポリシーに応じて、2 API 版で運用負荷を抑えるか、拡張版で防御を厚くするかを選択する。Security 部門は 100% 防御を求め、SRE/DBA 部門は緊急時の柔軟性を求める傾向があるため、関係者で合意形成した上で導入することを推奨する。