コンテンツにスキップ

EKS.1

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

概要

EKS.1 EKS cluster endpoints should not be publicly accessible は、Amazon EKS クラスターの Kubernetes API サーバエンドポイントがインターネットからパブリックアクセス可能でないことを検証する CSPM コントロール。endpointPublicAccess=true の場合に FAILED となる。

EKS クラスターの Kubernetes コントロールプレーン(API サーバ、etcd 等)自体は AWS 管理領域の EKS サービス VPC で稼働するマネージドコンポーネントで、ユーザーから直接操作できない。一方、この API サーバへの通信経路はユーザーが制御する。kubectl などの API クライアントは、以下の 2 つの経路のいずれか、または両方を使って API サーバにアクセスする。

  • インターネット経由の経路(パブリックエンドポイント): endpointPublicAccess=true で有効化。API サーバのパブリック DNS 名がインターネット上に公開され、インターネット経由で到達できる。デフォルトで有効。デフォルトでは publicAccessCidrs=["0.0.0.0/0"](無制限)で、送信元 IP を CIDR で制限できる
  • VPC 内経由の経路(プライベートエンドポイント): endpointPrivateAccess=true で有効化。VPC 内からプライベート IP アドレスで到達できる。デフォルトで無効

endpointPublicAccessendpointPrivateAccess は独立した設定値で、両方 true にもできる(詳しい挙動は後述の「エンドポイントアクセスの 3 つの組み合わせ」を参照)。

クラスター作成時に、ユーザー指定サブネット内に X-ENI(Cross-account ENI)が自動作成される。これは主にコントロールプレーンとワーカーノード間の内部通信(kubectl exec の中継、kubelet からの情報取得など)に使われるもので、エンドポイント設定の有無に関わらず EKS クラスターの稼働に必要なリソースである。endpointPrivateAccess=true を有効にすると、EKS が Route 53 プライベートホストゾーンを自動作成して VPC に関連付け、API サーバの DNS 名がこの X-ENI のプライベート IP に解決されるため、VPC 内からの API クライアントもこの X-ENI 経由で到達する(このプライベートホストゾーンは EKS が管理しており、ユーザーアカウントの Route 53 リソースには表示されない。Cluster API server endpoint - Amazon EKS より)。

EKS.1 が評価するのは、2 つの経路のうちインターネット経由の経路(endpointPublicAccess)が有効かどうか。publicAccessCidrs の制限有無や VPC 内経路の有効無効によって判定が変わるかは、本検証のパターン 2 / 3 で実測する。

なお、endpointPublicAccess=true でも API サーバへのアクセスは AWS IAM と Kubernetes RBAC の組み合わせで認証・認可される(Cluster API server endpoint - Amazon EKS より)。それでも EKS.1 が FAILED を出すのは、EKS がプライベートエンドポイント化の標準手段を提供しており AWS のベストプラクティスとしても推奨されていること、および API サーバ侵害時の影響範囲がクラスター全体に及ぶため攻撃面を狭めておくことが望ましいこと、による判定と推察する。

EKS のエンドポイント設定と VPC/サブネット構成は独立した概念

EKS.1 が評価するのは EKS クラスターの API サーバエンドポイントの公開設定(endpointPublicAccess / endpointPrivateAccess)であり、VPC やサブネットのネットワーク構成とは独立している。詳細は Cluster API server endpoint - Amazon EKS を参照。

なお、endpointPrivateAccess=true を機能させるには VPC で enableDnsHostnames / enableDnsSupport が有効で、DHCP option set に AmazonProvidedDNS が含まれている必要がある(同公式ドキュメントより)。デフォルト VPC は通常これらを満たしているが、設定変更されている可能性もあるため、ステップ 1 で念のため確認する

エンドポイントアクセスの 3 つの組み合わせ

公式ドキュメント に記載されている 3 つの組み合わせと、EKS.1 の評価:

