Skip to main content

Restrict Access to the EKS Control Plane Endpoint

Overview

This check verifies that your Amazon EKS cluster's Kubernetes API server endpoint is not publicly accessible to the entire internet. By default, EKS creates a public endpoint that anyone can reach (0.0.0.0/0). Restricting access ensures only authorized networks can communicate with your cluster's control plane.

Risk

When your EKS control plane endpoint allows unrestricted public access:

  • Unauthorized access attempts: Attackers can probe your API server for vulnerabilities or misconfigurations
  • Credential brute-forcing: Exposed endpoints are targets for credential stuffing and authentication attacks
  • Reconnaissance: Attackers can gather information about your cluster version and configuration
  • DDoS vulnerability: A publicly accessible endpoint increases your attack surface for denial-of-service attacks

Restricting access to specific CIDR blocks or disabling public access entirely significantly reduces these risks.

Remediation Steps

Prerequisites

  • AWS account access with permissions to modify EKS cluster configurations
  • Know which IP ranges or networks need access to your cluster (e.g., your office IP, VPN, or CI/CD systems)
Required IAM permissions

To modify cluster endpoint access, you need:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"eks:UpdateClusterConfig",
"eks:DescribeCluster"
],
"Resource": "arn:aws:eks:*:*:cluster/*"
}
]
}

AWS Console Method

  1. Open the EKS Console at https://console.aws.amazon.com/eks
  2. Ensure you are in the us-east-1 region (top-right corner)
  3. Click Clusters in the left sidebar
  4. Click on the name of the cluster you want to secure
  5. Click the Networking tab
  6. In the Cluster endpoint access section, click Manage
  7. Choose one of these secure configurations:
    • Option A (Recommended for most users): Keep Public enabled, enable Private, and add specific CIDR blocks under Advanced settings (e.g., your office IP: 203.0.113.0/24)
    • Option B (Maximum security): Disable Public and enable Private (requires VPN or bastion host access)
  8. If using Option A, remove 0.0.0.0/0 from the allowed CIDR blocks and add only your trusted IP ranges
  9. Click Save changes

The update may take several minutes to complete. Your cluster status will show "Updating" during this time.

AWS CLI (optional)

Restrict public access to specific CIDR blocks

This allows public access only from your specified IP ranges:

aws eks update-cluster-config \
--name <YOUR_CLUSTER_NAME> \
--resources-vpc-config '{
"endpointPublicAccess": true,
"endpointPrivateAccess": true,
"publicAccessCidrs": ["203.0.113.0/24", "198.51.100.0/24"]
}' \
--region us-east-1

Replace <YOUR_CLUSTER_NAME> with your cluster name and update the CIDR blocks to match your trusted networks.

Disable public access entirely (private-only)

For maximum security, disable public access completely:

aws eks update-cluster-config \
--name <YOUR_CLUSTER_NAME> \
--resources-vpc-config '{
"endpointPublicAccess": false,
"endpointPrivateAccess": true
}' \
--region us-east-1

Warning: Before disabling public access, ensure you have an alternative way to reach the cluster (VPN, bastion host, or AWS Cloud9 in the VPC).

Check update status

The update runs asynchronously. Check its status:

aws eks describe-update \
--name <YOUR_CLUSTER_NAME> \
--update-id <UPDATE_ID_FROM_PREVIOUS_COMMAND> \
--region us-east-1
CloudFormation (optional)

When creating or updating an EKS cluster, configure endpoint access in your CloudFormation template:

AWSTemplateFormatVersion: '2010-09-09'
Description: EKS cluster with restricted endpoint access

Parameters:
ClusterName:
Type: String
Description: Name of the EKS cluster
ClusterVersion:
Type: String
Default: '1.28'
Description: Kubernetes version
ClusterRoleArn:
Type: String
Description: ARN of the EKS cluster IAM role
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: List of subnet IDs (minimum 2)
SecurityGroupIds:
Type: List<AWS::EC2::SecurityGroup::Id>
Description: Security group IDs for the cluster
AllowedCidrs:
Type: CommaDelimitedList
Default: '203.0.113.0/24'
Description: CIDR blocks allowed to access the public endpoint

Resources:
EKSCluster:
Type: AWS::EKS::Cluster
Properties:
Name: !Ref ClusterName
Version: !Ref ClusterVersion
RoleArn: !Ref ClusterRoleArn
ResourcesVpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds: !Ref SecurityGroupIds
EndpointPublicAccess: true
EndpointPrivateAccess: true
PublicAccessCidrs: !Ref AllowedCidrs
Tags:
- Key: Environment
Value: Production

Outputs:
ClusterEndpoint:
Description: EKS cluster endpoint
Value: !GetAtt EKSCluster.Endpoint
ClusterArn:
Description: EKS cluster ARN
Value: !GetAtt EKSCluster.Arn

