EKS.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 アドレスで到達できる。デフォルトで無効
endpointPublicAccess と endpointPrivateAccess は独立した設定値で、両方 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 の評価:
endpointPublicAccess | endpointPrivateAccess | 動作 | EKS.1 |
|---|---|---|---|
true | false | デフォルト。インターネットから API サーバに到達可能 | FAILED |
true | true | インターネット + VPC 内の両経路 | FAILED |
false | true | インターネットから到達不可。VPC/接続ネットワーク経由のみ | PASSED |
本記事で確認すること
本検証では 単一の EKS クラスター を使って以下を段階的に確認する:
| # | 検証観点 | endpointPublicAccess | endpointPrivateAccess | publicAccessCidrs | 期待 |
|---|---|---|---|---|---|
| 1 | デフォルト設定で作成した EKS クラスター | true | false | ["0.0.0.0/0"] | FAILED |
| 2 | publicAccessCidrs を特定 IP に制限 | true | false | ["<検証者のグローバル IP>/32"] | 実測確認 |
| 3 | update-cluster-config で両方 true(publicAccessCidrs の継承挙動も実測対象) | true | true | 継承想定 | 実測確認 |
| 4 | update-cluster-config でプライベート化 | false | true | 適用外 | PASSED |
パターン 2 では publicAccessCidrs で送信元 IP を特定 IP に制限しても endpointPublicAccess=true である以上 FAILED になるか、CIDR 制限により PASSED 扱いになるかを実測で確認する。パターン 3 では endpointPrivateAccess=true を追加した両方有効の状態で、EKS.1 の判定が endpointPublicAccess のみで決まるか実測で確認する。
結果
EKS.1 は endpointPublicAccess=true であれば、publicAccessCidrs や endpointPrivateAccess の値に関わらず FAILED となる。endpointPublicAccess=false にすると PASSED に復帰する。
| # | endpointPublicAccess | endpointPrivateAccess | publicAccessCidrs | EKS.1 |
|---|---|---|---|---|
| 1 | true | false | ["0.0.0.0/0"](デフォルト) | FAILED |
| 2 | true | false | ["<特定 IP>/32"](CIDR 制限) | FAILED |
| 3 | true | true | 継承(前のステップの値が保持) | FAILED |
| 4 | false | true | 適用外だが値は保持される | PASSED |
判明したこと(AWS 公式ドキュメントには明示されていない仕様):
- EKS.1(Config ルール
eks-endpoint-no-public-access)はendpointPublicAccessフラグのみを評価し、publicAccessCidrsの値は評価対象としない update-cluster-configでpublicAccessCidrsを指定しない場合、既存値が継承される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-4 | EKS クラスター作成 → ACTIVE | 約 6 分 |
| 6-7 | publicAccessCidrs 変更の更新完了 | 約 1 分 20 秒 |
| 9-10 | endpointPrivateAccess=true 追加の更新完了 | 約 4 分 20 秒 |
| 12-13 | endpointPublicAccess=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-1Trueaws ec2 describe-vpc-attribute \
--vpc-id $VPC_ID --attribute enableDnsSupport \
--query 'EnableDnsSupport.Value' --output text \
--region ap-northeast-1TrueDHCP 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 -3An 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 -3An 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-role3. デフォルト設定で 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: true、endpointPrivateAccess: false、publicAccessCidrs: ["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-test5. 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-update の status が Successful になるまでポーリングする。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-update の status が Successful になるまでポーリングする。
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-config で publicAccessCidrs を明示せず endpointPublicAccess と endpointPrivateAccess だけを指定したため、既存値が継承されたことを実測で確認できた。
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 となる。
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-update の status が Successful になるまでポーリングする。
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 が FAILED → PASSED に遷移した。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 がまだアタッチされている」エラーが出ることがあるため、detach と delete の間に数秒間隔を入れている。
削除確認
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 -3An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name eks-1-test-cluster-role cannot be found.