コンテンツにスキップ

RDS Publicly Accessible

検証日: 2026-05-19 / リージョン: ap-northeast-1

カスタム 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
指定ありdefaulttrue
指定あり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"
        }
      }
    }
  ]
}

Conditionrds:PubliclyAccessible: true の場合のみ Deny するため、PubliclyAccessible=false を指定する操作(または PubliclyAccessible を指定しない作成・変更操作)は引き続き許可される。

CreateDBCluster / ModifyDBCluster を含めない理由

Aurora DB クラスターは PubliclyAccessible パラメータをクラスターレベルでサポートしない。Aurora エンジン指定時は CreateDBCluster --publicly-accessibleInvalidParameterCombination で 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.50modify-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: available

Config リソース 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-1
db-XXXXXXXXXXXXXXXXXXXXXXXXXX

Config ルール名のサフィックスを確認

aws configservice describe-config-rules \
  --query 'ConfigRules[?contains(ConfigRuleName,`rds-instance-public-access-check`)].ConfigRuleName' \
  --output text \
  --region ap-northeast-1
securityhub-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: True

SCP 適用前はパブリックアクセス有効化が成功する。

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: False

4. カスタム 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"
        }
      }
    }
  ]
}
EOF

SCP 作成

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>&1
An 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>&1
An 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>&1
An 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-accessiblecreate-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>&1
An 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>&1
An 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 対象外であることを確認する。設定値は falsefalse で変化しないが、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
done

SCP デタッチ後は再びパブリックアクセスを有効化できる。

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 分待ってから評価結果を確認する。ResultRecordedTimeModifyDBInstance の完了時刻より後であることを確認し、再評価されたことを示す。

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
done

SCP 削除

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:RestoreDBInstanceFromS3S3 からの移行
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-accessiblerds:CreateDBInstance の Deny として返ることを実機確認済み)

組織のポリシーに応じて、2 API 版で運用負荷を抑えるか、拡張版で防御を厚くするかを選択する。Security 部門は 100% 防御を求め、SRE/DBA 部門は緊急時の柔軟性を求める傾向があるため、関係者で合意形成した上で導入することを推奨する。

Amazonアソシエイトリンク