MSK.4
概要
MSK.4 は、MSK クラスターのパブリックアクセスが無効になっているかをチェックする Security Hub CSPM コントロールである。対応する Config ルールは msk-cluster-public-access-disabled。対象リソースタイプは AWS::MSK::Cluster(クラスター単位のコントロール)。
Amazon MSK(Managed Streaming for Apache Kafka)は、Apache Kafka を AWS 上で運用するためのマネージドサービス。MSK クラスターは顧客が作成した VPC のサブネット内にブローカーノード(EC2 インスタンス)として配置され、通信は顧客管理のセキュリティグループで制御される。
MSK クラスターには 2 種類のアクセス経路がある:
- VPC 内からのアクセス(デフォルト): VPC 内のクライアントがブローカーに接続する経路
- パブリックアクセス: インターネットからブローカーに接続する経路。MSK が各ブローカーにパブリック IP(Elastic IP)を割り当て、公開 DNS 名経由で接続可能になる
MSK.4 は後者(パブリックアクセス)が有効かどうかをチェックし、有効な場合に FAILED となる。
検出範囲
MSK.4 の対応 Config ルール msk-cluster-public-access-disabled の公式仕様は次の通り:
Checks if public access is disabled on Amazon MSK clusters. The rule is NON_COMPLIANT if public access on an Amazon MSK cluster is not disabled.
評価対象は 個別の MSK クラスター単位。クラスターの ConnectivityInfo.PublicAccess.Type の値で判定する:
PublicAccess.Type | 意味 | MSK.4 |
|---|---|---|
DISABLED | パブリックアクセス無効 | PASSED |
SERVICE_PROVIDED_EIPS | パブリックアクセス有効 | FAILED |
クラスターが 1 つも存在しないアカウントでは、Security Hub finding は AWS::::Account:<アカウント ID> をリソース ID として PASSED を返す(評価対象ゼロの場合のフォールバック)。クラスターが存在する場合、個別クラスターの ARN をリソース ID とする finding が生成される。
パブリックアクセス有効化の条件(意図性の強さ)
MSK クラスターのパブリックアクセスは、EMR の BPA や S3 の Block Public Access のような「設定ミスで有効化される可能性がある」機能とは性質が異なる。AWS 公式ドキュメントが示すように、パブリックアクセス有効化には多数の前提条件があり、作成時には有効化できず、既存クラスターに対して UpdateConnectivity API で明示的に操作する必要がある。
パブリックアクセス有効化に必要な条件は以下の通り(AWS 公式ドキュメント より):
| 条件 | 内容 | 「うっかり」発生度 |
|---|---|---|
| サブネット | パブリックサブネット(IGW へのルートを持つ)必須 | 中(VPC 選択次第) |
| Unauthenticated access | OFF 必須 | 低 |
| 強認証方式(SASL/IAM、SASL/SCRAM、mTLS のいずれか) | ON 必須 | 低(明示的設定) |
| Client-broker 暗号化 | TLS が必須と推測される(2021 年の AWS 機能リリース告知に “Public accessibility requires clients encrypt traffic using TLS” の記載あり。API レベルでの強制は本記事のステップ 5 の実機検証で確認する) | 低 |
ブローカー間暗号化(EncryptionInCluster) | ON 必須(作成時のみ設定可、デフォルト ON) | 自動 |
クラスター作成後の UpdateConnectivity 実行 | 明示的な API 呼び出しが必要 | 明確な意図が必要 |
作成時にはパブリックアクセスを有効化する API オプションが存在せず、必ず DISABLED で作成される。MSK.4 が FAILED になるケースは 意図的なパブリック公開の検出に近い。
なお、MSK クラスターは作成後に VPC やサブネットを変更できないため、作成時にプライベートサブネットを指定したクラスターは、そもそもパブリックアクセス有効化できない(前提条件「パブリックサブネット必須」を満たせない)。パブリック化のリスクは作成時のサブネット選択の時点で大きく決まる。
外部アクセス制御レイヤー
| レイヤー | コントロール | 役割 |
|---|---|---|
| デフォルト保護 | MSK クラスター作成時はパブリックアクセス有効化不可(必ず DISABLED で作成) | 作成時点ではどのような指定をしても MSK.4 は PASSED |
| 予防(SCP / RCP / 宣言型ポリシー) | なし | 本記事執筆時点(2026-05-09)では、Control Tower の標準予防コントロールに MSK クラスターのパブリック化を禁止するものは確認できない。カスタム SCP で kafka:UpdateConnectivity を Deny する運用は可能 |
| MSK のサービス機能 | パブリックアクセス有効化時の前提条件チェック | 認証・暗号化・サブネット配置の条件を満たさないクラスターは UpdateConnectivity API 自体が拒否される |
| 検出(CSPM、クラスター単位) | MSK.4(本記事) | 各 MSK クラスターの ConnectivityInfo.PublicAccess.Type を評価 |
本記事で確認すること
本検証では 単一の MSK クラスター を使って以下を段階的に確認する:
| # | 検証観点 | 状態 |
|---|---|---|
| 1 | CLI 最小構成(認証指定なし)で作成した MSK クラスターが MSK.4 で PASSED | 本記事で検証 |
| 2 | CLI 最小構成のクラスター(Unauthenticated のみ)では、パブリックアクセス有効化 API が拒否される | 本記事で検証 |
| 3 | UpdateSecurity で SASL/IAM 認証 + TLS 暗号化に変更し、パブリックアクセス有効化を可能にする | 本記事で検証 |
| 4 | パブリックアクセス有効化後、MSK.4 が FAILED になる | 本記事で検証 |
| 5 | パブリックアクセス無効化後、MSK.4 が PASSED に戻る | 本記事で検証 |
結果
(検証前)
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
本検証では デフォルト VPC の 3 つのパブリックサブネット(ap-northeast-1a / 1c / 1d)に MSK クラスターを配置する。パブリックアクセス有効化の前提条件「パブリックサブネット必須」を満たすため。
検証の流れ
flowchart LR
A[1. 前提確認] --> B[2. クラスター作成]
B --> C[3. ACTIVE 待ち]
C --> D[4. PASSED 確認]
D --> E[5. パブリック化試行<br>→ 失敗]
E --> F[6. UpdateSecurity]
F --> G[7. 更新完了待ち]
G --> H[8. パブリック化有効]
H --> I[9. 更新完了待ち]
I --> J[10. FAILED 確認]
J --> K[11. パブリック化無効]
K --> L[12. 更新完了待ち]
L --> M[13. PASSED 復帰]
M --> N[14. クリーンアップ]
MSK クラスターの作成と各 Update 操作にはそれぞれ 15〜30 分以上かかる場合があるため、検証全体で半日程度の時間を要する。実時間は実機検証後に結果セクションへ記録する。
1. 前提確認
パブリックサブネットの確認
デフォルト VPC のサブネット情報を取得する。MapPublicIpOnLaunch: true の 3 サブネット(3 AZ)が必要。
DEFAULT_VPC=$(aws ec2 describe-vpcs \
--filters Name=is-default,Values=true \
--query 'Vpcs[0].VpcId' --output text \
--region ap-northeast-1)
aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$DEFAULT_VPC \
--query 'Subnets[].{SubnetId:SubnetId,AZ:AvailabilityZone,MapPublicIpOnLaunch:MapPublicIpOnLaunch}' \
--region ap-northeast-1[
{"SubnetId": "<パブリックサブネット 1>", "AZ": "ap-northeast-1a", "MapPublicIpOnLaunch": true},
{"SubnetId": "<パブリックサブネット 2>", "AZ": "ap-northeast-1d", "MapPublicIpOnLaunch": true},
{"SubnetId": "<パブリックサブネット 3>", "AZ": "ap-northeast-1c", "MapPublicIpOnLaunch": true}
]VPC に IGW へのルートが存在することも確認する(厳密な意味での「パブリックサブネット」の定義)。
aws ec2 describe-route-tables \
--filters Name=vpc-id,Values=$DEFAULT_VPC \
--query 'RouteTables[].Routes[?GatewayId!=`null` && starts_with(GatewayId, `igw-`)].GatewayId' \
--region ap-northeast-1[
["<IGW ID>"]
]デフォルトセキュリティグループの確認
aws ec2 describe-security-groups \
--filters Name=vpc-id,Values=$DEFAULT_VPC Name=group-name,Values=default \
--query 'SecurityGroups[0].GroupId' --output text \
--region ap-northeast-1<デフォルト SG ID>既存 MSK クラスター確認
既存のクラスターがあると検証に影響する可能性があるため、ゼロ件であることを確認する。
aws kafka list-clusters-v2 \
--query 'ClusterInfoList[].{Name:ClusterName,State:State}' \
--region ap-northeast-1[]2. MSK クラスター作成(CLI 最小構成)
kafka.t3.small インスタンスを 3 ノード、3 AZ のパブリックサブネットに配置して作成する。ClientAuthentication を指定しない CLI 最小構成のため、認証方式はデフォルト挙動となる。
aws kafka create-cluster-v2 \
--cluster-name msk-4-test \
--provisioned '{
"BrokerNodeGroupInfo": {
"InstanceType": "kafka.t3.small",
"ClientSubnets": ["<パブリックサブネット 1>", "<パブリックサブネット 2>", "<パブリックサブネット 3>"],
"SecurityGroups": ["<デフォルト SG ID>"],
"StorageInfo": {
"EbsStorageInfo": {"VolumeSize": 1}
}
},
"KafkaVersion": "3.6.0",
"NumberOfBrokerNodes": 3
}' \
--region ap-northeast-1{
"ClusterArn": "arn:aws:kafka:ap-northeast-1:<アカウント ID>:cluster/msk-4-test/<UUID>",
"ClusterName": "msk-4-test",
"State": "CREATING",
"ClusterType": "PROVISIONED"
}出力された ClusterArn は後続のステップで使用する。
3. ACTIVE 状態になるまで待機
クラスターの作成完了まで待つ。MSK クラスターの作成には通常 15〜30 分かかる。
CLUSTER_ARN="<ステップ 2 で取得した ARN>"
while true; do
STATE=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.State' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') State: $STATE"
if [ "$STATE" = "ACTIVE" ]; then break; fi
if [ "$STATE" = "FAILED" ]; then echo "作成失敗"; break; fi
sleep 60
done<時刻> State: CREATING
<時刻> State: CREATING
...
<時刻> State: ACTIVE作成時のデフォルト設定確認
クラスターが ACTIVE になったら、ClientAuthentication・EncryptionInfo・ConnectivityInfo のデフォルト値を確認する。
aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.{ClientAuthentication:Provisioned.ClientAuthentication,EncryptionInfo:Provisioned.EncryptionInfo,ConnectivityInfo:Provisioned.BrokerNodeGroupInfo.ConnectivityInfo}' \
--region ap-northeast-1{
"ClientAuthentication": "<検証時に実測>",
"EncryptionInfo": "<検証時に実測>",
"ConnectivityInfo": "<検証時に実測>"
}4. デフォルト状態の PASSED 確認
Config ルール名のサフィックスはアカウントごとに異なるため、まずルール名を変数として取得する(以降のステップでも使用する)。
RULE_NAME=$(aws configservice describe-config-rules \
--query "ConfigRules[?starts_with(ConfigRuleName, 'securityhub-msk-cluster-public-access-disabled')].ConfigRuleName | [0]" \
--output text --region ap-northeast-1)
echo $RULE_NAMEsecurityhub-msk-cluster-public-access-disabled-<サフィックス>Config ルールを手動トリガーして即座に評価させる。
aws configservice start-config-rules-evaluation \
--config-rule-names $RULE_NAME \
--region ap-northeast-1(出力なし)1〜2 分待ってから評価結果を確認する。
sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name $RULE_NAME \
--query 'EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId==`<クラスター ARN または resourceId>`].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
--region ap-northeast-1[
{
"ResourceId": "<クラスター ARN または resourceId>",
"ComplianceType": "COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]Security Hub への反映を待ってから、finding を確認する。
sleep 120
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>",
"UpdatedAt": "<更新時刻>"
}
]5. 現状の認証設定ではパブリックアクセス有効化できないことの確認
ステップ 4 の実測で、現クラスターの ClientAuthentication と ClientBroker 暗号化の設定が判明する。CLI 最小構成で作成したクラスターでは、パブリックアクセス有効化の前提条件(強認証 ON + ClientBroker 暗号化)を満たしていない想定。この状態で UpdateConnectivity API を呼び出し、拒否されることを確認する。
まず現在の CurrentVersion を取得する。
CURRENT_VERSION=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.CurrentVersion' --output text \
--region ap-northeast-1)
echo $CURRENT_VERSION<バージョン文字列>パブリックアクセス有効化を試行する。
aws kafka update-connectivity \
--cluster-arn $CLUSTER_ARN \
--current-version $CURRENT_VERSION \
--connectivity-info '{"PublicAccess": {"Type": "SERVICE_PROVIDED_EIPS"}}' \
--region ap-northeast-1<エラー出力を検証時に記録>エラーで拒否されることで、「パブリックアクセス有効化には追加の設定変更が必要」という MSK の仕様が実証される。
6. UpdateSecurity で SASL/IAM + TLS に変更
パブリックアクセス有効化の前提条件を満たすため、クラスターの認証方式を Unauthenticated から SASL/IAM に、client-broker 暗号化を TLS_PLAINTEXT から TLS に変更する。
CURRENT_VERSION=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.CurrentVersion' --output text \
--region ap-northeast-1)
aws kafka update-security \
--cluster-arn $CLUSTER_ARN \
--current-version $CURRENT_VERSION \
--client-authentication '{
"Sasl": {"Iam": {"Enabled": true}},
"Unauthenticated": {"Enabled": false}
}' \
--encryption-info '{
"EncryptionInTransit": {"ClientBroker": "TLS"}
}' \
--region ap-northeast-1{
"ClusterArn": "<クラスター ARN>",
"ClusterOperationArn": "<operation ARN>"
}7. UpdateSecurity の完了待ち
while true; do
STATE=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.State' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') State: $STATE"
if [ "$STATE" = "ACTIVE" ]; then break; fi
if [ "$STATE" = "FAILED" ]; then echo "更新失敗"; break; fi
sleep 60
done<時刻> State: UPDATING
...
<時刻> State: ACTIVE更新後の認証設定を確認する。
aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.{ClientAuthentication:Provisioned.ClientAuthentication,ClientBroker:Provisioned.EncryptionInfo.EncryptionInTransit.ClientBroker}' \
--region ap-northeast-1{
"ClientAuthentication": {
"Sasl": {"Iam": {"Enabled": true}},
"Unauthenticated": {"Enabled": false}
},
"ClientBroker": "TLS"
}8. パブリックアクセス有効化
前提条件を満たしたので、UpdateConnectivity でパブリックアクセスを有効化する。
CURRENT_VERSION=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.CurrentVersion' --output text \
--region ap-northeast-1)
aws kafka update-connectivity \
--cluster-arn $CLUSTER_ARN \
--current-version $CURRENT_VERSION \
--connectivity-info '{"PublicAccess": {"Type": "SERVICE_PROVIDED_EIPS"}}' \
--region ap-northeast-1{
"ClusterArn": "<クラスター ARN>",
"ClusterOperationArn": "<operation ARN>"
}9. UpdateConnectivity の完了待ち
while true; do
STATE=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.State' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') State: $STATE"
if [ "$STATE" = "ACTIVE" ]; then break; fi
if [ "$STATE" = "FAILED" ]; then echo "更新失敗"; break; fi
sleep 60
done<時刻> State: UPDATING
...
<時刻> State: ACTIVEパブリックアクセスが有効化されたことを確認する。
aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.Provisioned.BrokerNodeGroupInfo.ConnectivityInfo' \
--region ap-northeast-1{
"PublicAccess": {"Type": "SERVICE_PROVIDED_EIPS"}
}10. パブリックアクセス有効後の FAILED 確認
Config ルールを再度トリガーする。
aws configservice start-config-rules-evaluation \
--config-rule-names $RULE_NAME \
--region ap-northeast-1(出力なし)評価結果を確認する。
sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name $RULE_NAME \
--query 'EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId==`<クラスター ARN または resourceId>`].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
--region ap-northeast-1[
{
"ResourceId": "<クラスター ARN または resourceId>",
"ComplianceType": "NON_COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]Security Hub finding を確認する。
sleep 120
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>",
"UpdatedAt": "<更新時刻>"
}
]11. パブリックアクセス無効化
CURRENT_VERSION=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.CurrentVersion' --output text \
--region ap-northeast-1)
aws kafka update-connectivity \
--cluster-arn $CLUSTER_ARN \
--current-version $CURRENT_VERSION \
--connectivity-info '{"PublicAccess": {"Type": "DISABLED"}}' \
--region ap-northeast-1{
"ClusterArn": "<クラスター ARN>",
"ClusterOperationArn": "<operation ARN>"
}12. 無効化の完了待ち
while true; do
STATE=$(aws kafka describe-cluster-v2 \
--cluster-arn $CLUSTER_ARN \
--query 'ClusterInfo.State' --output text \
--region ap-northeast-1)
echo "$(date '+%H:%M:%S') State: $STATE"
if [ "$STATE" = "ACTIVE" ]; then break; fi
if [ "$STATE" = "FAILED" ]; then echo "更新失敗"; break; fi
sleep 60
done13. PASSED 復帰確認
aws configservice start-config-rules-evaluation \
--config-rule-names $RULE_NAME \
--region ap-northeast-1(出力なし)sleep 90
aws configservice get-compliance-details-by-config-rule \
--config-rule-name $RULE_NAME \
--query 'EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId==`<クラスター ARN または resourceId>`].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
--region ap-northeast-1[
{
"ResourceId": "<クラスター ARN または resourceId>",
"ComplianceType": "COMPLIANT",
"ResultRecordedTime": "<評価時刻>"
}
]sleep 120
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>",
"UpdatedAt": "<更新時刻>"
}
]14. クリーンアップ
検証用クラスターを削除する。削除完了まで 10〜30 分かかる。
aws kafka delete-cluster \
--cluster-arn $CLUSTER_ARN \
--region ap-northeast-1{
"ClusterArn": "<クラスター ARN>",
"State": "DELETING"
}削除完了を確認する(list-clusters-v2 の結果から当該 ARN が消えるまで待つ)。
while true; do
COUNT=$(aws kafka list-clusters-v2 \
--query "length(ClusterInfoList[?ClusterArn=='$CLUSTER_ARN'])" \
--output text --region ap-northeast-1)
echo "$(date '+%H:%M:%S') 残存クラスター数: $COUNT"
if [ "$COUNT" = "0" ]; then echo "削除完了"; break; fi
sleep 60
done推奨設定
MSK クラスターは作成時にパブリックアクセス有効化の設定ができず、MSK.4 は初期状態で常に PASSED となる。ただし、UpdateSecurity + UpdateConnectivity の組み合わせで後から有効化可能。
運用上の推奨:
UpdateConnectivityを実行する権限は限定的に付与する(カスタム SCP でkafka:UpdateConnectivityを Deny する運用も検討)- MSK クラスターはプライベートサブネットに配置することで、パブリックアクセス有効化自体を構造的に不可能にする
- Kafka クライアントが AWS 外部にあってパブリック接続が必要な場合は、MSK のパブリックアクセスではなく Multi-VPC private connectivity や VPN / Direct Connect 経由の接続を優先的に検討する