コンテンツにスキップ

CT.EC2.PV.3

検証日: 2026-03-14 / リージョン: ap-northeast-1

コントロールの説明

EBS スナップショットをパブリックに共有する操作を禁止(拒否)します。

ここでいう「パブリック共有」とは、スナップショット単位の createVolumePermissionGroup: all を設定し、世界中の全 AWS アカウントがそのスナップショットからボリュームを作成できる状態にすることを指します。デフォルトでは CreateVolumePermissions は空(プライベート=自アカウントのみ)です。

Security Hub CSPM の [EC2.1] は「EBS スナップショットがパブリック共有されていないか」をチェック(検出)しますが、本コントロールを有効にすることで、そもそもパブリックへの属性変更操作自体を API レベルでブロックします。なお、本コントロールはパブリック共有(Group: all)のみをブロックし、特定アカウント ID へのクロスアカウント共有はブロックしない。

Security Hub CSPM の EC2.182 は「アカウントレベルの BPA 設定が block-all-sharing か」をチェックするが、本コントロールは BPA 設定を変更しないため、EC2.182 の finding には影響しない。BPA を強制するには CT.EC2.PV.7(Declarative Policy)を使用する。

なお、同じ目的のコントロールとして [CT.EC2.PV.7](Declarative Policy)があります。PV.7 はアカウントレベルで「Block Public Access for EBS Snapshots」を強制し、既にパブリック共有されていたスナップショットもプライベート扱いにします。EBS の Block Public Access はアカウントレベルの設定のみであり、S3 のようなリソース(バケット)レベルの設定はない。そのため、アカウントレベルで有効化すれば、個々のスナップショットのパブリック共有設定に関わらずパブリック共有がブロックされる。PV.3(SCP)は API コールをブロックするだけで、有効化前にパブリック共有されていたスナップショットはそのまま維持されるため、PV.7 の方がより強力です。

検証環境

検証環境の構成図

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

検証の流れ

    flowchart LR
    A[1. 事前準備] --> B[2. パブリック共有<br>成功を確認]
    B --> C[3. Security Hub EC2.1<br>FAILED 検出]
    C --> D[4. CT.EC2.PV.3<br>有効化]
    D --> E[5. 既存パブリック共有<br>維持を確認]
    D --> F[6. Security Hub EC2.1<br>FAILED のまま]
    D --> G[7. 新規パブリック共有<br>拒否を確認]
    D --> H[8. パブリック共有の<br>解除は可能]
    E & F & G & H --> I[9. CloudTrail<br>で確認]
    I --> J[10. CT.EC2.PV.3<br>無効化]
    J --> K[11. パブリック共有<br>成功を確認]
  

結果

  • コントロールの有効化前、EBS スナップショットをパブリック共有に設定でき、Security Hub [EC2.1] で「FAILED」として検出されることを確認できた。
  • コントロールの有効化後、スナップショットの createVolumePermissionGroup: all を追加する操作が拒否されることを確認できた。
  • 有効化前にパブリック共有されていたスナップショットについては、有効化後もパブリック設定が維持され、強制的なプライベート化は行われないことを確認できた。
  • コントロール有効化後も Security Hub [EC2.1] は FAILED のまま変化しなかった。SCP はパブリック共有の属性を変更しないため、Config の評価結果も NON_COMPLIANT のままであり、[CT.EC2.PV.7](Declarative Policy)で PASSED に変化する動作とは対照的である。
  • 有効化中でも、パブリック共有の解除(remove)は制限されないことを確認できた。SCP は ec2:Add/groupall の場合のみブロックするため、解除方向は許可される。
  • 拒否時、「explicit deny in a service control policy」というメッセージが表示されるため、SCP による制御であることを確認できた。
  • 拒否されたイベントが CloudTrail に記録されること、および管理アカウントの Organization Trail(CloudWatch Logs)からも確認できることを確認できた。
  • コントロールの無効化後、再びパブリック共有が可能になることを確認できた。
  • 有効化中でも、特定アカウント ID への共有(--user-ids)はブロックされないことを確認できた。本コントロールはパブリック共有(Group: all)のみをブロックし、クロスアカウント共有は制御対象外である。