endpointPublicAccessendpointPrivateAccess動作EKS.1
truefalseデフォルト。インターネットから API サーバに到達可能FAILED
truetrueインターネット + VPC 内の両経路FAILED
falsetrueインターネットから到達不可。VPC/接続ネットワーク経由のみPASSED

本記事で確認すること

本検証では 単一の EKS クラスター を使って以下を段階的に確認する:

#検証観点endpointPublicAccessendpointPrivateAccesspublicAccessCidrs期待
1デフォルト設定で作成した EKS クラスターtruefalse["0.0.0.0/0"]FAILED
2publicAccessCidrs を特定 IP に制限truefalse["<検証者のグローバル IP>/32"]実測確認
3update-cluster-config で両方 truepublicAccessCidrs の継承挙動も実測対象)truetrue継承想定実測確認
4update-cluster-config でプライベート化falsetrue適用外PASSED

パターン 2 では publicAccessCidrs で送信元 IP を特定 IP に制限しても endpointPublicAccess=true である以上 FAILED になるか、CIDR 制限により PASSED 扱いになるかを実測で確認する。パターン 3 では endpointPrivateAccess=true を追加した両方有効の状態で、EKS.1 の判定が endpointPublicAccess のみで決まるか実測で確認する。

結果

EKS.1 は endpointPublicAccess=true であれば、publicAccessCidrsendpointPrivateAccess の値に関わらず FAILED となる。endpointPublicAccess=false にすると PASSED に復帰する。

#endpointPublicAccessendpointPrivateAccesspublicAccessCidrsEKS.1
1truefalse["0.0.0.0/0"](デフォルト)FAILED
2truefalse["<特定 IP>/32"](CIDR 制限)FAILED
3truetrue継承(前のステップの値が保持)FAILED
4falsetrue適用外だが値は保持されるPASSED

判明したこと(AWS 公式ドキュメントには明示されていない仕様):

  • EKS.1(Config ルール eks-endpoint-no-public-access)は endpointPublicAccess フラグのみを評価し、publicAccessCidrs の値は評価対象としない
  • update-cluster-configpublicAccessCidrs を指定しない場合、既存値が継承される
  • endpointPublicAccess=false にしても publicAccessCidrs の値は消去されず保持される(適用外になるだけ)。後で endpointPublicAccess=true に戻すと、保持されていた古い publicAccessCidrs が再適用されるため、再公開時は意図した CIDR を明示的に指定し直す必要がある

検証環境

検証環境の構成図

フローチャート

    flowchart LR
    s1[1. 前提確認] --> s2[2. IAM ロール作成]
    s2 --> s3[3. EKS クラスター作成]
    s3 --> s4[4. ACTIVE 待ち]
    s4 --> s5[5. FAILED 確認<br/>パターン 1]
    s5 --> s6[6. CIDR 制限]
    s6 --> s7[7. 更新完了待ち]
    s7 --> s8[8. 評価確認<br/>パターン 2]
    s8 --> s9[9. 両方有効化]
    s9 --> s10[10. 更新完了待ち]
    s10 --> s11[11. FAILED 確認<br/>パターン 3]
    s11 --> s12[12. プライベート化]
    s12 --> s13[13. 更新完了待ち]
    s13 --> s14[14. PASSED 確認<br/>パターン 4]
    s14 --> s15[15. クリーンアップ]
  

実測所要時間

ステップ操作所要時間
3-4EKS クラスター作成 → ACTIVE約 6 分
6-7publicAccessCidrs 変更の更新完了約 1 分 20 秒
9-10endpointPrivateAccess=true 追加の更新完了約 4 分 20 秒
12-13endpointPublicAccess=false 変更の更新完了約 1 分 10 秒
15クラスター削除完了約 2 分 20 秒
合計検証全体(API 操作と待機時間)約 50 分

上記は API 操作と待機時間の累積。コマンド入力、結果記録、想定外時のトラブルシュートを含めた実作業時間は別に見積もる必要がある。本検証では「Configuration Item 更新ラグへの対処」が不要だったため、MSK.4 のような長時間の乖離は発生しなかった。

