MSK Public Access
カスタム SCP の説明
背景: MSK クラスターのパブリックアクセス
Amazon MSK クラスターのパブリックアクセスは、クラスター作成時には有効化できず、kafka:UpdateConnectivity API で既存クラスターに対して明示的に操作する必要がある。有効化には認証・暗号化・サブネット配置の前提条件があり、意図的な操作なしには発生しない。
詳細は MSK.4 を参照。
Security Hub CSPM MSK.4 との関係
Security Hub CSPM の MSK.4 は、MSK クラスターの ConnectivityInfo.PublicAccess.Type が DISABLED かどうかをチェックするコントロールである。クラスター作成時は必ず DISABLED で作成されるため、MSK.4 は新規クラスターで PASSED となる。
このカスタム SCP の役割
Control Tower の標準予防コントロールには MSK クラスターのパブリックアクセス有効化を制御するものが存在しないため、カスタム SCP で対応する。
kafka:UpdateConnectivity API には kafka:publicAccessEnabled 条件キーが提供されており、パブリックアクセスを有効化する操作(kafka:publicAccessEnabled: true)のみを Deny できる。これにより、パブリックアクセスを無効化する操作(kafka:publicAccessEnabled: false)は引き続き許可される。
適用順序の注意
MSK クラスターは作成時に必ず DISABLED で作成されるため、SSM.7 のような「事前設定 → SCP 適用」の順序制約はない。SCP を先に適用しても問題ない。
SCP の内容
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyMSKPublicAccessEnable",
"Effect": "Deny",
"Action": [
"kafka:UpdateConnectivity"
],
"Resource": "*",
"Condition": {
"Bool": {
"kafka:publicAccessEnabled": "true"
}
}
}
]
}Condition で kafka:publicAccessEnabled: true の場合のみ Deny するため、パブリックアクセスを無効化する UpdateConnectivity(kafka:publicAccessEnabled: false)は引き続き許可される。
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。Master アカウントでの操作には --profile Master を指定する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
本検証では MSK クラスターを作成する。クラスター作成・更新・削除には各 25〜35 分程度かかる。
検証の流れ
flowchart LR
A[1. 事前準備<br>MSK クラスター作成<br>MSK.4 PASSED 確認] --> B[2. SCP 適用前<br>パブリックアクセス有効化が成功]
B --> C[3. パブリックアクセス無効化<br>MSK.4 PASSED 復帰]
C --> D[4. SCP 作成]
D --> E[5. OU にアタッチ]
E --> F[6. パブリックアクセス有効化が<br>Deny されることを確認]
F --> G[7. パブリックアクセス無効化は<br>引き続き許可されることを確認]
G --> H[8. SCP デタッチ]
H --> I[9. パブリックアクセス有効化が<br>再び成功することを確認]
I --> J[10. クリーンアップ<br>クラスター削除・SCP 削除]
結果
- SCP 適用前:
update-connectivity(PublicAccess: SERVICE_PROVIDED_EIPS)が成功することを確認した - SCP 適用後:
AccessDeniedExceptionで拒否された。エラーメッセージにexplicit deny in a service control policyが含まれており、SCP による拒否であることを確認した - SCP 適用中の無効化操作:
kafka:publicAccessEnabled: false(PublicAccess: DISABLED)のupdate-connectivityは許可されることを確認した(条件キーBool型 Deny はtrueの場合のみ Deny する) - SCP デタッチ後: 再びパブリックアクセス有効化が成功することを確認した
- MSK.4 finding: パブリックアクセス有効化後に FAILED に変化することを確認した(Config の Configuration Item 更新にはタグ追加などの別操作が必要)
- SCP のラグ: アタッチ・デタッチの反映には数分かかる場合がある(実測: デタッチ後約 5 分 Deny が続いた)
1. 事前準備(MSK クラスター作成)
デフォルト VPC のサブネット ID を確認
パブリックアクセス有効化の前提条件「パブリックサブネット必須」を満たすため、デフォルト VPC のサブネットを使用する。
aws ec2 describe-subnets \
--filters "Name=defaultForAz,Values=true" \
--query 'Subnets[].{SubnetId:SubnetId,AZ:AvailabilityZone}' \
--region ap-northeast-1[
{"SubnetId": "<サブネット ID 1>", "AZ": "ap-northeast-1a"},
{"SubnetId": "<サブネット ID 2>", "AZ": "ap-northeast-1c"},
{"SubnetId": "<サブネット ID 3>", "AZ": "ap-northeast-1d"}
]デフォルト VPC の IGW とサブネットのパブリック性を確認
MSK のパブリックアクセス有効化の前提条件として、サブネットがインターネットゲートウェイ(IGW)へのルートを持つ必要がある。デフォルト VPC は通常 IGW がアタッチされているが、過去の設定変更等でルートテーブルが変わっている可能性があるため、実機で確認する。
VPC_ID=$(aws ec2 describe-vpcs \
--filters 'Name=isDefault,Values=true' \
--query 'Vpcs[0].VpcId' \
--output text \
--region ap-northeast-1)
echo "VPC ID: $VPC_ID"
aws ec2 describe-internet-gateways \
--filters "Name=attachment.vpc-id,Values=$VPC_ID" \
--query 'InternetGateways[].{IgwId:InternetGatewayId,State:Attachments[0].State}' \
--region ap-northeast-1[
{
"IgwId": "<IGW ID>",
"State": "available"
}
]各サブネットに紐づくルートテーブルに IGW ルートがあることを確認する。
for SUBNET in <サブネット ID 1> <サブネット ID 2> <サブネット ID 3>; do
RT=$(aws ec2 describe-route-tables \
--filters "Name=association.subnet-id,Values=$SUBNET" \
--query 'RouteTables[0].RouteTableId' \
--output text \
--region ap-northeast-1 2>/dev/null)
if [ "$RT" = "None" ] || [ -z "$RT" ]; then
RT=$(aws ec2 describe-route-tables \
--filters "Name=vpc-id,Values=$VPC_ID" "Name=association.main,Values=true" \
--query 'RouteTables[0].RouteTableId' \
--output text \
--region ap-northeast-1)
fi
IGW_ROUTE=$(aws ec2 describe-route-tables \
--route-table-ids $RT \
--query 'RouteTables[0].Routes[?GatewayId!=null && starts_with(GatewayId, `igw-`)].{Dest:DestinationCidrBlock,GW:GatewayId}' \
--output json \
--region ap-northeast-1)
echo "Subnet $SUBNET → RT $RT, IGW Route: $IGW_ROUTE"
doneSubnet <サブネット ID 1> → RT <RT ID>, IGW Route: [{"Dest":"0.0.0.0/0","GW":"<IGW ID>"}]
Subnet <サブネット ID 2> → RT <RT ID>, IGW Route: [{"Dest":"0.0.0.0/0","GW":"<IGW ID>"}]
Subnet <サブネット ID 3> → RT <RT ID>, IGW Route: [{"Dest":"0.0.0.0/0","GW":"<IGW ID>"}]各サブネットのルートテーブルに 0.0.0.0/0 → IGW のルートがあれば、パブリックサブネットとして MSK のパブリックアクセス有効化要件を満たす。
デフォルト VPC のデフォルトセキュリティグループを取得
MSK クラスター作成時に SecurityGroups を指定する必要がある。デフォルト VPC のデフォルト SG を使用する。
DEFAULT_SG=$(aws ec2 describe-security-groups \
--filters "Name=group-name,Values=default" "Name=vpc-id,Values=$VPC_ID" \
--query 'SecurityGroups[0].GroupId' \
--output text \
--region ap-northeast-1)
echo "Default SG: $DEFAULT_SG"Default SG: <SG ID>MSK クラスター作成
aws kafka create-cluster-v2 \
--cluster-name msk-scp-test \
--provisioned '{
"BrokerNodeGroupInfo": {
"InstanceType": "kafka.t3.small",
"ClientSubnets": ["<サブネット ID 1>", "<サブネット ID 2>", "<サブネット ID 3>"],
"SecurityGroups": ["<SG ID>"]
},
"KafkaVersion": "3.5.1",
"NumberOfBrokerNodes": 3
}' \
--query 'ClusterArn' \
--output text \
--region ap-northeast-1arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>クラスター作成には約 25 分かかる。
クラスター ACTIVE 待機
aws kafka describe-cluster-v2 \
--cluster-arn <クラスター ARN> \
--query 'ClusterInfo.{State:State,CurrentVersion:CurrentVersion}' \
--region ap-northeast-1{
"State": "ACTIVE",
"CurrentVersion": "<バージョン>"
}ACTIVE になるまで数分おきに確認する。CurrentVersion は以降の update-security / update-connectivity で使用する。
パブリックアクセス有効化の前提条件を整える
CLI 最小構成で作成したクラスターは認証未設定のため、パブリックアクセス有効化前に SASL/IAM 認証を有効化する必要がある。
UPDATE_ID=$(aws kafka update-security \
--cluster-arn <クラスター ARN> \
--current-version <クラスターの現在バージョン> \
--client-authentication '{"Sasl": {"Iam": {"Enabled": true}}}' \
--query 'ClusterOperationArn' \
--output text \
--region ap-northeast-1)
echo "Operation ARN: $UPDATE_ID"Operation ARN: arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster-operation/<操作 ID>操作完了まで約 30〜35 分かかる。完了を確認する。
aws kafka describe-cluster-v2 \
--cluster-arn <クラスター ARN> \
--query 'ClusterInfo.State' \
--region ap-northeast-1"ACTIVE"Config ルール名のサフィックスを確認
aws configservice describe-config-rules \
--query 'ConfigRules[?contains(ConfigRuleName,`msk-cluster-public-access-disabled`)].ConfigRuleName' \
--output text \
--region ap-northeast-1securityhub-msk-cluster-public-access-disabled-<サフィックス>Security Hub MSK.4 finding 確認(PASSED であること)
MSK.4 は Configuration changes 型のため、クラスター ARN をリソース ID として finding が生成される。
aws configservice start-config-rules-evaluation \
--config-rule-names securityhub-msk-cluster-public-access-disabled-<サフィックス> \
--region ap-northeast-1(出力なし)1〜2 分待ってから評価結果を確認する。ResultRecordedTime がクラスター作成時刻より後であることを確認し、再評価されたことを示す。
sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name securityhub-msk-cluster-public-access-disabled-<サフィックス> \
--query "EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId=='<クラスター ARN>'].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}" \
--region ap-northeast-1[
{
"ResourceId": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"ComplianceType": "COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]aws securityhub get-findings \
--filters '{
"ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "MSK.4"}],
"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:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"UpdatedAt": "<更新時刻>"
}
]MSK.4 が PASSED であることを確認する。クラスター作成前から存在する AWS::::Account:<アカウント ID> の finding(クラスターが存在しない場合のフォールバック)も残っているが、クラスター ARN の finding が PASSED であれば問題ない。これで検証の前提条件が成立する。
2. SCP 適用前のベースライン確認
パブリックアクセスを有効化できることを確認する。
aws kafka update-connectivity \
--cluster-arn <クラスター ARN> \
--connectivity-info '{"PublicAccess": {"Type": "SERVICE_PROVIDED_EIPS"}}' \
--current-version <クラスターの現在バージョン> \
--region ap-northeast-1{
"ClusterArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"ClusterOperationArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster-operation/<操作 ID>"
}--current-version は describe-cluster-v2 の ClusterInfo.CurrentVersion で確認できる。操作完了(ACTIVE)まで約 30 分待つ。
SCP 適用前はパブリックアクセス有効化が成功する。
3. パブリックアクセス無効化(MSK.4 PASSED 復帰)
SCP 適用前に元の状態(パブリックアクセス無効)に戻す。
aws kafka update-connectivity \
--cluster-arn <クラスター ARN> \
--connectivity-info '{"PublicAccess": {"Type": "DISABLED"}}' \
--current-version <クラスターの現在バージョン> \
--region ap-northeast-1{
"ClusterArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"ClusterOperationArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster-operation/<操作 ID>"
}操作完了(ACTIVE)まで約 30 分待つ。
MSK.4 PASSED 復帰確認
aws configservice start-config-rules-evaluation \
--config-rule-names securityhub-msk-cluster-public-access-disabled-<サフィックス> \
--region ap-northeast-1(出力なし)1〜2 分待ってから評価結果を確認する。ResultRecordedTime が無効化操作の時刻より後であることを確認し、再評価されたことを示す。
sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name securityhub-msk-cluster-public-access-disabled-<サフィックス> \
--query "EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId=='<クラスター ARN>'].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}" \
--region ap-northeast-1[
{
"ResourceId": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"ComplianceType": "COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]aws securityhub get-findings \
--filters '{
"ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "MSK.4"}],
"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:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"UpdatedAt": "<更新時刻>"
}
]MSK.4 は Configuration changes 型の Config ルールだが、UpdateConnectivity が Config の Configuration Item を自動更新しない場合がある(MSK.4 検証 で観測)。その場合はタグ追加などの別操作で CI 再キャプチャを誘発する必要がある。
タグ追加後も CI 更新に数分かかる場合があるため、NON_COMPLIANT が続く場合は数分待ってから Config を再トリガーする。
また、タグ追加後にクラスターが UPDATING 状態に遷移する場合がある(本検証では初回タグ追加時に約 46 分 UPDATING が続いた)。一方、タグ値の変更(既存キーへの上書き)では即 ACTIVE のまま完了するケースも観測されている。Security Hub の UpdatedAt は Config の評価結果が変わらない場合(COMPLIANT のまま)は更新されないことがある。
4. カスタム SCP 作成
ポリシー JSON ファイル作成
cat > /tmp/scp-msk-public-access.json <<'EOF'
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyMSKPublicAccessEnable",
"Effect": "Deny",
"Action": [
"kafka:UpdateConnectivity"
],
"Resource": "*",
"Condition": {
"Bool": {
"kafka:publicAccessEnabled": "true"
}
}
}
]
}
EOFSCP 作成
aws organizations create-policy \
--name DenyMSKPublicAccessEnable \
--description "Deny enabling public access on MSK clusters" \
--type SERVICE_CONTROL_POLICY \
--content file:///tmp/scp-msk-public-access.json \
--query 'Policy.PolicySummary.{Id:Id,Name:Name,Type:Type}' \
--profile Master{
"Id": "<ポリシー ID>",
"Name": "DenyMSKPublicAccessEnable",
"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==`DenyMSKPublicAccessEnable`]' \
--profile Master[
{
"Id": "<ポリシー ID>",
"Arn": "arn:aws:organizations::<Master アカウント ID>:policy/o-<組織 ID>/service_control_policy/<ポリシー ID>",
"Name": "DenyMSKPublicAccessEnable",
"Description": "Deny enabling public access on MSK clusters",
"Type": "SERVICE_CONTROL_POLICY",
"AwsManaged": false
}
]SCP の適用には数秒〜数分のラグがある。ステップ 6 の操作はアタッチ確認後、さらに数分待ってから実施すること。
6. パブリックアクセス有効化が Deny されることを確認
aws kafka update-connectivity \
--cluster-arn <クラスター ARN> \
--connectivity-info '{"PublicAccess": {"Type": "SERVICE_PROVIDED_EIPS"}}' \
--current-version <クラスターの現在バージョン> \
--region ap-northeast-1 2>&1An error occurred (AccessDeniedException) when calling the UpdateConnectivity operation: User: arn:aws:sts::<Workload アカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: kafka:UpdateConnectivity on resource: arn:aws:kafka:ap-northeast-1:<Workload アカウント ID>:cluster/msk-scp-test/<クラスター ID> with an explicit deny in a service control policyexplicit deny in a service control policy というメッセージが含まれることから、SCP による拒否であることを確認できる。
7. パブリックアクセス無効化は引き続き許可されることを確認
SCP の Condition は kafka:publicAccessEnabled: true の場合のみ Deny するため、無効化操作(DISABLED)は許可されるはず。現在すでに DISABLED なので、一度 SCP をデタッチしてパブリックアクセスを有効化してから再アタッチして確認する。
SCP を一時デタッチ
aws organizations detach-policy \
--policy-id <ポリシー ID> \
--target-id <OU ID> \
--profile Master(出力なし)パブリックアクセスを有効化
aws kafka update-connectivity \
--cluster-arn <クラスター ARN> \
--connectivity-info '{"PublicAccess": {"Type": "SERVICE_PROVIDED_EIPS"}}' \
--current-version <クラスターの現在バージョン> \
--region ap-northeast-1操作完了(ACTIVE)まで約 30 分待つ。
SCP を再アタッチ
aws organizations attach-policy \
--policy-id <ポリシー ID> \
--target-id <OU ID> \
--profile Master(出力なし)数分待ってから無効化操作を試みる。
パブリックアクセスを無効化(SCP に阻まれず成功するはず)
aws kafka update-connectivity \
--cluster-arn <クラスター ARN> \
--connectivity-info '{"PublicAccess": {"Type": "DISABLED"}}' \
--current-version <クラスターの現在バージョン> \
--region ap-northeast-1{
"ClusterArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"ClusterOperationArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster-operation/<操作 ID>"
}SCP の Condition により、無効化操作は許可される。
8. SCP デタッチ
aws organizations detach-policy \
--policy-id <ポリシー ID> \
--target-id <OU ID> \
--profile Master(出力なし)9. パブリックアクセス有効化が再び成功することを確認
SCP のデタッチには数分のラグがある場合があるため、Deny が続く場合は数分待ってから再試行する。
aws kafka update-connectivity \
--cluster-arn <クラスター ARN> \
--connectivity-info '{"PublicAccess": {"Type": "SERVICE_PROVIDED_EIPS"}}' \
--current-version <クラスターの現在バージョン> \
--region ap-northeast-1{
"ClusterArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"ClusterOperationArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster-operation/<操作 ID>"
}SCP デタッチ後は再びパブリックアクセスを有効化できる。操作完了(ACTIVE)まで約 30 分待つ。
Security Hub MSK.4 finding 確認(FAILED に変化)
最終状態はパブリックアクセス有効なので、MSK.4 は FAILED に変化するはず。これを確認することで、検証中に SCP 以外の何かが MSK.4 の評価に影響していないことの証明になる。
aws configservice start-config-rules-evaluation \
--config-rule-names securityhub-msk-cluster-public-access-disabled-<サフィックス> \
--region ap-northeast-1(出力なし)1〜2 分待ってから評価結果を確認する。ResultRecordedTime が有効化操作の時刻より後であることを確認し、再評価されたことを示す。
sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name securityhub-msk-cluster-public-access-disabled-<サフィックス> \
--query "EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId=='<クラスター ARN>'].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}" \
--region ap-northeast-1[
{
"ResourceId": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"ComplianceType": "NON_COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]aws securityhub get-findings \
--filters '{
"ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "MSK.4"}],
"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:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"UpdatedAt": "<更新時刻>"
}
]MSK.4 が FAILED に変化した。SCP デタッチ後に有効化操作が成功し、想定通りの状態になっていることを確認できる。
10. クリーンアップ
クラスターを削除
aws kafka delete-cluster \
--cluster-arn <クラスター ARN> \
--region ap-northeast-1{
"ClusterArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-scp-test/<クラスター ID>",
"State": "DELETING"
}削除には約 4 分かかる。
SCP 削除
aws organizations delete-policy \
--policy-id <ポリシー ID> \
--profile Master(出力なし)