1. 事前準備

検証用の EBS ボリュームを作成し、スナップショットを取得する。

aws ec2 create-volume \
  --availability-zone ap-northeast-1a \
  --size 1 \
  --volume-type gp3 \
  --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=ct-ec2-pv3-test}]' \
  --query '{VolumeId:VolumeId,State:State}'
{
    "VolumeId": "<ボリューム ID>",
    "State": "creating"
}

ボリュームが利用可能になったことを確認する。

aws ec2 describe-volumes \
  --volume-ids <ボリューム ID> \
  --query 'Volumes[0].{VolumeId:VolumeId,State:State}'
{
    "VolumeId": "<ボリューム ID>",
    "State": "available"
}

スナップショットを作成する。

aws ec2 create-snapshot \
  --volume-id <ボリューム ID> \
  --description "ct-ec2-pv3-test" \
  --tag-specifications 'ResourceType=snapshot,Tags=[{Key=Name,Value=ct-ec2-pv3-test}]' \
  --query '{SnapshotId:SnapshotId,State:State}'
{
    "SnapshotId": "<コントロール有効化前スナップショット ID>",
    "State": "pending"
}

スナップショットの作成が完了するまで待機する。

aws ec2 describe-snapshots \
  --snapshot-ids <コントロール有効化前スナップショット ID> \
  --query 'Snapshots[0].{SnapshotId:SnapshotId,State:State}'
{
    "SnapshotId": "<コントロール有効化前スナップショット ID>",
    "State": "completed"
}

スナップショットの共有設定がデフォルト(プライベート)であることを確認する。

aws ec2 describe-snapshot-attribute \
  --snapshot-id <コントロール有効化前スナップショット ID> \
  --attribute createVolumePermission
{
    "SnapshotId": "<コントロール有効化前スナップショット ID>",
    "CreateVolumePermissions": []
}

CreateVolumePermissions が空であれば、自アカウントのみがアクセス可能な状態(デフォルト)。

2. コントロール有効化前の確認

スナップショットをパブリック共有に設定する。

aws ec2 modify-snapshot-attribute \
  --snapshot-id <コントロール有効化前スナップショット ID> \
  --attribute createVolumePermission \
  --operation-type add \
  --group-names all

パブリック共有されたことを確認する。

aws ec2 describe-snapshot-attribute \
  --snapshot-id <コントロール有効化前スナップショット ID> \
  --attribute createVolumePermission
{
    "SnapshotId": "<コントロール有効化前スナップショット ID>",
    "CreateVolumePermissions": [
        {
            "Group": "all"
        }
    ]
}

Group: all が設定され、全 AWS アカウントからこのスナップショットを使ってボリュームを作成できる状態になった。

3. Security Hub CSPM での検出確認(EC2.1 FAILED)

Security Hub の [EC2.1](EBS snapshots should not be publicly restorable)でパブリック共有スナップショットが検出されることを確認する。

aws securityhub get-findings \
  --filters '{
    "ComplianceSecurityControlId": [{"Value": "EC2.1", "Comparison": "EQUALS"}],
    "AwsAccountId": [{"Value": "<Workload のアカウント ID>", "Comparison": "EQUALS"}],
    "ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}]
  }' \
  --query 'Findings[0].{Title:Title,ComplianceStatus:Compliance.Status,Severity:Severity.Label}'
{
    "Title": "EBS snapshots should not be publicly restorable",
    "ComplianceStatus": "FAILED",
    "Severity": "CRITICAL"
}
Security Hub の検出はスケジュールベースのため、パブリック共有直後には反映されない場合があります。すぐに確認したい場合は、末尾の「補足: Security Hub の検出を早める方法」を参照してください。

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/chlzfpsllhs3knp1ixr773wa6 \
  --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. 有効化後の挙動確認(既存パブリック共有の維持)

有効化前にパブリック共有されていたスナップショットが、有効化後もパブリック設定のまま維持されていることを確認する。