endpointPrivateAccess=true への変更時のみ、X-ENI 作成や Route 53 プライベートホストゾーン関連付けが走るため他の設定変更より時間がかかる。

1. 前提確認

AWS_PROFILE=Workload をエクスポートしてから以下を実行する。

デフォルト VPC の取得

VPC_ID=$(aws ec2 describe-vpcs \
  --filters "Name=is-default,Values=true" \
  --query 'Vpcs[0].VpcId' --output text \
  --region ap-northeast-1)
echo "VPC_ID=$VPC_ID"
VPC_ID=<デフォルト VPC ID>

VPC の DNS 設定確認(endpointPrivateAccess=true の前提)

aws ec2 describe-vpc-attribute \
  --vpc-id $VPC_ID --attribute enableDnsHostnames \
  --query 'EnableDnsHostnames.Value' --output text \
  --region ap-northeast-1
True
aws ec2 describe-vpc-attribute \
  --vpc-id $VPC_ID --attribute enableDnsSupport \
  --query 'EnableDnsSupport.Value' --output text \
  --region ap-northeast-1
True

DHCP option set の DNS 設定確認

DHCP_ID=$(aws ec2 describe-vpcs \
  --vpc-ids $VPC_ID \
  --query 'Vpcs[0].DhcpOptionsId' --output text \
  --region ap-northeast-1)

aws ec2 describe-dhcp-options \
  --dhcp-options-ids $DHCP_ID \
  --query 'DhcpOptions[0].DhcpConfigurations[?Key==`domain-name-servers`].Values[].Value' \
  --region ap-northeast-1
[
    "AmazonProvidedDNS"
]

True/True/AmazonProvidedDNS が確認できれば、endpointPrivateAccess=true が機能する前提を満たしている。

サブネット取得(2 AZ 以上必要)

EKS クラスター作成には最低 2 AZ にまたがるサブネットが必要。デフォルト VPC の 3 AZ 分のサブネットを取得する。

aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'Subnets[].{SubnetId:SubnetId,AZ:AvailabilityZone,Cidr:CidrBlock}' \
  --region ap-northeast-1
[
    {"SubnetId": "<サブネット 1a ID>", "AZ": "ap-northeast-1a", "Cidr": "172.31.32.0/20"},
    {"SubnetId": "<サブネット 1d ID>", "AZ": "ap-northeast-1d", "Cidr": "172.31.16.0/20"},
    {"SubnetId": "<サブネット 1c ID>", "AZ": "ap-northeast-1c", "Cidr": "172.31.0.0/20"}
]

サブネット種別(パブリック / プライベート)の確認

本検証ではデフォルト VPC のサブネットを使用する。EKS.1 の判定はサブネット種別に影響されないが、読者の検証環境でサブネット構成を把握できるよう確認する。デフォルト VPC では明示的なルートテーブル関連付けを持たないサブネットにメインルートテーブルが適用される。

aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=$VPC_ID" "Name=association.main,Values=true" \
  --query 'RouteTables[].{RouteTableId:RouteTableId,Routes:Routes[].{Dest:DestinationCidrBlock,Gateway:GatewayId}}' \
  --region ap-northeast-1
[
    {
        "RouteTableId": "<ルートテーブル ID>",
        "Routes": [
            {"Dest": "172.31.0.0/16", "Gateway": "local"},
            {"Dest": "0.0.0.0/0", "Gateway": "<IGW ID>"}
        ]
    }
]

0.0.0.0/0 → igw-... のルートがあることから、これらのサブネットはインターネット到達可能なパブリックサブネットである。繰り返しになるが、EKS.1 の判定はこれに影響されない。

Config ルール名の取得

RULE_NAME=$(aws configservice describe-config-rules \
  --query "ConfigRules[?Source.SourceIdentifier=='EKS_ENDPOINT_NO_PUBLIC_ACCESS'].ConfigRuleName" \
  --output text --region ap-northeast-1)
echo "RULE_NAME=$RULE_NAME"
RULE_NAME=securityhub-eks-endpoint-no-public-access-<ハッシュ>

