Restrict Access To Control Plane Endpoint
More Info:
Enable Endpoint Private Access to restrict access to the clusters control plane to only an allowlist of authorized IPs.
Risk Level
Medium
Address
Security
Compliance Standards
- APRA CPS 234 (Australia)
- BSI C5 (Germany)
- Brazil LGPD
- CCPA / CPRA (California)
- CIS Critical Security Controls v8
- CIS EKS
- CMMC 2.0
- CSA Cloud Controls Matrix v4
- DPDPA
- Digital Operational Resilience Act (EU)
- Essential 8
- ISO/IEC 27017
- ISO/IEC 27018
- ISO/IEC 27701
- KSA PDPL
- MAS Technology Risk Management (Singapore)
- MITRE ATT&CK (Cloud)
- NIS2 Directive
- NIST SP 800-171
- NYDFS 23 NYCRR 500
- SWIFT Customer Security Controls Framework
- Sarbanes-Oxley IT General Controls
- UK NCSC Cyber Assessment Framework
Triage and Remediation
- Remediation
Remediation
Manual Steps
-
Gather current cluster endpoint configuration
Run on any machine with AWS CLI access and IAM permissions:aws eks describe-cluster \--region us-east-1 \--name my-eks-cluster \--query 'cluster.resourcesVpcConfig.{endpointPrivateAccess:endpointPrivateAccess,endpointPublicAccess:endpointPublicAccess,publicAccessCidrs:publicAccessCidrs}' \--output tableReplace
us-east-1andmy-eks-clusterwith your actual region and cluster name. -
Decide desired access model with security/network teams
- Preferred: private-only API (no public access) for production clusters.
- If public access is required (e.g., for admins from office/VPN): define a minimal set of trusted IPv4 CIDR blocks, ideally
/32or small ranges, and ensure they do not overlap with reserved ranges. - Confirm your nodes and any automation can reach the private endpoint via VPC/VPN/Direct Connect/Transit Gateway.
-
Configure private endpoint access (if not already enabled)
IfendpointPrivateAccessisfalse, enable it:aws eks update-cluster-config \--region us-east-1 \--name my-eks-cluster \--resources-vpc-config endpointPrivateAccess=trueThis keeps node↔API communication inside the VPC. No pod restart is required, but control‑plane connectivity paths change; ensure routing and security groups allow this.
-
Restrict or disable public endpoint access
- To disable public access entirely:
aws eks update-cluster-config \--region us-east-1 \--name my-eks-cluster \--resources-vpc-config endpointPublicAccess=false
- If public access is needed, tighten CIDRs to your allowlist (example uses single admin IP):
aws eks update-cluster-config \--region us-east-1 \--name my-eks-cluster \--resources-vpc-config \"endpointPublicAccess=true,publicAccessCidrs=203.0.113.5/32,198.51.100.10/32"
Replace the example CIDRs with your approved ranges.
- To disable public access entirely:
-
Verify the updated configuration
After the update completes (may take a few minutes), re-run on any machine with AWS CLI access:aws eks describe-cluster \--region us-east-1 \--name my-eks-cluster \--query 'cluster.resourcesVpcConfig.{endpointPrivateAccess:endpointPrivateAccess,endpointPublicAccess:endpointPublicAccess,publicAccessCidrs:publicAccessCidrs}' \--output tableConfirm
endpointPrivateAccessistrue, and thatendpointPublicAccessandpublicAccessCidrsmatch your chosen policy. -
Functionally test access paths
- From a node inside the VPC, confirm API access still works:
kubectl get nodes
- From a client IP not in any allowed CIDR, confirm the public endpoint is no longer reachable (connection timeout or TLS failure).
- From an allowed client IP (if public access is enabled), confirm API access succeeds with valid credentials.
- From a node inside the VPC, confirm API access still works:
Using kubectl
kubectl cannot change control plane endpoint access, because this setting is managed at the cloud provider / EKS control plane level, not via Kubernetes API objects. Use the cloud provider console/CLI/IaC to update the cluster’s endpoint access as described in the Manual Steps section.
Automation
#!/usr/bin/env bash
#
# Report EKS control plane endpoint exposure for all clusters in an account/region.
# Requires: aws CLI, jq
#
# Run on: any machine with AWS credentials and aws/jq installed.
set -euo pipefail
AWS_REGION="${1:-us-east-1}"
echo "Inspecting EKS clusters in region: ${AWS_REGION}" >&2
echo
# Header
printf "%-40s %-8s %-7s %-18s %-30s\n" \
"CLUSTER_NAME" "PRIVATE" "PUBLIC" "PUBLIC_ACCESS_MODE" "PUBLIC_ACCESS_CIDRS"
printf -- "%.0s-" {1..120}; echo
clusters_json="$(aws eks list-clusters --region "${AWS_REGION}")"
cluster_names=($(echo "${clusters_json}" | jq -r '.clusters[]'))
if [ "${#cluster_names[@]}" -eq 0 ]; then
echo "No EKS clusters found in region ${AWS_REGION}" >&2
exit 0
fi
for cluster in "${cluster_names[@]}"; do
desc="$(aws eks describe-cluster --region "${AWS_REGION}" --name "${cluster}")"
private_access="$(echo "${desc}" \
| jq -r '.cluster.resourcesVpcConfig.endpointPrivateAccess')"
public_access="$(echo "${desc}" \
| jq -r '.cluster.resourcesVpcConfig.endpointPublicAccess')"
public_cidrs="$(echo "${desc}" \
| jq -r '.cluster.resourcesVpcConfig.publicAccessCidrs | join(",")')"
# Derived "mode" for easier review:
# - FULLY_PRIVATE: private=true, public=false
# - RESTRICTED_PUBLIC: private=true, public=true, CIDRs not 0.0.0.0/0
# - OPEN_PUBLIC: public=true and includes 0.0.0.0/0
# - PUBLIC_NO_PRIVATE: private=false, public=true (with any CIDRs)
mode="UNKNOWN"
if [ "${public_access}" = "false" ] && [ "${private_access}" = "true" ]; then
mode="FULLY_PRIVATE"
elif [ "${public_access}" = "true" ] && [[ "${public_cidrs}" == *"0.0.0.0/0"* ]]; then
mode="OPEN_PUBLIC"
elif [ "${public_access}" = "true" ] && [ "${private_access}" = "false" ]; then
mode="PUBLIC_NO_PRIVATE"
elif [ "${public_access}" = "true" ] && [ "${private_access}" = "true" ]; then
mode="RESTRICTED_PUBLIC"
fi
printf "%-40s %-8s %-7s %-18s %-30s\n" \
"${cluster}" \
"${private_access}" \
"${public_access}" \
"${mode}" \
"${public_cidrs}"
done
cat <<'EOF'
How to interpret results (potential problems):
1) endpointPrivateAccess = false
- Modes: PUBLIC_NO_PRIVATE or OPEN_PUBLIC
- Issue: Nodes and workloads must reach the API over the public internet; control-plane
traffic is not restricted to the VPC as recommended.
2) endpointPublicAccess = true AND publicAccessCidrs includes 0.0.0.0/0
- Mode: OPEN_PUBLIC
- Issue: The API server is reachable from any IP on the internet.
3) endpointPublicAccess = true AND publicAccessCidrs contains broad CIDRs
(e.g. 0.0.0.0/0, 0.0.0.0/1, 10.0.0.0/8, etc.)
- Mode: RESTRICTED_PUBLIC (but possibly overly permissive)
- Issue: Review whether each CIDR is truly required and minimize where possible.
4) Preferred states with respect to this control:
- FULLY_PRIVATE:
endpointPrivateAccess = true
endpointPublicAccess = false
- RESTRICTED_PUBLIC (if public access is required):
endpointPrivateAccess = true
endpointPublicAccess = true
publicAccessCidrs = tightly scoped, specific /32s or small ranges
Note: This script only reports configuration. Changes must be made via aws eks update-cluster-config
or your IaC/console workflow after a risk/operations review.
EOF