aws ec2 describe-snapshot-attribute \
  --snapshot-id <コントロール有効化前スナップショット ID> \
  --attribute createVolumePermission
{
    "SnapshotId": "<コントロール有効化前スナップショット ID>",
    "CreateVolumePermissions": [
        {
            "Group": "all"
        }
    ]
}

SCP は API コールをブロックするだけであり、有効化前に設定済みのパブリック共有は強制的にプライベート化されない。

6. Security Hub EC2.1 が FAILED のまま変化しないことの確認

SCP はパブリック共有の属性を変更しないため、コントロール有効化後も Security Hub [EC2.1] は FAILED のまま変化しない。Config ルールの手動トリガー後、Config の評価結果と Security Hub の検出結果の両方で確認する。

まず Config ルールの評価を手動トリガーする。

aws configservice start-config-rules-evaluation \
  --config-rule-names <Config ルール名>

Config の評価結果を直接確認する。

aws configservice get-compliance-details-by-config-rule \
  --config-rule-name <Config ルール名> \
  --query 'EvaluationResults[*].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}'
[
    {
        "ResourceId": "<Workload のアカウント ID>",
        "ComplianceType": "NON_COMPLIANT",
        "ResultRecordedTime": "<評価完了時刻>"
    }
]

Config レベルで NON_COMPLIANT のままであることを確認できた。

さらに 5 分以上待機した後、Security Hub でも FAILED のまま変化しないことを確認する。

aws securityhub get-findings \
  --filters '{
    "ComplianceSecurityControlId": [{"Value": "EC2.1", "Comparison": "EQUALS"}],
    "AwsAccountId": [{"Value": "<Workload のアカウント ID>", "Comparison": "EQUALS"}]
  }' \
  --query 'Findings[0].{Title:Title,ComplianceStatus:Compliance.Status,Severity:Severity.Label,ResourceId:Resources[0].Id}'
{
    "Title": "EBS snapshots should not be publicly restorable",
    "ComplianceStatus": "FAILED",
    "Severity": "CRITICAL",
    "ResourceId": "AWS::::Account:<Workload のアカウント ID>"
}

Config ルール手動トリガーから 5 分以上経過しても FAILED / CRITICAL のまま変化しなかった。[CT.EC2.PV.7](Declarative Policy)では Config COMPLIANT 後、約 4 分で Security Hub が PASSED に変化したことと対照的である。SCP はパブリック共有の属性自体を変更しないため、Config の評価結果も NON_COMPLIANT のままであり、反映ラグではなく本当に状態が変わらないことが確認できた。

7. 新規パブリック共有は制限(拒否)されることの確認

別のスナップショットを作成し、パブリック共有を試みる。

aws ec2 create-snapshot \
  --volume-id <ボリューム ID> \
  --description "ct-ec2-pv3-test-after" \
  --tag-specifications 'ResourceType=snapshot,Tags=[{Key=Name,Value=ct-ec2-pv3-test-after}]' \
  --query '{SnapshotId:SnapshotId,State:State}'
{
    "SnapshotId": "<コントロール有効化後スナップショット ID>",
    "State": "pending"
}

スナップショットの作成が完了するまで待機する。

aws ec2 describe-snapshots \
  --snapshot-ids <コントロール有効化後スナップショット ID> \
  --query 'Snapshots[0].{SnapshotId:SnapshotId,State:State}'
{
    "SnapshotId": "<コントロール有効化後スナップショット ID>",
    "State": "completed"
}

パブリック共有を試みる。

aws ec2 modify-snapshot-attribute \
  --snapshot-id <コントロール有効化後スナップショット ID> \
  --attribute createVolumePermission \
  --operation-type add \
  --group-names all

以下のようなエラーが返り、操作が拒否されることを確認する。

An error occurred (UnauthorizedOperation) when calling the ModifySnapshotAttribute operation:
You are not authorized to perform this operation.
User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<ユーザー名>
is not authorized to perform: ec2:ModifySnapshotAttribute on resource:
arn:aws:ec2:ap-northeast-1::snapshot/<コントロール有効化後スナップショット ID>
with an explicit deny in a service control policy