既存リソースの確認(冪等性確保)

ステップ 2 で作成する IAM ロール、ステップ 3 で作成する EKS クラスターが既に存在すると後続が失敗するため、事前に確認する。

aws iam get-role --role-name eks-1-test-cluster-role 2>&1 | head -3
An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name eks-1-test-cluster-role cannot be found.
aws eks describe-cluster --name eks-1-test --region ap-northeast-1 2>&1 | head -3
An error occurred (ResourceNotFoundException) when calling the DescribeCluster operation: No cluster found for name: eks-1-test.

どちらも NoSuchEntity / ResourceNotFoundException が返れば、前回検証のクリーンアップが完了しており問題なし。残存している場合は先にクリーンアップ(ステップ 15 相当)を実施する。

2. EKS Cluster 用 IAM ロール作成

EKS クラスター作成には AmazonEKSClusterPolicy をアタッチした IAM ロールが必要。検証用に最小構成のロールを作成する。

Trust policy ファイル作成

cat > /tmp/eks-cluster-trust-policy.json <<'EOF'
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {"Service": "eks.amazonaws.com"},
    "Action": "sts:AssumeRole"
  }]
}
EOF

ロール作成とポリシーアタッチ

aws iam create-role \
  --role-name eks-1-test-cluster-role \
  --assume-role-policy-document file:///tmp/eks-cluster-trust-policy.json \
  --query 'Role.{Name:RoleName,Arn:Arn}'
{
    "Name": "eks-1-test-cluster-role",
    "Arn": "arn:aws:iam::<アカウント ID>:role/eks-1-test-cluster-role"
}
aws iam attach-role-policy \
  --role-name eks-1-test-cluster-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy
(出力なし)

アタッチ確認

aws iam list-attached-role-policies \
  --role-name eks-1-test-cluster-role \
  --query 'AttachedPolicies[].PolicyArn'
[
    "arn:aws:iam::aws:policy/AmazonEKSClusterPolicy"
]
ROLE_ARN=$(aws iam get-role \
  --role-name eks-1-test-cluster-role \
  --query 'Role.Arn' --output text)
echo "ROLE_ARN=$ROLE_ARN"
ROLE_ARN=arn:aws:iam::<アカウント ID>:role/eks-1-test-cluster-role

3. デフォルト設定で EKS クラスター作成

resourcesVpcConfig でエンドポイント設定を明示しない(= デフォルト endpointPublicAccess=true, endpointPrivateAccess=false)状態でクラスターを作成する。EKS は最低 2 サブネット(異なる AZ)が必須なので、ステップ 1 で取得した 3 つのうち 2 つを指定する。本検証ではエンドポイント設定のみが評価対象であり、冗長性は本質ではないため 2 つで十分。ステップ 1 で確認した通りデフォルト VPC のサブネットはパブリックサブネットだが、EKS.1 の判定はサブネット種別に影響されない(概要の callout 参照)。

SUBNET_1A="<サブネット 1a ID>"
SUBNET_1C="<サブネット 1c ID>"

aws eks create-cluster \
  --name eks-1-test \
  --role-arn $ROLE_ARN \
  --resources-vpc-config "subnetIds=$SUBNET_1A,$SUBNET_1C" \
  --region ap-northeast-1
{
    "cluster": {
        "name": "eks-1-test",
        "arn": "arn:aws:eks:ap-northeast-1:<アカウント ID>:cluster/eks-1-test",
        "createdAt": "<日時>",
        "version": "1.35",
        "roleArn": "arn:aws:iam::<アカウント ID>:role/eks-1-test-cluster-role",
        "resourcesVpcConfig": {
            "subnetIds": ["<サブネット 1a ID>", "<サブネット 1c ID>"],
            "securityGroupIds": [],
            "vpcId": "<VPC ID>",
            "endpointPublicAccess": true,
            "endpointPrivateAccess": false,
            "publicAccessCidrs": ["0.0.0.0/0"]
        },
        "status": "CREATING",
        "platformVersion": "eks.11",
        "accessConfig": {
            "bootstrapClusterCreatorAdminPermissions": true,
            "authenticationMode": "CONFIG_MAP"
        }
    }
}

