Skip to content

EKS and ECS Cluster Enumeration

5 min read Created May 24, 2026 Last updated May 24, 2026

EKS clusters expose a Kubernetes API endpoint backed by AWS IAM authentication. Enumerating clusters tells you what is deployed and where identity boundaries may be weak.

  • EKS clusters with public API endpoints and unrestricted CIDR ranges
  • Node group IAM roles with overly broad permissions (S3, ECR, Secrets Manager)
  • OIDC trust policies using StringLike wildcards instead of StringEquals
  • ECS task definitions with plaintext credentials in environment variables
  • ECS Exec enabled on clusters, allowing direct shell access to running containers

EKS

List Clusters

Output is just cluster names for the specified region. Run this across every region your access covers.

┌──(kali㉿kali)-[~]
└─$ aws eks list-clusters --profile $PROFILE --region $REGION

Describe Cluster

The describe call returns the full cluster config. Focus on the Kubernetes version, the API endpoint, the cluster IAM role, and the public access setting.

┌──(kali㉿kali)-[~]
└─$ aws eks describe-cluster --name $CLUSTER_NAME --profile $PROFILE --region $REGION --query 'cluster.{Name:name,Version:version,Endpoint:endpoint,RoleARN:roleArn,VPC:resourcesVpcConfig.vpcId,Public:resourcesVpcConfig.endpointPublicAccess}'

endpointPublicAccess: true means the Kubernetes API server is reachable from the internet. If public CIDR ranges are not restricted, valid credentials can reach the API from anywhere. Check publicAccessCidrs in the full describe output. A value of 0.0.0.0/0 means no IP restriction is in place. If you have valid AWS credentials, try generating a kubeconfig with aws eks update-kubeconfig. Even read-only Kubernetes API access can reveal running workloads, mounted secrets, and service account tokens. Also note the Kubernetes version. Older versions may expose known API vulnerabilities or run deprecated admission controllers that weaken pod security.

Node Groups

Node groups define the EC2 instances that run cluster workloads. The node IAM role is the critical field.

┌──(kali㉿kali)-[~]
└─$ aws eks list-nodegroups --cluster-name $CLUSTER_NAME --profile $PROFILE --region $REGION
┌──(kali㉿kali)-[~]
└─$ aws eks describe-nodegroup --cluster-name $CLUSTER_NAME --nodegroup-name $NODEGROUP --profile $PROFILE --region $REGION --query 'nodegroup.{NodeRole:nodeRole,InstanceType:instanceTypes,AmiType:amiType}'

The nodeRole ARN reveals the IAM role attached to all EC2 nodes in the group. Any pod that escapes to the host inherits this role. Look at this role's policies using IAM enumeration. Common misconfigurations attach broad S3 or ECR access at the node level. The correct pattern is pod-level access via IRSA. With hostPID or privileged mode enabled, the node role is reachable via the instance metadata at 169.254.169.254. Also note the AMI type. Amazon Linux 2 nodes run older kernels that may be vulnerable to container escape techniques.

OIDC Provider

EKS uses OIDC for pod-level IAM roles (IRSA). IRSA lets individual pods assume IAM roles without sharing the node role. This is the correct isolation pattern, but misconfigured trust policies break it. List the OIDC provider:

┌──(kali㉿kali)-[~]
└─$ aws iam list-open-id-connect-providers --profile $PROFILE

OIDC-trusted roles have trust policies that bind to a specific namespace and service account. The StringEquals condition is the secure form:

{
  "Condition": {
    "StringEquals": {
      "oidc.eks.$REGION.amazonaws.com/id/$CLUSTER_ID:sub": "system:serviceaccount:$NAMESPACE:$SA_NAME"
    }
  }
}

Overly broad conditions (e.g. StringLike with wildcards) allow any pod in the namespace to assume the role. A condition like system:serviceaccount:prod:* means any service account in the prod namespace can assume the role. Control any pod in that namespace and you can request the projected token to assume the role directly. Look for roles with high-value permissions such as s3:GetObject, secretsmanager:GetSecretValue, or iam:PassRole. Cross-reference OIDC-trusted roles with their policies to find the most valuable targets.

ECS clusters group EC2 or Fargate capacity. The cluster ARN is required for all subsequent calls. Check every region and account in scope.

ECS

List Clusters

┌──(kali㉿kali)-[~]
└─$ aws ecs list-clusters --profile $PROFILE --region $REGION

List Services

Services keep a desired count of tasks running. The task definition revision shows which container config version is active. A stopped service may still have task definitions worth inspecting.

┌──(kali㉿kali)-[~]
└─$ aws ecs list-services --cluster $CLUSTER_ARN --profile $PROFILE --region $REGION
┌──(kali㉿kali)-[~]
└─$ aws ecs describe-services --cluster $CLUSTER_ARN --services $SERVICE_ARN --profile $PROFILE --region $REGION --query 'services[*].{Name:serviceName,Task:taskDefinition,Status:status,Running:runningCount}'

Task Definitions

Task definitions are versioned. Always check the active revision and at least one previous revision. Secrets and credentials are often left in plain environment variables after initial setup and never removed. The secrets field references Parameter Store or Secrets Manager values by ARN. Finding those ARNs tells you what secrets are in use and whether your current role can read them.

┌──(kali㉿kali)-[~]
└─$ aws ecs list-task-definitions --profile $PROFILE --region $REGION
┌──(kali㉿kali)-[~]
└─$ aws ecs describe-task-definition --task-definition $TASK_DEF --profile $PROFILE --region $REGION
┌──(kali㉿kali)-[~]
└─$ aws ecs describe-task-definition --task-definition $TASK_DEF --profile $PROFILE --region $REGION | jq '.taskDefinition.containerDefinitions[].environment'
┌──(kali㉿kali)-[~]
└─$ aws ecs describe-task-definition --task-definition $TASK_DEF --profile $PROFILE --region $REGION | jq '.taskDefinition.containerDefinitions[].secrets'

Running Tasks

Running tasks show what is live right now. The task ARN is your handle for ECS Exec if it is enabled on the cluster. Check lastStatus to confirm the task is running before attempting to exec. If ECS Exec is enabled, the SSM agent runs inside the container. Open a shell via aws ecs execute-command without touching the task definition.

┌──(kali㉿kali)-[~]
└─$ aws ecs list-tasks --cluster $CLUSTER_ARN --profile $PROFILE --region $REGION
┌──(kali㉿kali)-[~]
└─$ aws ecs describe-tasks --cluster $CLUSTER_ARN --tasks $TASK_ARN --profile $PROFILE --region $REGION --query 'tasks[*].{Task:taskArn,Role:overrides.taskRoleArn,Status:lastStatus}'

Task Role

The task role is distinct from the task execution role. The execution role only covers pulling images and writing logs to CloudWatch. The task role is what the application code runs as. Enumerate what it can do:

┌──(kali㉿kali)-[~]
└─$ aws ecs describe-task-definition --task-definition $TASK_DEF --profile $PROFILE --region $REGION | jq -r '.taskDefinition.taskRoleArn'

Then enumerate that role's policies as per IAM Authenticated Enumeration. An ECS task role with broad S3 or SSM access is a high-value lateral movement target. If the task role has iam:PassRole or ecs:RegisterTaskDefinition, you can register a new task definition with a different role. Specify a higher-privileged role in the new definition and launch a task to pivot.