Private-only cluster configuration

For maximum security with no public access:

ResourcesVpcConfig:
SubnetIds: !Ref SubnetIds
SecurityGroupIds: !Ref SecurityGroupIds
EndpointPublicAccess: false
EndpointPrivateAccess: true

Deploy with:

aws cloudformation deploy \
--template-file eks-cluster.yaml \
--stack-name secure-eks-cluster \
--parameter-overrides \
ClusterName=my-cluster \
ClusterRoleArn=arn:aws:iam::123456789012:role/EKSClusterRole \
SubnetIds=subnet-abc123,subnet-def456 \
SecurityGroupIds=sg-12345678 \
AllowedCidrs=203.0.113.0/24 \
--region us-east-1
Terraform (optional)

EKS cluster with restricted public access

variable "cluster_name" {
description = "Name of the EKS cluster"
type = string
}

variable "cluster_version" {
description = "Kubernetes version"
type = string
default = "1.28"
}

variable "subnet_ids" {
description = "List of subnet IDs for the cluster"
type = list(string)
}

variable "allowed_cidrs" {
description = "CIDR blocks allowed to access the public endpoint"
type = list(string)
default = ["203.0.113.0/24"]
}

resource "aws_eks_cluster" "main" {
name = var.cluster_name
version = var.cluster_version
role_arn = aws_iam_role.eks_cluster.arn

vpc_config {
subnet_ids = var.subnet_ids
endpoint_public_access = true
endpoint_private_access = true
public_access_cidrs = var.allowed_cidrs
}

tags = {
Environment = "Production"
}

depends_on = [
aws_iam_role_policy_attachment.eks_cluster_policy
]
}

output "cluster_endpoint" {
description = "EKS cluster endpoint"
value = aws_eks_cluster.main.endpoint
}

Private-only cluster configuration

For maximum security:

vpc_config {
subnet_ids = var.subnet_ids
endpoint_public_access = false
endpoint_private_access = true
}

Deploy with:

terraform init
terraform plan -var="cluster_name=my-cluster" -var='subnet_ids=["subnet-abc123","subnet-def456"]'
terraform apply

Verification

After making changes, verify your cluster endpoint access is properly restricted:

  1. Open the EKS Console at https://console.aws.amazon.com/eks
  2. Click on your cluster name
  3. Select the Networking tab
  4. Under Cluster endpoint access, confirm:
    • Public access is either disabled, or
    • Public access shows specific CIDR blocks (not 0.0.0.0/0)
Verify with AWS CLI
aws eks describe-cluster \
--name <YOUR_CLUSTER_NAME> \
--query 'cluster.resourcesVpcConfig.{PublicAccess:endpointPublicAccess,PrivateAccess:endpointPrivateAccess,PublicCidrs:publicAccessCidrs}' \
--output table \
--region us-east-1

Expected output for a compliant cluster:

-------------------------------------------------
| DescribeCluster |
+----------------+---------------+--------------+
| PrivateAccess | PublicAccess | PublicCidrs |
+----------------+---------------+--------------+
| True | True | 203.0.113.0/24 |
+----------------+---------------+--------------+

If PublicCidrs shows 0.0.0.0/0 or ::/0, the cluster is still publicly accessible and not compliant.

List all clusters with unrestricted public access

for cluster in $(aws eks list-clusters --query 'clusters[*]' --output text --region us-east-1); do
cidrs=$(aws eks describe-cluster --name "$cluster" --query 'cluster.resourcesVpcConfig.publicAccessCidrs' --output text --region us-east-1)
if echo "$cidrs" | grep -q "0.0.0.0/0"; then
echo "UNRESTRICTED: $cluster"
fi
done

Additional Resources

Notes

  • Update duration: Endpoint access changes can take several minutes to complete. The cluster remains operational during the update.
  • Private access prerequisites: When enabling private endpoint access, your VPC must have enableDnsHostnames and enableDnsSupport set to true.
  • Accessing private-only clusters: If you disable public access, you will need one of these methods to access the API server:
    • AWS VPN or Direct Connect to your VPC
    • A bastion host (EC2 instance) within the VPC
    • AWS Cloud9 IDE in the VPC
    • AWS Systems Manager Session Manager
  • Node communication: EKS worker nodes always communicate with the control plane. When private access is enabled, node-to-control-plane traffic stays within AWS networks.
  • kubectl access: Ensure your local machine's IP is included in the allowed CIDR blocks if you need to run kubectl commands from outside AWS.
  • CI/CD systems: Remember to add the IP ranges of your CI/CD systems (e.g., GitHub Actions, Jenkins) to the allowed CIDR blocks.
  • IPv6 clusters: For EKS clusters created after October 2024 with IPv6 enabled, you can specify both IPv4 and IPv6 CIDR blocks in publicAccessCidrs.