endpointPublicAccess: trueendpointPrivateAccess: falsepublicAccessCidrs: ["0.0.0.0/0"] がパターン 1(デフォルト)の設定として確認できる。

4. ACTIVE 待ち

aws eks wait cluster-active \
  --name eks-1-test \
  --region ap-northeast-1 || {
    echo "wait がタイムアウト(20 分)。describe-cluster で実際の状態を確認する"
    aws eks describe-cluster --name eks-1-test --query 'cluster.status' --output text --region ap-northeast-1
  }

aws eks describe-cluster \
  --name eks-1-test \
  --query 'cluster.{Status:status,PublicAccess:resourcesVpcConfig.endpointPublicAccess,PrivateAccess:resourcesVpcConfig.endpointPrivateAccess,PublicAccessCidrs:resourcesVpcConfig.publicAccessCidrs}' \
  --region ap-northeast-1
{
    "Status": "ACTIVE",
    "PublicAccess": true,
    "PrivateAccess": false,
    "PublicAccessCidrs": ["0.0.0.0/0"]
}

クラスター ARN の取得

以降のステップで使用するため、クラスター ARN を変数に取得しておく。

CLUSTER_ARN=$(aws eks describe-cluster \
  --name eks-1-test \
  --query 'cluster.arn' --output text \
  --region ap-northeast-1)
echo "CLUSTER_ARN=$CLUSTER_ARN"
CLUSTER_ARN=arn:aws:eks:ap-northeast-1:<アカウント ID>:cluster/eks-1-test

5. FAILED 確認 - パターン 1(デフォルト)

endpointPublicAccess=true, endpointPrivateAccess=false の状態で EKS.1 が 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[].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
  --region ap-northeast-1
[
    {
        "ResourceId": "eks-1-test",
        "ComplianceType": "NON_COMPLIANT",
        "ResultRecordedTime": "<日時>"
    }
]

ComplianceType: NON_COMPLIANT でパターン 1 の想定通り FAILED 相当の評価が出力される。ResourceId は EKS クラスター名(AWS::EKS::Cluster リソースタイプ)。

Security Hub finding も確認する。

sleep 120
aws securityhub get-findings \
  --filters '{
    "ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "EKS.1"}],
    "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:eks:ap-northeast-1:<アカウント ID>:cluster/eks-1-test",
        "UpdatedAt": "<日時>"
    },
    {
        "Status": "PASSED",
        "ResourceId": "AWS::::Account:<アカウント ID>",
        "UpdatedAt": "<日時>"
    }
]

EKS クラスター(arn:aws:eks:...:cluster/eks-1-test)のレコードが FAILED になっている。もう 1 件の AWS::::Account:<アカウント ID> は EKS クラスター非存在時の評価用のアカウントレベルのレコードで、本検証の対象ではない。

6. update-cluster-config で publicAccessCidrs を制限

endpointPublicAccess=true はそのままに、publicAccessCidrs を特定 IP (/32) に制限する。検証者のグローバル IP を取得してから update-cluster-config を実行する。

MY_IP=$(curl -s https://checkip.amazonaws.com | tr -d '[:space:]')
if [[ ! "$MY_IP" =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then
  echo "MY_IP 取得失敗または形式不正: '$MY_IP'"; exit 1
fi
echo "MY_IP=$MY_IP"
MY_IP=<検証者のグローバル IP>
UPDATE_ID=$(aws eks update-cluster-config \
  --name eks-1-test \
  --resources-vpc-config "endpointPublicAccess=true,endpointPrivateAccess=false,publicAccessCidrs=${MY_IP}/32" \
  --query 'update.id' --output text \
  --region ap-northeast-1)
echo "UPDATE_ID=$UPDATE_ID"
UPDATE_ID=<update ID>

7. 更新完了待ち

describe-updatestatusSuccessful になるまでポーリングする。UPDATING への遷移は eventually consistent なので、aws eks wait cluster-active では捕捉できない場合がある(公式ドキュメント Update an existing cluster to a new Kubernetes version より)。

while true; do
  STATUS=$(aws eks describe-update \
    --name eks-1-test \
    --update-id $UPDATE_ID \
    --query 'update.status' --output text \
    --region ap-northeast-1)
  echo "$(date '+%H:%M:%S') update status: $STATUS"
  case "$STATUS" in
    Successful) break ;;
    Failed|Cancelled) echo "更新に失敗しました"; break ;;
    *) sleep 30 ;;
  esac