8. パブリック共有の解除は制限されないことの確認

SCP は ec2:Add/groupall の場合のみブロックするため、パブリック共有の解除(remove)は制限されない。ステップ 5 で確認したパブリック共有中のスナップショットで確認する。

aws ec2 modify-snapshot-attribute \
  --snapshot-id <コントロール有効化前スナップショット ID> \
  --attribute createVolumePermission \
  --operation-type remove \
  --group-names all
aws ec2 describe-snapshot-attribute \
  --snapshot-id <コントロール有効化前スナップショット ID> \
  --attribute createVolumePermission
{
    "SnapshotId": "<コントロール有効化前スナップショット ID>",
    "CreateVolumePermissions": []
}

パブリック共有の解除は正常に実行できた。

9. CloudTrail での確認

拒否されたイベントが CloudTrail に記録されていることを確認する。

Workload アカウントの CloudTrail イベント履歴から確認する。

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=ModifySnapshotAttribute \
  --start-time <検索開始時刻> \
  --end-time <検索終了時刻> \
  --query 'Events[].CloudTrailEvent' \
  --output text | jq 'select(.errorCode != null) | {eventName, errorCode, errorMessage}'
{
  "eventName": "ModifySnapshotAttribute",
  "errorCode": "Client.UnauthorizedOperation",
  "errorMessage": "You are not authorized to perform this operation. User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: ec2:ModifySnapshotAttribute on resource: arn:aws:ec2:ap-northeast-1::snapshot/<コントロール有効化後スナップショット ID> with an explicit deny in a service control policy: arn:aws:organizations::<管理アカウント ID>:policy/<組織 ID>/service_control_policy/<ポリシー ID>. Encoded authorization failure message: ..."
}

errorMessageexplicit deny in a service control policy と記録されており、SCP による拒否であることがわかる。

管理アカウントの Organization Trail(CloudWatch Logs)からも同じイベントを確認できる。

aws logs start-query \
  --log-group-name "aws-controltower/CloudTrailLogs" \
  --start-time $(date -d '<検索開始時刻>' +%s) \
  --end-time $(date -d '<検索終了時刻>' +%s) \
  --query-string 'fields @timestamp, eventName, errorCode, errorMessage
    | filter eventName = "ModifySnapshotAttribute" and errorCode like /UnauthorizedOperation|AccessDenied/
    | sort @timestamp asc' \
  --profile Master
{
    "queryId": "<クエリ ID>"
}

返却された queryId を指定して結果を取得する。

aws logs get-query-results \
  --query-id <クエリ ID> \
  --query '{results:results,status:status}' \
  --profile Master
{
    "results": [
        [
            {"field": "@timestamp", "value": "2026-03-14 13:07:45.988"},
            {"field": "eventName", "value": "ModifySnapshotAttribute"},
            {"field": "errorCode", "value": "Client.UnauthorizedOperation"},
            {"field": "errorMessage", "value": "You are not authorized to perform this operation. User: arn:aws:sts::<Workload のアカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: ec2:ModifySnapshotAttribute on resource: arn:aws:ec2:ap-northeast-1::snapshot/<コントロール有効化後スナップショット ID> with an explicit deny in a service control policy: arn:aws:organizations::<管理アカウント ID>:policy/<組織 ID>/service_control_policy/<ポリシー ID>. Encoded authorization failure message: ..."},
            {"field": "@ptr", "value": "<ログポインタ>"}
        ]
    ],
    "status": "Complete"
}

管理アカウントの CloudWatch Logs にも同じ拒否イベントが記録されていることを確認できた。

10. 対象の予防コントロールを無効化

検証完了後、コントロールを無効化する。

aws controltower disable-control \
  --control-identifier arn:aws:controlcatalog:::control/chlzfpsllhs3knp1ixr773wa6 \
  --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."
}

11. 制限解除の確認

コントロール無効化後、再びパブリック共有が可能になることを確認する。

