Amazon EKS — IRSA (IAM Roles for Service Accounts)
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
- EKS cluster has an OIDC provider (identity provider)
- An IAM role trusts this OIDC provider for a specific K8s ServiceAccount
- The ServiceAccount is annotated with the IAM role ARN
- When a pod runs with this ServiceAccount, the EKS pod identity webhook injects
AWS_ROLE_ARNandAWS_WEB_IDENTITY_TOKEN_FILEenvironment variables - 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
- Use IRSA for every pod that accesses AWS services — no exceptions
- Scope the trust policy to specific namespace:serviceaccount
- Use separate roles per application — don’t share roles across services
- Regularly audit IAM roles — check for overly permissive policies
- Consider Pod Identity for simpler setup — see EKS Pod Identity