EKS Public Endpoint
カスタム SCP の説明
背景: EKS クラスターエンドポイントのパブリックアクセス
EKS クラスターは、Kubernetes API サーバへの 2 つの経路を持つ。
| 設定 | 説明 |
|---|---|
endpointPublicAccess | インターネット経由(パブリック)のアクセス経路の有効/無効 |
endpointPrivateAccess | VPC 内(プライベート)のアクセス経路の有効/無効 |
クラスター作成時のデフォルトは endpointPublicAccess=true / endpointPrivateAccess=false で、何も設定しないとパブリック経路のみ有効な状態になる。Security Hub CSPM の EKS.1 はこの設定をチェックし、endpointPublicAccess=true の場合に FAILED となる。
詳細は EKS.1 を参照。
このカスタム SCP の役割
EKS の eks:endpointPublicAccess 条件キー(Bool 型)は 2026 年 4 月に追加された比較的新しい機能で、eks:CreateCluster / eks:UpdateClusterConfig API でリクエストパラメータの値を SCP で評価できる。これにより、endpointPublicAccess=true を指定する操作のみを Deny できる。
Control Tower の標準予防コントロールには EKS のパブリックアクセスを直接制御するものが存在しない。代わりに VPC BPA(宣言型ポリシー) によるネットワーク層での実害ブロックが選択肢となるが、EKS の API サーバエンドポイントは VPC BPA の対象外(EKS が管理するパブリック DNS で AWS バックボーン経由のアクセスとなるため)。よって本カスタム SCP が予防コントロールとしての主軸となる。
適用順序の注意
EKS クラスターは作成時にパブリック化可能なため、SSM.7 のような「事前設定 → SCP 適用」の順序制約はない。SCP を先に適用しても問題ない。
ただし、本 SCP は予防コントロールであり、SCP 適用前から存在するパブリック EKS クラスターは endpointPublicAccess=false に強制されない。SCP 適用前に Security Hub の EKS.1 finding で棚卸しし、既存の違反クラスターを修正する運用フローを推奨する。
endpointPublicAccess=true のみを Deny する。endpointPublicAccess=false への変更は通過するが、endpointPrivateAccess=false のままだと Kubernetes API への経路が完全に途絶える。プライベート化する際は endpointPrivateAccess=true も同時に設定すること。SCP の内容
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyEksPublicEndpoint",
"Effect": "Deny",
"Action": [
"eks:CreateCluster",
"eks:UpdateClusterConfig"
],
"Resource": "*",
"Condition": {
"Bool": {
"eks:endpointPublicAccess": "true"
}
}
}
]
}Condition で eks:endpointPublicAccess: true の場合のみ Deny するため、endpointPublicAccess=false を指定する操作(または endpointPublicAccess を指定しない作成・変更操作で暗黙のデフォルトが false 解決されるケース)は引き続き許可される。
対象 API について
| API | 役割 |
|---|---|
eks:CreateCluster | クラスター新規作成時のパブリックアクセス有効化を阻止 |
eks:UpdateClusterConfig | 既存クラスターのパブリックアクセス有効化変更を阻止 |
UpdateClusterConfig は --resources-vpc-config endpointPublicAccess=true のような形でエンドポイント設定を変更する API。それ以外の用途(ロギング設定、認証モードなど)でも使われるが、eks:endpointPublicAccess: true 条件にマッチしない呼び出しは Deny されない。
検証環境
本記事のコマンドは、--profile 指定がない場合は Workload アカウントで実行する。Master アカウントでの操作には --profile Master を指定する。事前に export AWS_PROFILE=Workload でデフォルトを設定しておくと便利。
- 検証アカウント: Workload(
AWS_PROFILE=Workload) - ポリシー操作アカウント: Master(
AWS_PROFILE=Master) - リージョン: ap-northeast-1
- 対象 OU:
<OU ID>
検証の流れ
flowchart LR
s1[1. EKS クラスター作成<br/>endpointPublicAccess=true] --> s2[2. SCP 適用前のベースライン確認<br/>EKS.1 FAILED]
s2 --> s3[3. プライベート化<br/>EKS.1 PASSED 復帰]
s3 --> s4[4. カスタム SCP 作成]
s4 --> s5[5. OU にアタッチ]
s5 --> s6[6. パブリック化が Deny されることを確認]
s6 --> s7[7. プライベート維持の操作は引き続き許可されることを確認]
s7 --> s8[8. SCP デタッチ]
s8 --> s9[9. パブリック化が再び成功することを確認]
s9 --> s10[10. クリーンアップ]
結果
| 検証項目 | 結果 |
|---|---|
ステップ 6.1: update-cluster-config で endpointPublicAccess=true 明示時の Deny | ✓ AccessDeniedException |
ステップ 6.2: create-cluster で endpointPublicAccess=true 明示時の Deny | ✓ AccessDeniedException(explicit deny in a service control policy) |
ステップ 6.3: create-cluster で endpointPublicAccess 未指定時の Deny(暗黙のデフォルト) | ✓ AccessDeniedException(暗黙のデフォルト true を捕捉) |
ステップ 6.4: update-cluster-config でエンドポイント設定以外の変更は通過 | ✓ InProgress(LoggingUpdate) |
| ステップ 7: プライベート維持の操作は許可 | ✓(InvalidParameterException / CREATING) |
| ステップ 9: SCP デタッチ後にパブリック化が再び成功 | ✓ Successful、PublicAccess: true |
1. 事前準備(EKS クラスター作成)
既存リソースのクリーンアップ確認
aws eks list-clusters \
--region ap-northeast-1 \
--profile Workload \
--query 'clusters' \
--output json[]過去検証で残存しているクラスターがあれば、本検証前に削除する。
IAM ロール作成
cat <<'EOF' > /tmp/eks-trust.json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "eks.amazonaws.com"},
"Action": "sts:AssumeRole"
}
]
}
EOF
aws iam create-role \
--role-name eks-scp-test-cluster-role \
--assume-role-policy-document file:///tmp/eks-trust.json \
--query 'Role.Arn' \
--output text \
--profile Workloadarn:aws:iam::<Workload アカウント ID>:role/eks-scp-test-cluster-roleaws iam attach-role-policy \
--role-name eks-scp-test-cluster-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy \
--profile Workload(出力なし)
aws iam list-attached-role-policies \
--role-name eks-scp-test-cluster-role \
--query 'AttachedPolicies[].PolicyName' \
--output text \
--profile WorkloadAmazonEKSClusterPolicyサブネット情報取得
VPC_ID=$(aws ec2 describe-vpcs \
--filters Name=is-default,Values=true \
--query 'Vpcs[0].VpcId' \
--output text \
--region ap-northeast-1 \
--profile Workload)
aws ec2 describe-subnets \
--filters Name=vpc-id,Values=$VPC_ID \
--query 'Subnets[0:2].[SubnetId,AvailabilityZone]' \
--output text \
--region ap-northeast-1 \
--profile Workloadsubnet-<ID-1> ap-northeast-1a
subnet-<ID-2> ap-northeast-1cEKS は異なる AZ の最低 2 サブネットが必要なので、2 つの ID を環境変数に保持する。
SUBNET_1=subnet-<ID-1>
SUBNET_2=subnet-<ID-2>クラスター作成(endpointPublicAccess=true)
ROLE_ARN=$(aws iam get-role \
--role-name eks-scp-test-cluster-role \
--query 'Role.Arn' \
--output text)
aws eks create-cluster \
--name eks-scp-test \
--role-arn $ROLE_ARN \
--resources-vpc-config subnetIds=$SUBNET_1,$SUBNET_2,endpointPublicAccess=true,endpointPrivateAccess=false \
--region ap-northeast-1 \
--profile Workload \
--query 'cluster.{Name:name,Status:status,PublicAccess:resourcesVpcConfig.endpointPublicAccess}' \
--output json{
"Name": "eks-scp-test",
"Status": "CREATING",
"PublicAccess": true
}ACTIVE 待ち
while true; do
STATUS=$(aws eks describe-cluster \
--name eks-scp-test \
--region ap-northeast-1 \
--profile Workload \
--query 'cluster.status' \
--output text)
echo "$(date '+%H:%M:%S') status: $STATUS"
case "$STATUS" in
ACTIVE) break ;;
FAILED) echo "クラスター作成に失敗しました"; break ;;
*) sleep 30 ;;
esac
doneHH:MM:SS status: CREATING
(数分間 CREATING を繰り返す。実測では約 7 分で ACTIVE になる)
HH:MM:SS status: ACTIVEaws eks wait cluster-active は使わない。状態遷移が eventually consistent のため、即座に ACTIVE 扱いで終了するケースがある(公式ドキュメント明記)。describe-cluster でのポーリングが確実。2. SCP 適用前のベースライン確認(EKS.1 FAILED)
Config ルールの手動トリガー
RULE_NAME=$(aws configservice describe-config-rules \
--query "ConfigRules[?contains(ConfigRuleName, 'eks-endpoint-no-public-access')].ConfigRuleName" \
--output text \
--region ap-northeast-1 \
--profile Workload)
aws configservice start-config-rules-evaluation \
--config-rule-names $RULE_NAME \
--region ap-northeast-1 \
--profile Workload(出力なし)
Config 評価結果確認
sleep 30
aws configservice get-compliance-details-by-config-rule \
--config-rule-name $RULE_NAME \
--query 'EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId==`eks-scp-test`].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,Compliance:ComplianceType,RecordedTime:ResultRecordedTime}' \
--region ap-northeast-1 \
--profile Workload[
{
"ResourceId": "eks-scp-test",
"Compliance": "NON_COMPLIANT",
"RecordedTime": "<評価時刻>"
}
]Security Hub finding 確認
aws securityhub get-findings \
--filters '{"ComplianceSecurityControlId":[{"Comparison":"EQUALS","Value":"EKS.1"}],"ResourceId":[{"Comparison":"PREFIX","Value":"arn:aws:eks:ap-northeast-1:<Workload アカウント ID>:cluster/eks-scp-test"}]}' \
--query 'Findings[0].{Status:Compliance.Status,Severity:Severity.Label,UpdatedAt:UpdatedAt}' \
--region ap-northeast-1 \
--profile Workload{
"Status": "FAILED",
"Severity": "HIGH",
"UpdatedAt": "<更新時刻>"
}3. プライベート化(EKS.1 PASSED 復帰)
UPDATE_ID=$(aws eks update-cluster-config \
--name eks-scp-test \
--resources-vpc-config endpointPublicAccess=false,endpointPrivateAccess=true,publicAccessCidrs=0.0.0.0/0 \
--region ap-northeast-1 \
--profile Workload \
--query 'update.id' \
--output text)
echo "UPDATE_ID=$UPDATE_ID"UPDATE_ID=<update-uuid>update-cluster-config で --resources-vpc-config を指定する際、publicAccessCidrs を省略すると既存値が継承される。明示指定が確実。更新完了待ち
while true; do
STATUS=$(aws eks describe-update \
--name eks-scp-test \
--update-id $UPDATE_ID \
--region ap-northeast-1 \
--profile Workload \
--query 'update.status' \
--output text)
echo "$(date '+%H:%M:%S') update status: $STATUS"
case "$STATUS" in
Successful) break ;;
Failed|Cancelled) echo "更新に失敗しました"; break ;;
*) sleep 30 ;;
esac
doneHH:MM:SS update status: InProgress
(数分間 InProgress を繰り返す。実測では約 5 分で Successful になる)
HH:MM:SS update status: SuccessfulEKS.1 PASSED 復帰確認
aws configservice start-config-rules-evaluation \
--config-rule-names $RULE_NAME \
--region ap-northeast-1 \
--profile Workload
sleep 30
aws configservice get-compliance-details-by-config-rule \
--config-rule-name $RULE_NAME \
--query 'EvaluationResults[?EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId==`eks-scp-test`].{ResourceId:EvaluationResultIdentifier.EvaluationResultQualifier.ResourceId,Compliance:ComplianceType}' \
--region ap-northeast-1 \
--profile Workload[
{
"ResourceId": "eks-scp-test",
"Compliance": "COMPLIANT"
}
]4. カスタム SCP 作成
cat <<'EOF' > /tmp/eks-public-endpoint-scp.json
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyEksPublicEndpoint",
"Effect": "Deny",
"Action": [
"eks:CreateCluster",
"eks:UpdateClusterConfig"
],
"Resource": "*",
"Condition": {
"Bool": {
"eks:endpointPublicAccess": "true"
}
}
}
]
}
EOF
POLICY_ID=$(aws organizations create-policy \
--name DenyEksPublicEndpoint \
--description "Deny EKS cluster create/update with endpointPublicAccess=true" \
--type SERVICE_CONTROL_POLICY \
--content file:///tmp/eks-public-endpoint-scp.json \
--profile Master \
--query 'Policy.PolicySummary.Id' \
--output text)
echo "POLICY_ID=$POLICY_ID"POLICY_ID=p-<ポリシー ID>5. OU にアタッチ
aws organizations attach-policy \
--policy-id $POLICY_ID \
--target-id <OU ID> \
--profile Master(出力なし)
アタッチ確認
aws organizations list-policies-for-target \
--target-id <OU ID> \
--filter SERVICE_CONTROL_POLICY \
--profile Master \
--query "Policies[?Id=='$POLICY_ID'].{Name:Name,Type:Type}" \
--output json[
{
"Name": "DenyEksPublicEndpoint",
"Type": "SERVICE_CONTROL_POLICY"
}
]SCP の反映には数秒のラグがあるため、次のステップに進む前に少し待つ。
sleep 306. パブリック化が Deny されることを確認
6.1 update-cluster-config で endpointPublicAccess=true を明示指定した場合
既存クラスターのパブリックアクセスを再度有効化しようとし、SCP で Deny されることを確認する。
aws eks update-cluster-config \
--name eks-scp-test \
--resources-vpc-config endpointPublicAccess=true,endpointPrivateAccess=true,publicAccessCidrs=0.0.0.0/0 \
--region ap-northeast-1 \
--profile Workload 2>&1An error occurred (AccessDeniedException) when calling the UpdateClusterConfig operation: User is not authorized to perform this actionexplicit deny in a service control policy というメッセージが含まれることから、SCP による拒否であることを確認できる。
設定値が変わっていないことを確認
aws eks describe-cluster \
--name eks-scp-test \
--region ap-northeast-1 \
--profile Workload \
--query 'cluster.{Status:status,PublicAccess:resourcesVpcConfig.endpointPublicAccess,PrivateAccess:resourcesVpcConfig.endpointPrivateAccess}'{
"Status": "ACTIVE",
"PublicAccess": false,
"PrivateAccess": true
}設定値は endpointPublicAccess=false のまま維持されている。
6.2 create-cluster で endpointPublicAccess=true を明示指定した場合
新規作成も Deny されることを確認する。
aws eks create-cluster \
--name eks-scp-test-public \
--role-arn $ROLE_ARN \
--resources-vpc-config subnetIds=$SUBNET_1,$SUBNET_2,endpointPublicAccess=true,endpointPrivateAccess=false \
--region ap-northeast-1 \
--profile Workload 2>&1An error occurred (AccessDeniedException) when calling the CreateCluster operation: User: arn:aws:sts::<Workload アカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: eks:CreateCluster on resource: arn:aws:eks:ap-northeast-1:<Workload アカウント ID>:cluster/eks-scp-test-public with an explicit deny in a service control policyCreateCluster のエラーメッセージには with an explicit deny in a service control policy が含まれる。一方、UpdateClusterConfig(ステップ 6.1)のエラーメッセージには含まれず User is not authorized to perform this action のみとなる。CloudTrail 上は両方とも errorCode: AccessDenied で同一であり、どちらも SCP による Deny である。クライアントに返るメッセージの文言が異なるのは EKS の API 層の実装差異。endpointPublicAccess=true を指定した新規作成も Deny される。
6.3 create-cluster で endpointPublicAccess を指定しない場合(暗黙のデフォルト)
EKS の endpointPublicAccess のデフォルト値は true。--resources-vpc-config でこのフラグを指定せずに create-cluster を呼んだ場合、SCP の Bool 条件キーが暗黙のデフォルト値 true を捕捉するかを実測する。
aws eks create-cluster \
--name eks-scp-test-default \
--role-arn $ROLE_ARN \
--resources-vpc-config subnetIds=$SUBNET_1,$SUBNET_2 \
--region ap-northeast-1 \
--profile Workload 2>&1An error occurred (AccessDeniedException) when calling the CreateCluster operation: User: arn:aws:sts::<Workload アカウント ID>:assumed-role/<ロール名>/<セッション名> is not authorized to perform: eks:CreateCluster on resource: arn:aws:eks:ap-northeast-1:<Workload アカウント ID>:cluster/eks-scp-test-default with an explicit deny in a service control policy--endpoint-public-access の指定がなくても、暗黙のデフォルト値が true に解決されるケースでは SCP の Bool 条件キーで Deny される。RDS.2 の検証(rds:PubliclyAccessible)と同じ挙動。
Bool 条件キーが暗黙のデフォルト値も捕捉することが実機確認できた(--publicly-accessible を指定せずに default 名 DBSG で作成した場合に Deny された)。EKS でも同じ挙動が期待されるが、サービスごとに条件キー評価の実装が異なる可能性があるため、実機検証で確定する。6.4 update-cluster-config で endpointPublicAccess を含まない変更は通過することを確認
SCP の Condition は eks:endpointPublicAccess: true のみなので、エンドポイント設定を変更しない UpdateClusterConfig(ロギング設定変更など)は SCP を通過するはずであることを確認する。
aws eks update-cluster-config \
--name eks-scp-test \
--logging '{"clusterLogging":[{"types":["api"],"enabled":true}]}' \
--region ap-northeast-1 \
--profile Workload \
--query 'update.{id:id,status:status,type:type}' \
--output json{
"id": "<update-uuid>",
"status": "InProgress",
"type": "LoggingUpdate"
}endpointPublicAccess を含まない UpdateClusterConfig は SCP を通過する。
7. プライベート維持の操作は引き続き許可されることを確認
endpointPublicAccess=false を維持する操作は SCP を通過することを確認する。
7.1 update-cluster-config で endpointPublicAccess=false を明示指定
UPDATE_ID=$(aws eks update-cluster-config \
--name eks-scp-test \
--resources-vpc-config endpointPublicAccess=false,endpointPrivateAccess=true,publicAccessCidrs=0.0.0.0/0 \
--region ap-northeast-1 \
--profile Workload \
--query 'update.id' \
--output text)
echo "UPDATE_ID=$UPDATE_ID"An error occurred (InvalidParameterException) when calling the UpdateClusterConfig operation: Cluster is already at the desired configuration with endpointPrivateAccess: true , endpointPublicAccess: false, and Public Endpoint Restrictions: [0.0.0.0/0]既に同じ設定のため InvalidParameterException が返る。SCP による AccessDeniedException ではないことから、SCP を通過していることが確認できる。
7.2 create-cluster で endpointPublicAccess=false を明示指定
aws eks create-cluster \
--name eks-scp-test-private \
--role-arn $ROLE_ARN \
--resources-vpc-config subnetIds=$SUBNET_1,$SUBNET_2,endpointPublicAccess=false,endpointPrivateAccess=true \
--region ap-northeast-1 \
--profile Workload \
--query 'cluster.{Name:name,Status:status}' \
--output json{
"Name": "eks-scp-test-private",
"Status": "CREATING"
}endpointPublicAccess=false を指定した新規作成は SCP を通過する。本ステップでは作成完了を待たずに次に進んで良い(クリーンアップで削除する)。
8. SCP デタッチ
aws organizations detach-policy \
--policy-id $POLICY_ID \
--target-id <OU ID> \
--profile Master(出力なし)
デタッチ確認
aws organizations list-policies-for-target \
--target-id <OU ID> \
--filter SERVICE_CONTROL_POLICY \
--profile Master \
--query "Policies[?Id=='$POLICY_ID'].Id" \
--output json[]SCP デタッチの反映にも数秒〜数分のラグがあるため、次のステップに進む前に少し待つ。
sleep 609. パブリック化が再び成功することを確認
UPDATE_ID=$(aws eks update-cluster-config \
--name eks-scp-test \
--resources-vpc-config endpointPublicAccess=true,endpointPrivateAccess=true,publicAccessCidrs=0.0.0.0/0 \
--region ap-northeast-1 \
--profile Workload \
--query 'update.id' \
--output text)
echo "UPDATE_ID=$UPDATE_ID"UPDATE_ID=<update-uuid>SCP デタッチ後、endpointPublicAccess=true への変更が成功する。
設定変更を確認
while true; do
STATUS=$(aws eks describe-update \
--name eks-scp-test \
--update-id $UPDATE_ID \
--region ap-northeast-1 \
--profile Workload \
--query 'update.status' \
--output text)
echo "$(date '+%H:%M:%S') update status: $STATUS"
case "$STATUS" in
Successful) break ;;
Failed|Cancelled) break ;;
*) sleep 30 ;;
esac
done
aws eks describe-cluster \
--name eks-scp-test \
--region ap-northeast-1 \
--profile Workload \
--query 'cluster.{Status:status,PublicAccess:resourcesVpcConfig.endpointPublicAccess}'HH:MM:SS update status: InProgress
...
HH:MM:SS update status: Successful{
"Status": "ACTIVE",
"PublicAccess": true
}10. クリーンアップ
aws organizations delete-policy \
--policy-id $POLICY_ID \
--profile Master(出力なし)
aws eks delete-cluster \
--name eks-scp-test \
--region ap-northeast-1 \
--profile Workload \
--query 'cluster.status' \
--output textDELETING# eks-scp-test-private が作成されている場合は削除
aws eks delete-cluster \
--name eks-scp-test-private \
--region ap-northeast-1 \
--profile Workload \
--query 'cluster.status' \
--output text 2>&1 || echo "クラスター不存在、スキップ"DELETING# 削除完了待ち
for CLUSTER in eks-scp-test eks-scp-test-private; do
while aws eks describe-cluster \
--name $CLUSTER \
--region ap-northeast-1 \
--profile Workload >/dev/null 2>&1; do
echo "$(date '+%H:%M:%S') $CLUSTER deleting..."
sleep 30
done
echo "$CLUSTER deleted"
doneHH:MM:SS eks-scp-test deleting...
...
eks-scp-test deleted
eks-scp-test-private deletedaws iam detach-role-policy \
--role-name eks-scp-test-cluster-role \
--policy-arn arn:aws:iam::aws:policy/AmazonEKSClusterPolicy \
--profile Workload
aws iam delete-role \
--role-name eks-scp-test-cluster-role \
--profile Workload(出力なし)
付録: その他の利用可能な EKS 条件キー
2026 年 4 月の追加で、EKS は本記事で扱った eks:endpointPublicAccess 以外にも以下の Bool 型条件キーを提供している。組織のポリシーに応じて、複数を組み合わせた SCP 設計が可能。以下は Service Authorization Reference に基づく情報であり、本記事では実機検証していない。
| 条件キー | 型 | 想定用途 |
|---|---|---|
eks:endpointPrivateAccess | Bool | プライベートエンドポイントの有効化を強制(endpointPublicAccess=false 単独だと API 経路が完全消滅するリスクがあるため、両方を組み合わせる運用が現実的) |
eks:deletionProtection | Bool | 削除保護の無効化を Deny |
eks:bootstrapClusterCreatorAdminPermissions | Bool | クラスター作成者への Kubernetes admin 自動付与を Deny |
eks:authenticationMode | String | 認証モード(API / API_AND_CONFIG_MAP / CONFIG_MAP)の特定値強制 |
詳細は Service Authorization Reference for EKS を参照。