Amazon EKS — IRSA (IAM Roles for Service Accounts)

2 minute read

IRSA lets pods assume IAM roles without needing node-level IAM permissions. Each pod gets only the AWS permissions it needs — no more shared node roles.


How IRSA Works

  1. EKS cluster has an OIDC provider (identity provider)
  2. An IAM role trusts this OIDC provider for a specific K8s ServiceAccount
  3. The ServiceAccount is annotated with the IAM role ARN
  4. When a pod runs with this ServiceAccount, the EKS pod identity webhook injects AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE environment variables
  5. AWS SDK calls STS AssumeRoleWithWebIdentity using the projected token

Setup

Step 1 — Enable OIDC Provider

# Check if already enabled
aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text

# Create the OIDC provider
eksctl utils associate-iam-oidc-provider --cluster my-cluster --approve

Step 2 — Create an IAM Policy

cat > s3-read-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:ListBucket"
      ],
      "Resource": [
        "arn:aws:s3:::my-bucket",
        "arn:aws:s3:::my-bucket/*"
      ]
    }
  ]
}
EOF

aws iam create-policy \
  --policy-name S3ReadPolicy \
  --policy-document file://s3-read-policy.json

Step 3 — Create the IAM Role with IRSA

eksctl create iamserviceaccount \
  --cluster my-cluster \
  --namespace production \
  --name s3-reader-sa \
  --attach-policy-arn arn:aws:iam::123456789:policy/S3ReadPolicy \
  --approve

This creates:

  • An IAM role with a trust policy scoped to the OIDC provider + namespace + ServiceAccount
  • A Kubernetes ServiceAccount annotated with eks.amazonaws.com/role-arn

Step 4 — Use the ServiceAccount in Your Pod

apiVersion: apps/v1
kind: Deployment
metadata:
  name: s3-reader
  namespace: production
spec:
  replicas: 1
  selector:
    matchLabels:
      app: s3-reader
  template:
    metadata:
      labels:
        app: s3-reader
    spec:
      serviceAccountName: s3-reader-sa    # IRSA-annotated SA
      containers:
        - name: app
          image: amazon/aws-cli:latest
          command: ["sleep", "infinity"]

Verify

# Check the ServiceAccount annotation
kubectl get sa s3-reader-sa -n production -o yaml

# Check environment variables in the pod
kubectl exec deploy/s3-reader -n production -- env | grep AWS

# Test S3 access
kubectl exec deploy/s3-reader -n production -- aws s3 ls s3://my-bucket/

Trust Policy

The IAM role’s trust policy looks like this:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/ABCDEF1234567890"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "oidc.eks.us-east-1.amazonaws.com/id/ABCDEF1234567890:sub": "system:serviceaccount:production:s3-reader-sa",
          "oidc.eks.us-east-1.amazonaws.com/id/ABCDEF1234567890:aud": "sts.amazonaws.com"
        }
      }
    }
  ]
}

The Condition ensures only the specific namespace:serviceaccount can assume this role.


IRSA vs Node-Level IAM

Feature Node IAM Role IRSA
Scope All pods on the node Specific ServiceAccount
Least privilege No — all pods share permissions Yes — per-pod permissions
Cross-account Complex Native (trust policy condition)
Security Over-privileged Granular

Never use node-level IAM for application workloads. IRSA or Pod Identity should be the standard.


Troubleshooting IRSA

Issue Check
Pod can’t assume role Verify SA annotation: eks.amazonaws.com/role-arn
“Not authorized to perform” Check IAM policy permissions and trust policy conditions
Token not injected Verify OIDC provider exists: aws iam list-open-id-connect-providers
Wrong role assumed Check if AWS_ROLE_ARN env var is correct in the pod
Cross-account fails Verify trust policy allows the source account’s OIDC provider

Best Practices

  1. Use IRSA for every pod that accesses AWS services — no exceptions
  2. Scope the trust policy to specific namespace:serviceaccount
  3. Use separate roles per application — don’t share roles across services
  4. Regularly audit IAM roles — check for overly permissive policies
  5. Consider Pod Identity for simpler setup — see EKS Pod Identity

Updated: