Authentication Patterns on EKS

Overview

EKS uses two-layer authentication:

  1. Authentication (AuthN) - Who are you?
  2. Authorization (AuthZ) - What can you do?
┌─────────────────────────────────────────────────────────────┐
│                    EKS Access Model                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Request comes in                                           │
│       │                                                     │
│       ▼                                                     │
│  ┌─────────────┐                                            │
│  │     IAM     │ ──── AuthN: Verify IAM identity           │
│  └─────────────┘                                            │
│       │                                                     │
│       ▼                                                     │
│  ┌─────────────┐                                            │
│  │    RBAC     │ ──── AuthZ: Check Kubernetes permissions  │
│  └─────────────┘                                            │
│       │                                                     │
│       ▼                                                     │
│  Allow or Deny                                              │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Authentication Methods

For Humans (kubectl)

MethodAuthNUse Case
AWS CLI (IAM user/role)IAMLocal development, CI/CD
AWS ConsoleIAMWeb UI
Bastion hostIAM + networkPrivate clusters
CloudShellIAMQuick operations, private clusters
Cloud9IAM + VPCDevelopment environment

For Workloads (Pods)

MethodAuthNUse Case
Pod IdentityIAM roleAWS SDK access
IRSAIAM role via OIDCAWS SDK access
Node IAM roleInstance profileFallback (not recommended)

Pod Authentication Deep-Dive

Available Methods

MethodProsCons
Pod IdentitySimple, no OIDC, cross-clusterAgent required, EKS-only
IRSAMature, OIDC-basedOIDC setup, per-cluster config
Instance ProfileNo setupAll pods share permissions, no isolation

Credential Isolation Comparison

┌─────────────────────────────────────────────────────────────┐
│              Credential Access by Method                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Method: Instance Profile (on node)                         │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Node Instance Role                                 │    │
│  │    │                                                │    │
│  │    ├──► Pod A (can access if knows role)           │    │
│  │    ├──► Pod B (can access if knows role)           │    │
│  │    └──► Pod C (can access if knows role)           │    │
│  └─────────────────────────────────────────────────────┘    │
│  Problem: All pods on node can potentially access creds     │
│                                                             │
│  Method: Pod Identity / IRSA                                │
│  ┌─────────────────────────────────────────────────────┐    │
│  │  Pod A ──► Own Role (read-s3)                       │    │
│  │  Pod B ──► Own Role (write-dynamo)                  │    │
│  │  Pod C ──► Own Role (admin-eks)                    │    │
│  └─────────────────────────────────────────────────────┘    │
│  Benefit: Each pod has only its own permissions            │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Node IMDS Consideration

IMDS ConfigurationPod can access node role?Pod can use IRSA/Pod Identity?
IMDSv2 requiredNoYes
IMDSv1 allowedPotentiallyYes
UnrestrictedYesSDK prefers IRSA

Recommendation: Always require IMDSv2:

# On all nodes
aws ec2 modify-instance-metadata-options \
  --instance-id i-xxxx \
  --http-tokens required \
  --http-put-response-hop-limit 1

IRSA vs Pod Identity Quick Reference

AspectIRSAPod Identity
SetupCreate OIDC provider + trust policyCreate EKS association
OIDC providerMust create and manageNot needed
Trust policyoidc.eks.region.../id/CLUSTER_ID:sub=...Service: pods.eks.amazonaws.com
Per-cluster configYes, separate trust policyNo, same role works everywhere
Cross-clusterRequires separate rolesSingle role works
SDK callsEach pod calls STSCached at node level
Key rotation7 daysManaged by EKS
AgentNoneDaemonSet required
LimitsNone5,000 associations/cluster
FargateNot supportedNot supported

Decision Tree

                    Do you need AWS SDK access from pods?
                                    │
                                    Yes
                                    │
                    ┌───────────────┴───────────────┐
                    │                               │
            Multi-cluster?                    Single cluster?
                    │                               │
                    │                               │
            Use Pod Identity              ┌───────────┴───────────┐
            (same role works                 │                   │
            everywhere)                      │                   │
                                              │                   │
                              Simpler setup?              Complex OIDC needed?
                                    │                         │
                                    │                         │
                            Use Pod Identity          Use IRSA

Node Authentication

Node IAM Role

Required for nodes to join the cluster:

{
  "Effect": "Allow",
  "Action": [
    "ec2:DescribeInstances",
    "ec2:DescribeTags",
    "ec2:DescribeVolumes",
    "eks:DescribeCluster"
  ],
  "Resource": "*"
}

Node Authorization (NodeAuthorizer)

Kubernetes uses NodeAuthorizer to authorize kubelet requests:

# Nodes get these groups via aws-auth or Cluster Access API
groups:
  - system:bootstrappers  # Allows node registration
  - system:nodes          # Allows kubelet to operate

Instance Profile vs IRSA for Nodes

PurposeInstance ProfileIRSA for kubelet
Node registrationYesNo (different flow)
Node to API serverYesYes
Pod AWS accessSharedPer-pod via IRSA/PodIdentity

Note: Nodes still need instance profile/role for:

  • Node registration
  • Pulling ECR images
  • CloudWatch logging
  • Other AWS service access from node level

Cluster Access Methods Summary

For Cluster Management (kubectl)

