コンテンツにスキップ

RDS Publicly Accessible

検証日: 未実施 / リージョン: 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 インスタンスはデフォルトで保護されていない。デフォルト VPC のサブネットグループを使用する場合、PubliclyAccessible のデフォルトは true になる。意図せずパブリック DB インスタンスを起動してしまうリスクがある。

このカスタム SCP の役割

Control Tower の標準予防コントロールには RDS のパブリックアクセスを直接制御するものが存在しない。代わりに VPC BPA(宣言型ポリシー) によるネットワーク層での実害ブロックが主軸となる。

ただし VPC BPA は finding を残したまま実害だけをブロックするため、設定値そのものを PubliclyAccessible=false に強制したい場合や、VPC BPA の Exclusion 対象 VPC でもパブリック化を防ぎたい場合の多層防御として、本カスタム SCP が選択肢となる。

rds:CreateDBInstance / rds:ModifyDBInstance / rds:CreateDBCluster / rds:ModifyDBCluster API には rds:PubliclyAccessible 条件キーが提供されており、PubliclyAccessible=true を指定する操作のみを Deny できる。これにより、PubliclyAccessible=false の操作(パブリックアクセスを無効化する操作や、最初からプライベートで作成する操作)は引き続き許可される。

適用順序の注意

RDS DB インスタンスは作成時にパブリック化可能なため、SSM.7 のような「事前設定 → SCP 適用」の順序制約はない。SCP を先に適用しても問題ない。

SCP の内容

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyRDSPublicAccess",
      "Effect": "Deny",
      "Action": [
        "rds:CreateDBInstance",
        "rds:ModifyDBInstance",
        "rds:CreateDBCluster",
        "rds:ModifyDBCluster"
      ],
      "Resource": "*",
      "Condition": {
        "Bool": {
          "rds:PubliclyAccessible": "true"
        }
      }
    }
  ]
}

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

DB インスタンスと DB クラスターの両方に対応するため、4 つの API(CreateDBInstance / ModifyDBInstance / CreateDBCluster / ModifyDBCluster)を Deny 対象とする。

検証環境

検証環境の構成図

本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。Master アカウントでの操作には --profile Master を指定する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。

検証の流れ

    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-instancePubliclyAccessible=true に変更できることを確認する
  • SCP 適用後、rds:PubliclyAccessible: truemodify-db-instanceAccessDeniedException で拒否されることを確認する。エラーメッセージに explicit deny in a service control policy が含まれることから、SCP による拒否であることを確認する
  • SCP 適用中でも rds:PubliclyAccessible: false(パブリックアクセスを無効化する変更、またはパブリックアクセスを指定しない作成)の modify-db-instance は許可されることを確認する(条件キー Bool 型 Deny の動作確認)
  • SCP デタッチ後は再び PubliclyAccessible=true に変更できることを確認する
  • RDS.2 finding は PubliclyAccessible=true への変更後に FAILED に変化することを確認する

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 がインスタンス作成完了時刻より後であることを確認し、再評価されたことを示す。

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"
  [ "$PA" = "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
}

反映確認

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"
  [ "$PA" = "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",
        "rds:CreateDBCluster",
        "rds:ModifyDBCluster"
      ],
      "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 (AccessDeniedException) 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

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 (AccessDeniedException) 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

PubliclyAccessible=true を指定した新規作成も Deny される。

Aurora クラスター作成(PubliclyAccessible=true)も Deny されることを確認

SCP は CreateDBInstance / ModifyDBInstance だけでなく CreateDBCluster / ModifyDBCluster も Deny 対象に入っている。Aurora クラスター作成は通常時間がかかるが、SCP による Deny は即時返るためクラスター作成は始まらない。

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 (AccessDeniedException) when calling the CreateDBCluster operation: User: arn:aws:sts::<Workload アカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: rds:CreateDBCluster on resource: arn:aws:rds:ap-northeast-1:<Workload アカウント ID>:cluster:rds2-scp-test-aurora with an explicit deny in a service control policy

これで SCP の対象 4 API すべて(CreateDBInstance / ModifyDBInstance / CreateDBCluster / ModifyDBCluster)が PubliclyAccessible=true を Deny することが確認できる。

7. PubliclyAccessible=false への変更は引き続き許可されることを確認

SCP の Condition は rds:PubliclyAccessible: true の場合のみ Deny するため、PubliclyAccessible=false への変更は許可されるはず。現在すでに PubliclyAccessible=false なので、SCP に阻まれずに modify-db-instance を実行できることを確認する。

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=false の操作は SCP に阻まれず実行できる。

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"
  [ "$PA" = "True" ] && [ "$STATUS" = "available" ] && break
  sleep 30
done

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

Security Hub RDS.2 finding 確認(FAILED に変化)

最終状態は PubliclyAccessible=true なので、RDS.2 は FAILED に変化するはず。これを確認することで、検証中に SCP 以外の何かが RDS.2 の評価に影響していないことの証明になる。

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 に変化した。SCP デタッチ後にパブリックアクセス有効化操作が成功し、想定通りの状態になっていることを確認できる。

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
(出力なし)

Amazonアソシエイトリンク