done
<HH:MM:SS> update status: InProgress
...
<HH:MM:SS> update status: Successful

更新完了後、クラスター側の設定を最終確認する。

aws eks describe-cluster \
  --name eks-1-test \
  --query 'cluster.{Status:status,PublicAccess:resourcesVpcConfig.endpointPublicAccess,PrivateAccess:resourcesVpcConfig.endpointPrivateAccess,PublicAccessCidrs:resourcesVpcConfig.publicAccessCidrs}' \
  --region ap-northeast-1
{
    "Status": "ACTIVE",
    "PublicAccess": true,
    "PrivateAccess": false,
    "PublicAccessCidrs": ["<検証者のグローバル IP>/32"]
}

8. 評価確認 - パターン 2(CIDR 制限)

endpointPublicAccess=true のまま publicAccessCidrs/32 に絞った状態で EKS.1 がどう判定されるか確認する。

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": "eks-1-test",
        "ComplianceType": "NON_COMPLIANT",
        "ResultRecordedTime": "<日時>"
    }
]

実測結果: publicAccessCidrs/32 に制限しても NON_COMPLIANT のまま。EKS.1 の判定ロジックは endpointPublicAccess フラグのみを見ており、publicAccessCidrs の値を評価対象としない。

評価結果が古い状態のまま返る場合の対処(MSK.4 検証時に同様の事象を発見)

Config の Configuration Item がクラスター更新を自動キャプチャしない場合、評価が古い状態に基づいた結果を返すことがある。その場合はタグを変更して Configuration Item の再キャプチャを誘発する。

aws eks tag-resource \
  --resource-arn $CLUSTER_ARN \
  --tags VerificationTrigger=EKS1-Step8 \
  --region ap-northeast-1
(出力なし)
sleep 180
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

本検証ではこの対処は不要だった(最初の start-config-rules-evaluation でキャッシュされていない最新状態の評価結果が返ったため)。

Security Hub finding も確認する。

sleep 120
aws securityhub get-findings \
  --filters '{
    "ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "EKS.1"}],
    "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:eks:ap-northeast-1:<アカウント ID>:cluster/eks-1-test",
        "UpdatedAt": "<日時>"
    },
    {
        "Status": "PASSED",
        "ResourceId": "AWS::::Account:<アカウント ID>",
        "UpdatedAt": "<日時>"
    }
]

EKS クラスターの finding も Security Hub では FAILED のまま。UpdatedAt はステップ 5 時点から更新されており、評価が再実行されたことを確認できる。

9. update-cluster-config で両方有効化

endpointPrivateAccess=true を追加する(endpointPublicAccess=true はそのまま。publicAccessCidrs を指定していないため、前のステップで設定した特定 IP のまま継承される想定。実際に継承されるかはステップ 10 の最終確認で実測する)。update オブジェクトから id を取得し、後続ステップで describe-update による完了確認に使用する。

UPDATE_ID=$(aws eks update-cluster-config \
  --name eks-1-test \
  --resources-vpc-config 'endpointPublicAccess=true,endpointPrivateAccess=true' \
  --query 'update.id' --output text \
  --region ap-northeast-1)
echo "UPDATE_ID=$UPDATE_ID"
UPDATE_ID=<update ID>

10. 更新完了待ち