aws ec2 modify-snapshot-attribute \
  --snapshot-id <コントロール有効化後スナップショット ID> \
  --attribute createVolumePermission \
  --operation-type add \
  --group-names all
aws ec2 describe-snapshot-attribute \
  --snapshot-id <コントロール有効化後スナップショット ID> \
  --attribute createVolumePermission
{
    "SnapshotId": "<コントロール有効化後スナップショット ID>",
    "CreateVolumePermissions": [
        {
            "Group": "all"
        }
    ]
}

パブリック共有が再び可能になり、制限が解除されたことを確認できた。

12. クリーンアップ

検証で作成したリソースをすべて削除する。パブリック共有も解除しておく。

# パブリック共有を解除
aws ec2 modify-snapshot-attribute \
  --snapshot-id <コントロール有効化後スナップショット ID> \
  --attribute createVolumePermission \
  --operation-type remove \
  --group-names all

# スナップショットを削除
aws ec2 delete-snapshot --snapshot-id <コントロール有効化前スナップショット ID>
aws ec2 delete-snapshot --snapshot-id <コントロール有効化後スナップショット ID>

# EBS ボリュームを削除
aws ec2 delete-volume --volume-id <ボリューム ID>

補足1: クロスアカウント共有はブロックされない

本コントロールはパブリック共有(Group: all)のみをブロックし、特定アカウント ID への共有はブロックしない。コントロール有効化中に以下を確認した。

aws ec2 modify-snapshot-attribute \
  --snapshot-id <スナップショット ID> \
  --attribute createVolumePermission \
  --operation-type add \
  --group-names all
An error occurred (UnauthorizedOperation) when calling the ModifySnapshotAttribute operation: ...
with an explicit deny in a service control policy: ...
aws ec2 modify-snapshot-attribute \
  --snapshot-id <スナップショット ID> \
  --attribute createVolumePermission \
  --operation-type add \
  --user-ids <組織外アカウント ID>
(出力なし=成功)

SCP の条件は ec2:Add/groupall の場合のみ Deny するため、--user-ids による特定アカウントへの共有は条件に該当せず許可される。クロスアカウント共有の制御が必要な場合は、カスタム SCP 等で別途対応する必要がある。

補足2: Security Hub の検出を早める方法

Security Hub のコンプライアンスチェックはスケジュールベース(最大 12 時間間隔)で実行されるため、リソースの変更が即座に反映されない場合がある。検証時など、すぐに検出結果を確認したい場合は、対応する AWS Config ルールの評価を手動でトリガーできる。

Security Hub のコントロールは内部的に AWS Config ルールとして実装されている。EC2.1 に対応する Config ルールは SourceIdentifierEBS_SNAPSHOT_PUBLIC_RESTORABLE_CHECK なので、これを条件にルール名を取得する。

aws configservice describe-config-rules \
  --query 'ConfigRules[?Source.SourceIdentifier==`EBS_SNAPSHOT_PUBLIC_RESTORABLE_CHECK`].ConfigRuleName'

出力例(末尾のハッシュ値は環境ごとに異なる):

[
    "securityhub-ebs-snapshot-public-restorable-check-ab81288f"
]

取得したルール名を指定して、評価を手動トリガーする。

aws configservice start-config-rules-evaluation \
  --config-rule-names <上記で取得したルール名>

数十秒〜数分程度で評価が完了し、結果が Security Hub に反映される。

なお、Security Hub への反映を待たずに Config ルールの評価結果を直接確認することもできる。

aws configservice get-compliance-details-by-config-rule \
  --config-rule-name <上記で取得したルール名> \
  --query 'EvaluationResults[*].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}'
[
    {
        "ResourceId": "<Workload のアカウント ID>",
        "ComplianceType": "NON_COMPLIANT",
        "ResultRecordedTime": "<評価完了時刻>"
    }
]

Config ルールの評価は手動トリガー後すぐに完了するが、Config から Security Hub への反映にはさらに数分かかる。Security Hub が FAILED に変わらない場合は、まず Config の評価結果を確認することで、問題が Config 側か反映ラグかを切り分けられる。

Amazonアソシエイトリンク