MethodConfigurationBest For
Cluster Access APIaws eks create-access-entryAll new access grants
aws-auth ConfigMapkubectl edit configmapLegacy, node access only

Access Entry Policies

Policy ARNPermission
arn:aws:eks::aws:cluster-access-policy:AmazonEKSClusterAdminSuperuser (full cluster)
arn:aws:eks::aws:cluster-access-policy:AmazonEKSAdminViewRead-only cluster
arn:aws:eks::aws:cluster-access-policy:AmazonEKSEditRead/write workloads
arn:aws:eks::aws:cluster-access-policy:AmazonEKSViewRead-only namespaces

RBAC Mapping

Access entries grant IAM principals access, but RBAC controls what they can do:

IAM Principal (via Cluster Access API)
    │
    ▼
RBAC Role/ClusterRoleBinding
    │
    ▼
Kubernetes Permissions

Security Best Practices

1. Use IRSA or Pod Identity for All Pods

# Check for pods WITHOUT IRSA/Pod Identity
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.serviceAccountName}{"\n"}{end}' | \
  while read ns sa; do
    if ! kubectl get sa $sa -n $ns -o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}' 2>/dev/null | grep -q .; then
      echo "No IRSA: $ns/$sa"
    fi
  done

2. Restrict IMDS Access

# Require IMDSv2 on all instances
aws ec2 modify-instance-metadata-options \
  --instance-id i-xxxx \
  --http-tokens required \
  --http-put-response-hop-limit 1

3. Use Least-Privilege IAM Policies

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": [
      "s3:GetObject",
      "s3:GetObjectVersion"
    ],
    "Resource": "arn:aws:s3:::my-specific-bucket/my-specific-path/*"
  }]
}

4. Prefer Cluster Access API over aws-auth

# Grant access via Cluster Access API
aws eks create-access-entry \
  --cluster-name my-cluster \
  --principal-arn arn:aws:iam::123456789:user/developer
 
aws eks associate-access-policy \
  --cluster-name my-cluster \
  --principal-arn arn:aws:iam::123456789:user/developer \
  --policy-arn arn:aws:eks::aws:cluster-access-policy:AmazonEKSEdit

5. Use Separate Service Accounts

# Bad: Same SA for multiple apps with different permissions
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: shared-sa  # Used by app A, B, C
---
# Good: Separate SAs per app
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-a-sa  # Only for app A
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-b-sa  # Only for app B

Common Patterns

Pattern 1: App with S3 Access

# 1. Create role with S3 policy
aws iam create-role --role-name app-s3-role \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"pods.eks.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
 
# 2. Create Pod Identity association
aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --namespace production \
  --service-account my-app \
  --role-arn arn:aws:iam::123456789:role/app-s3-role
 
# 3. Use in deployment
kubectl apply -f - <<EOF
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-app
  namespace: production
---
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      serviceAccountName: my-app
EOF

Pattern 2: App with DynamoDB Access

# Role with DynamoDB policy
aws iam create-role --role-name app-dynamo-role \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"pods.eks.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
 
aws iam attach-role-policy --role-name app-dynamo-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess
 
# Association
aws eks create-pod-identity-association \
  --cluster-name my-cluster \
  --namespace backend \
  --service-account api-service \
  --role-arn arn:aws:iam::123456789:role/app-dynamo-role

Pattern 3: Database App with Secrets Manager

# Role for Secrets Manager + RDS
aws iam create-role --role-name db-app-role \
  --assume-role-policy-document '{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Principal":{"Service":"pods.eks.amazonaws.com"},"Action":"sts:AssumeRole"}]}'
 
# Attach policies
aws iam attach-role-policy --role-name db-app-role \
  --policy-arn arn:aws:iam::aws:policy/secretsmanager:ReadWrite
 
aws iam attach-role-policy --role-name db-app-role \
  --policy-arn arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess

Anti-Patterns to Avoid

1. Using Node Role for Application Workloads

# DON'T: Pod accessing AWS resources using node role
# (via IMDS if not restricted)
 
# DO: Use IRSA or Pod Identity instead

2. Overly Broad Trust Policies

// DON'T: Allow any service account in any cluster
{
  "Principal": {"Federated": "*"},
  "Condition": {"StringLike": {"*:sub": "system:serviceaccount:*:*"}}
}
 
// DO: Be specific
{
  "Condition": {
    "StringEquals": {
      "oidc.eks.us-west-2.amazonaws.com/id/CLUSTER_ID:sub": "system:serviceaccount:production:my-app"
    }
  }
}

3. Using Long-Lived AWS Credentials in Code

# DON'T: Embed credentials in image
RUN aws configure set aws_access_key_id xxx
 
# DO: Use IRSA/Pod Identity at runtime

4. Sharing Service Accounts Across Applications

# DON'T: App A and App B share same SA but have different permission needs
 
# DO: Separate SAs with appropriate permissions for each app

Reference: Environment Variables

VariableSourcePurpose
AWS_ROLE_ARNIRSA annotation or Pod IdentityRole being assumed
AWS_WEB_IDENTITY_TOKEN_FILEIRSA mountJWT for STS AssumeRoleWithWebIdentity
AWS_CONTAINER_CREDENTIALS_FULL_URIPod Identity agentLocal HTTP endpoint for creds
AWS_DEFAULT_REGIONUser/instanceDefault region
AWS_REGIONUser/instancePreferred region

References