EKS のクラスター更新は非同期で、UPDATING への遷移は eventually consistent である(公式ドキュメント Update an existing cluster to a new Kubernetes version より)。update-cluster-config 直後にクラスター status を確認すると ACTIVE のまま返ることがあるため、aws eks wait cluster-active では更新完了を確実に捉えられない場合がある。ここでは describe-updatestatusSuccessful になるまでポーリングする。

while true; do
  STATUS=$(aws eks describe-update \
    --name eks-1-test \
    --update-id $UPDATE_ID \
    --query 'update.status' --output text \
    --region ap-northeast-1)
  echo "$(date '+%H:%M:%S') update status: $STATUS"
  case "$STATUS" in
    Successful) break ;;
    Failed|Cancelled) echo "更新に失敗しました"; break ;;
    *) sleep 30 ;;
  esac
done
<HH:MM:SS> update status: InProgress
...
<HH:MM:SS> update status: Successful

更新完了後、クラスター側の設定を最終確認する。publicAccessCidrs をコマンドで指定していないため、前のステップで設定した値が継承されているかもここで確認する。

aws eks describe-cluster \
  --name eks-1-test \
  --query 'cluster.{Status:status,PublicAccess:resourcesVpcConfig.endpointPublicAccess,PrivateAccess:resourcesVpcConfig.endpointPrivateAccess,PublicAccessCidrs:resourcesVpcConfig.publicAccessCidrs}' \
  --region ap-northeast-1
{
    "Status": "ACTIVE",
    "PublicAccess": true,
    "PrivateAccess": true,
    "PublicAccessCidrs": ["<検証者のグローバル IP>/32"]
}

PublicAccessCidrs がステップ 6 で設定した特定 IP のまま保持されている。update-cluster-configpublicAccessCidrs を明示せず endpointPublicAccessendpointPrivateAccess だけを指定したため、既存値が継承されたことを実測で確認できた。

11. FAILED 確認 - パターン 3(両方有効)

endpointPublicAccess=true, endpointPrivateAccess=true の状態で EKS.1 の判定が endpointPrivateAccess の値に影響されるかを確認する(endpointPublicAccess=true である以上 FAILED と想定されるが、実測で確認する)。

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": "eks-1-test",
        "ComplianceType": "NON_COMPLIANT",
        "ResultRecordedTime": "<日時>"
    }
]

実測結果: endpointPrivateAccess=true を追加しても NON_COMPLIANT のまま。EKS.1 の判定ロジックは endpointPrivateAccess の値に関わらず endpointPublicAccess=true なら FAILED となる。

評価結果が古い状態のまま返る場合: ステップ 8 の callout を参照。タグ変更で Configuration Item の再キャプチャを誘発する(タグ値は VerificationTrigger=EKS1-Step11 のように変えて区別する)。本検証ではこの対処は不要だった。

Security Hub finding も確認する。

sleep 120
aws securityhub get-findings \
  --filters '{
    "ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "EKS.1"}],
    "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:eks:ap-northeast-1:<アカウント ID>:cluster/eks-1-test",
        "UpdatedAt": "<日時>"
    },
    {
        "Status": "PASSED",
        "ResourceId": "AWS::::Account:<アカウント ID>",
        "UpdatedAt": "<日時>"
    }
]

EKS クラスターの finding は FAILED のまま。UpdatedAt はパターン 2 時点から更新されており、再評価が実行されたことを確認できる。

12. update-cluster-config でプライベート化

endpointPublicAccess=false, endpointPrivateAccess=true に変更する。

UPDATE_ID=$(aws eks update-cluster-config \
  --name eks-1-test \
  --resources-vpc-config 'endpointPublicAccess=false,endpointPrivateAccess=true' \
  --query 'update.id' --output text \
  --region ap-northeast-1)
echo "UPDATE_ID=$UPDATE_ID"
UPDATE_ID=<update ID>

13. 更新完了待ち

ステップ 7 と同じく describe-updatestatusSuccessful になるまでポーリングする。

