コンテンツにスキップ

MSK.4

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

概要

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 accessOFF 必須
強認証方式(SASL/IAM、SASL/SCRAM、mTLS のいずれか)ON 必須低(明示的設定)
Client-broker 暗号化TLS が必須とされる(2021 年の AWS 機能リリース告知に “Public accessibility requires clients encrypt traffic using TLS” の記載あり)。本検証では CLI 最小構成のデフォルトが既に TLS だったため、TLS_PLAINTEXT での有効化試行は行わなかった
ブローカー間暗号化(EncryptionInClusterON 必須(作成時のみ設定可、デフォルト 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 クラスター を使って以下を段階的に確認する:

#検証観点状態
1CLI 最小構成(認証指定なし)で作成した MSK クラスターが MSK.4 で PASSED✓ 検証完了
2CLI 最小構成で作成したクラスター(ClientAuthentication: null = 強認証未設定・Unauthenticated も明示なし)では、パブリックアクセス有効化 API が拒否される✓ 検証完了
3UpdateSecurity で SASL/IAM 認証 + TLS 暗号化に変更し、パブリックアクセス有効化を可能にする✓ 検証完了
4パブリックアクセス有効化後、MSK.4 が FAILED になる✓ 検証完了
5パブリックアクセス無効化後、MSK.4 が PASSED に戻る✓ 検証完了

結果

  • クラスター作成時のデフォルト設定(CLI 最小構成): ClientAuthentication: null(未設定)、ClientBroker: TLSInCluster: truePublicAccess: DISABLED
  • デフォルト状態(CLI 最小構成のクラスター): MSK.4 は PASSED
  • CLI 最小構成のクラスターでパブリックアクセス有効化: BadRequestException で拒否される(認証方式: SASL/SCRAM, SASL/IAM, TLS のいずれかが ON である必要がある旨のエラー)
  • UpdateSecurity で SASL/IAM + Unauthenticated 無効化に変更後: UpdateConnectivity でパブリックアクセス有効化が可能となる
  • パブリックアクセス有効化後: MSK.4 は FAILED(ただし Config Configuration Item が自動更新されないケースがあり、タグ追加など別操作で CI 再キャプチャを誘発する必要がある場合がある)
  • パブリックアクセス無効化後: MSK.4 は PASSED に復帰(同様に Configuration Item の再キャプチャを誘発する必要がある)
  • クラスターのパブリックアクセス状態遷移(UPDATINGACTIVE)には多くの時間を要する: 本検証では UpdateSecurity が約 34 分、UpdateConnectivity の有効化が約 32 分、無効化が約 31 分、クラスター作成が約 25 分、クラスター削除が約 4 分。タグ追加の処理も状況により 46 分ほど UPDATING が続く場合がある
  • 本検証で観測した MSK API の特殊挙動: UPDATING 状態のクラスターに update-connectivity を実行すると API レスポンスは BadRequestException を返すが、内部では操作が受理されて処理が進行するケースがあった(list-cluster-operations-v2 で確認可能)

検証環境

検証環境の構成図

本記事のコマンドは、--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 操作にはそれぞれ長時間かかる。本検証での実測所要時間は以下の通り、API の待機時間だけで合計約 130 分。実際にはコマンド実行・結果確認・想定外挙動への対処(UPDATING 中の API エラー、Configuration Item 更新トリガーなど)も加わり、実作業全体では 4 時間以上要した。

ステップ操作実測所要時間
2 → 3クラスター作成(CREATINGACTIVE約 25 分
6 → 7UpdateSecurityUPDATINGACTIVE約 34 分
8 → 9UpdateConnectivity で有効化(UPDATINGACTIVE約 32 分
10タグ追加 → Config Configuration Item 更新 → 評価反映約 4 分(タグ追加から Config 評価完了まで)
11 → 12UpdateConnectivity で無効化(UPDATINGACTIVE約 31 分(タグ更新と連鎖して発生)
14クラスター削除(DELETING → 完全消失)約 4 分
合計約 130 分(≒ 2 時間 10 分)

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 になったら、ClientAuthenticationEncryptionInfoConnectivityInfo のデフォルト値を確認する。

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": null,
    "EncryptionInfo": {
        "EncryptionAtRest": {
            "DataVolumeKMSKeyId": "<KMS Key ARN>"
        },
        "EncryptionInTransit": {
            "ClientBroker": "TLS",
            "InCluster": true
        }
    },
    "ConnectivityInfo": {
        "PublicAccess": {"Type": "DISABLED"},
        "VpcConnectivity": {
            "ClientAuthentication": {
                "Sasl": {
                    "Scram": {"Enabled": false},
                    "Iam": {"Enabled": false}
                },
                "Tls": {"Enabled": false}
            }
        }
    }
}

本検証では ClientAuthenticationnull(未設定)、ClientBroker は既に TLSInClustertruePublicAccess.TypeDISABLED だった。CLI 最小構成で作成した場合、ステップ 6 の UpdateSecurity で変更が必要なのは 認証方式(SASL/IAM 有効化 + Unauthenticated 無効化) のみ。ClientBroker はデフォルトで既に TLS なので変更不要。

API リファレンスと実機挙動の乖離: AWS CLI リファレンス では EncryptionInTransit.ClientBroker のデフォルト値を TLS_PLAINTEXT と明記しているが、本検証では実機のデフォルト値は TLS だった。AWS 公式ドキュメントの記述と実機挙動が乖離している。

4. デフォルト状態の PASSED 確認

Config ルール名のサフィックスはアカウントごとに異なるため、まずルール名を変数として取得する(以降のステップでも使用する)。

RULE_NAME=$(aws configservice describe-config-rules \
  --query 'ConfigRules[].ConfigRuleName' --output text \
  --region ap-northeast-1 | tr '\t' '\n' \
  | grep '^securityhub-msk-cluster-public-access-disabled-')
echo $RULE_NAME
securityhub-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 の実測で、現クラスターの ClientAuthenticationClientBroker 暗号化の設定が判明する。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
An error occurred (BadRequestException) when calling the UpdateConnectivity operation: To enable public access, ensure that the access control methods for the cluster include at least one of the following client authentication mechanisms: SASL/SCRAM, SASL/IAM, TLS. Also ensure that unauthenticated access control is turned off.

エラーメッセージから、パブリックアクセス有効化に必要なのは「SASL/SCRAM、SASL/IAM、TLS のいずれかの認証方式が ON」かつ「Unauthenticated access control が OFF」であることが分かる。本検証では ClientBroker は既に TLS であるため、エラーメッセージでは言及されなかった。

6. UpdateSecurity で SASL/IAM + TLS に変更

ステップ 3 で実測した通り、ClientAuthenticationnull(未設定)、ClientBroker は既に TLS となっている。パブリックアクセス有効化の前提条件のうち「認証方式」のみが満たされていないため、update-security で SASL/IAM 有効 + Unauthenticated 無効に変更する。update-security 自体は --client-authentication--encryption-info のいずれか片方のみで実行可能だが、本検証では両方を明示的に指定して実行した(--client-authentication のみで実行した場合の挙動は確認していない)。

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 確認

MSK.4 対応の Config ルール msk-cluster-public-access-disabledConfiguration changes 型 だが、本検証の実測では UpdateConnectivity による PublicAccess フィールド変更だけでは Config の Configuration Item が更新されないケースがあった。その状態では start-config-rules-evaluation を実行しても 古い Configuration Item(パブリックアクセス無効時のもの)を参照して COMPLIANT を返すため、FAILED 遷移が観測できない。

この挙動を回避するため、本ステップでは先にクラスターへタグ追加を行い、Config の Configuration Item を強制的に更新させる。タグ追加により PublicAccess: SERVICE_PROVIDED_EIPS を含む新しい Configuration Item がキャプチャされ、それに対してルールが評価される。

タグ追加で Config の Configuration Item を更新

aws kafka tag-resource \
  --resource-arn $CLUSTER_ARN \
  --tags "VerificationTrigger=MSK4" \
  --region ap-northeast-1
(出力なし)

1〜数分待ってから、Configuration Item が更新されていることを確認する。

sleep 180
aws configservice get-resource-config-history \
  --resource-type AWS::MSK::Cluster \
  --resource-id $CLUSTER_ARN \
  --limit 1 \
  --query 'configurationItems[0].configuration' \
  --region ap-northeast-1 \
  | python3 -c "import json,sys; c=json.loads(json.loads(sys.stdin.read())); print(json.dumps(c['BrokerNodeGroupInfo']['ConnectivityInfo']['PublicAccess']))"

AWS Config の configurationItems[].configuration は JSON 文字列としてエスケープされて返るため、--query で取得した値(文字列化された JSON)と、その内側の JSON を両方デコードするために json.loads を 2 回呼んでいる。

{"Type": "SERVICE_PROVIDED_EIPS"}

SERVICE_PROVIDED_EIPS が取得できれば Configuration Item の更新完了。

Config ルール評価

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[].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
  --region ap-northeast-1
[
    {
        "ResourceId": "<クラスター ARN>",
        "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

本検証ではこのコマンドを実行した時点でクラスターが UPDATING 状態にあり(ステップ 10 のタグ追加処理の継続)、以下のエラーが返った。

An error occurred (BadRequestException) when calling the UpdateConnectivity operation: This operation is only valid for resources that are in one of the following states :[ACTIVE]
本検証で観測した MSK API の例外挙動: 上記のエラーで拒否されたように見えたが、実際には MSK 側で操作が受理されており、UpdateConnectivity 処理がバックグラウンドで実行されていた。これは aws kafka list-cluster-operations-v2 で確認できる(ステップ 12 後に確認する)。API のエラーメッセージは信頼できる指標とは限らないため、想定外の状態になった場合は list-cluster-operations-v2 で実際に実行された操作の履歴を確認するとよい。

12. 無効化の完了待ち

クラスターが ACTIVE に戻るまで待機する。タグ追加と(受理された)パブリックアクセス無効化の処理が並行して進むため、ACTIVE 復帰まで 30 分程度かかる。

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

ACTIVE 復帰後、パブリックアクセスが無効化されていることを確認する。

aws kafka describe-cluster-v2 \
  --cluster-arn $CLUSTER_ARN \
  --query 'ClusterInfo.Provisioned.BrokerNodeGroupInfo.ConnectivityInfo.PublicAccess' \
  --region ap-northeast-1
{
    "Type": "DISABLED"
}

操作履歴で UpdateConnectivity が実際に実行されたことも確認できる。

aws kafka list-cluster-operations-v2 \
  --cluster-arn $CLUSTER_ARN \
  --query 'ClusterOperationInfoList[].{StartTime:StartTime,EndTime:EndTime,OperationType:OperationType,OperationState:OperationState}' \
  --region ap-northeast-1
[
    ...
    {
        "StartTime": "<無効化コマンド実行時刻付近>",
        "EndTime": "<ACTIVE 復帰時刻付近>",
        "OperationType": "UPDATE_CONNECTIVITY",
        "OperationState": "UPDATE_COMPLETE"
    }
]

13. PASSED 復帰確認

ステップ 10 と同様、UpdateConnectivity DISABLED の完了だけでは Config の Configuration Item が自動的に更新されない。まず先に Config 評価を試し、NON_COMPLIANT のままだった場合に Configuration Item の再キャプチャを誘発する(タグ値を変更する)。

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[].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
  --region ap-northeast-1

本検証では NON_COMPLIANT のまま返った。Configuration Item 履歴を確認すると、直近の CI に PublicAccess: SERVICE_PROVIDED_EIPS(有効化時の状態)が残っており、無効化後の最新状態を反映していなかった。

aws configservice get-resource-config-history \
  --resource-type AWS::MSK::Cluster \
  --resource-id $CLUSTER_ARN \
  --limit 1 \
  --query 'configurationItems[0].configuration' \
  --region ap-northeast-1 \
  | python3 -c "import json,sys; c=json.loads(json.loads(sys.stdin.read())); print(json.dumps(c['BrokerNodeGroupInfo']['ConnectivityInfo']['PublicAccess']))"
{"Type": "SERVICE_PROVIDED_EIPS"}

タグ値変更で Configuration Item 再キャプチャを誘発

ステップ 10 で追加したタグ(VerificationTrigger=MSK4)の値を別の値に変更する。同じキーに別の値を設定することでタグ変更が行われ、Configuration Item が再キャプチャされる。

aws kafka tag-resource \
  --resource-arn $CLUSTER_ARN \
  --tags "VerificationTrigger=MSK4-Step13" \
  --region ap-northeast-1
(出力なし)

1〜数分待ってから、Configuration Item が更新され、PublicAccess: DISABLED が記録されていることを確認する。

sleep 180
aws configservice get-resource-config-history \
  --resource-type AWS::MSK::Cluster \
  --resource-id $CLUSTER_ARN \
  --limit 1 \
  --query 'configurationItems[0].configuration' \
  --region ap-northeast-1 \
  | python3 -c "import json,sys; c=json.loads(json.loads(sys.stdin.read())); print(json.dumps(c['BrokerNodeGroupInfo']['ConnectivityInfo']['PublicAccess']))"
{"Type": "DISABLED"}

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[].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
  --region ap-northeast-1
[
    {
        "ResourceId": "<クラスター ARN>",
        "ComplianceType": "COMPLIANT",
        "ResultRecordedTime": "<評価時刻>"
    }
]

Security Hub finding も PASSED に復帰していることを確認する。

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 経由の接続を優先的に検討する

Amazonアソシエイトリンク