while true; do
  STATUS=$(aws eks describe-update \
    --name eks-1-test \
    --update-id $UPDATE_ID \
    --query 'update.status' --output text \
    --region ap-northeast-1)
  echo "$(date '+%H:%M:%S') update status: $STATUS"
  case "$STATUS" in
    Successful) break ;;
    Failed|Cancelled) echo "更新に失敗しました"; break ;;
    *) sleep 30 ;;
  esac
done
<HH:MM:SS> update status: InProgress
...
<HH:MM:SS> update status: Successful

更新完了後、クラスター側の設定を最終確認する。

aws eks describe-cluster \
  --name eks-1-test \
  --query 'cluster.{Status:status,PublicAccess:resourcesVpcConfig.endpointPublicAccess,PrivateAccess:resourcesVpcConfig.endpointPrivateAccess,PublicAccessCidrs:resourcesVpcConfig.publicAccessCidrs}' \
  --region ap-northeast-1
{
    "Status": "ACTIVE",
    "PublicAccess": false,
    "PrivateAccess": true,
    "PublicAccessCidrs": ["<検証者のグローバル IP>/32"]
}

endpointPublicAccess=false でも publicAccessCidrs の値は消去されず保持されている。publicAccessCidrs はパブリック有効時の送信元 IP 制限を定義するもので、パブリック無効時は単に適用されないだけで値自体は保持される、という挙動が実測で確認できた。この挙動は、後で endpointPublicAccess=true に戻すと保持されていた古い publicAccessCidrs が再適用されるという運用上の含意がある。再公開時は意図した CIDR を明示的に指定し直すことが安全。

14. PASSED 復帰確認 - パターン 4(プライベート化)

endpointPublicAccess=false, endpointPrivateAccess=true の状態で EKS.1 が 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[].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,ComplianceType:ComplianceType,ResultRecordedTime:ResultRecordedTime}' \
  --region ap-northeast-1
[
    {
        "ResourceId": "eks-1-test",
        "ComplianceType": "COMPLIANT",
        "ResultRecordedTime": "<日時>"
    }
]

ComplianceType: COMPLIANT で PASSED 相当の評価に切り替わったことを確認できる。

NON_COMPLIANT のまま返る場合: ステップ 8 の callout を参照。タグ変更で Configuration Item の再キャプチャを誘発する(タグ値は VerificationTrigger=EKS1-Step14 のように変えて区別する)。本検証ではこの対処は不要だった。

Security Hub finding も確認する。

sleep 120
aws securityhub get-findings \
  --filters '{
    "ComplianceSecurityControlId": [{"Comparison": "EQUALS", "Value": "EKS.1"}],
    "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:eks:ap-northeast-1:<アカウント ID>:cluster/eks-1-test",
        "UpdatedAt": "<日時>"
    },
    {
        "Status": "PASSED",
        "ResourceId": "AWS::::Account:<アカウント ID>",
        "UpdatedAt": "<日時>"
    }
]

EKS クラスターの finding が FAILEDPASSED に遷移した。UpdatedAt も更新されており、評価結果の反映を確認できる。

15. クリーンアップ

EKS クラスター削除

aws eks delete-cluster \
  --name eks-1-test \
  --region ap-northeast-1
{
    "cluster": {
        "name": "eks-1-test",
        "status": "DELETING",
        "arn": "arn:aws:eks:ap-northeast-1:<アカウント ID>:cluster/eks-1-test"
    }
}
aws eks wait cluster-deleted \
  --name eks-1-test \
  --region ap-northeast-1
(出力なし)

IAM ロール削除

aws iam detach-role-policy \
  --role-name eks-1-test-cluster-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy

sleep 5

aws iam delete-role \
  --role-name eks-1-test-cluster-role
(出力なし)

IAM の伝播遅延によりまれに「policy がまだアタッチされている」エラーが出ることがあるため、detachdelete の間に数秒間隔を入れている。

削除確認

aws eks list-clusters \
  --query "clusters[?@=='eks-1-test']" \
  --region ap-northeast-1
[]
aws iam get-role \
  --role-name eks-1-test-cluster-role 2>&1 | head -3
An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name eks-1-test-cluster-role cannot be found.

Amazonアソシエイトリンク