IAM Roles for Service Accounts · OIDC trust · Token exchange · All interactive
What is IRSA and why does it exist? click each option
Your pod needs to call an AWS service (DynamoDB, S3, SES…). How does it prove to AWS who it is? Pick an approach below to see why it's good or bad.
How bad is the shared node role? before vs after
Click a pod to see what AWS services it can reach. Before IRSA, every pod on the node shared the EC2 instance role — one breach meant full access to everything. IRSA gives each pod its own scoped IAM role.
❌ Before IRSA — shared EC2 instance role
EC2 Node (single IAM role for everything)
💳 billing-svc
🖥 frontend
📊 analytics
← click a pod to see its access
✅ With IRSA — each pod gets its own role
EKS Node (pod-level IAM via OIDC)
💳 billing-svc
🖥 frontend
📊 analytics
← click a pod to see its access
The core problem IRSA solves: In the pre-IRSA world, pods called the EC2 Instance Metadata Service (IMDS) to get temporary credentials — but those credentials belonged to the node's IAM role, shared by all pods. A single vulnerable pod could read S3 buckets, DynamoDB tables, or Secrets Manager entries it had no business touching. IRSA introduces pod-level identity: each ServiceAccount maps to its own IAM role via OIDC, so a compromised pod can only reach what its specific role allows.
Scenario:
1. ServiceAccount & IAM Role setup interactive
Edit the inputs — the YAML manifest and eksctl command update live. Click "Apply to cluster" to see the resources appear.
Challenge: Build a SA + Role for a billing service in the payments namespace using DynamoDB read access.
eksctl one-liner
Manual YAML
What this does in one shot:
creates the IAM role · attaches the policy · configures the trust policy with your cluster's OIDC provider · creates the K8s ServiceAccount with the eks.amazonaws.com/role-arn annotation.
Manual mode: you create the IAM role + trust policy via Terraform/CLI, then apply this YAML.
The annotation tells the EKS Pod Identity Webhook to inject the projected token at pod startup.
🔑
OIDC Provider
—
🎭
IAM Role
—
📛
ServiceAccount
—
2. Configuration validation chain live validation
Four pieces must agree for IRSA to work: OIDC Provider ↔ IAM Trust Policy ↔ ServiceAccount ↔ Pod.
Edit any field — links turn green/red instantly, and you'll see the AWS error if broken.
Checking...
Click any link to highlight the related fields.
🔑
OIDC Provider
—
📜
Trust Policy
—
📛
ServiceAccount
—
📦
Pod
—
IAM Trust Policy —
OIDC registered—
Federated·
Condition—
sub—
aud—
ServiceAccount & Pod —
SA namespace—
SA name—
role-arn anno·
Pod uses SA—
Pod namespace—
AWS error
✓ Configuration valid
The pod's SDK will be able to call sts:AssumeRoleWithWebIdentity and receive temporary credentials. DynamoDB calls will work using the role's permissions.
Watch the request travel from the pod to DynamoDB. Each step shows what AWS actually sees in the API call.
Try the failure presets above — different scenarios fail at different steps.
Pod / Cluster
1
Pod scheduled
webhook injects env vars + projected token volume
2
SDK reads env
AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE
3
Token loaded
/var/run/secrets/eks.amazonaws.com/.../token
AWS STS
4
AssumeRoleWithWebIdentity
POST sts.amazonaws.com
5
Verify JWT signature
fetch JWKS from OIDC issuer
6
Check trust policy
match sub, aud against conditions
DynamoDB call
7
Receive temp creds
AccessKeyId, SecretKey, SessionToken
8
Sign API call
SigV4 with temp credentials
9
DynamoDB GetItem
TableName: Users · Key: { id: 42 }
# Press "Auto-play" or "Step ▶" to start the token exchange
4. JWT token inspector live decode
The projected service account token is a real JWT. Edit the claims on the left, see the trust policy match on the right.
Predict-then-verify: change a single character in the namespace — does the policy still match?
Projected SA token —
iss·
sub·
aud·
exp·
iat·
kubernetes.io/ namespace·
header
payload
signature
Each segment is base64url-encoded. The signature is computed by EKS's OIDC issuer using its private key, then verified by AWS STS via the public JWKS endpoint.
IAM Trust Policy —
5. Pitfall diagnostic lab game mode
Each scenario presents a real production symptom. Inspect the panels, then pick the root cause from the choices.
No second chances — read carefully before clicking.
Reference — commands, manifests, and gotchas
Trust policy template (the canonical IRSA trust policy)
Critical: use StringEquals, not StringLike. StringLike with wildcards is the #1 IRSA security gap — it lets any SA in the cluster assume the role.
Pod Identity Webhook — what it injects
When a pod is created using a SA with the eks.amazonaws.com/role-arn annotation, the mutating webhook injects:
Env var AWS_ROLE_ARN = the role ARN from the annotation
Env var AWS_WEB_IDENTITY_TOKEN_FILE = /var/run/secrets/eks.amazonaws.com/serviceaccount/token
A projected volume with the SA token at that path. The token is auto-refreshed before expiry by kubelet.
You can verify with: kubectl get mutatingwebhookconfiguration pod-identity-webhook -o yaml
Token rotation — why projected tokens, not regular SA tokens
Old K8s SA tokens were long-lived and stored in Secrets. Projected tokens have:
Short TTL (default 1 hour)
Bound to a specific audience (sts.amazonaws.com for IRSA)
Bound to a specific pod — invalidated when pod is deleted
Auto-rotated by kubelet at 80% of TTL
The AWS SDK re-reads the token file before each STS call, so rotation is transparent to the application.
Common AWS errors and what they actually mean
NotAuthorizedException: Not authorized to perform sts:AssumeRoleWithWebIdentity — the trust policy sub condition didn't match. Wrong namespace or wrong SA name. Most common.
InvalidIdentityToken: No OpenIDConnect provider found — cluster's OIDC provider not added to IAM. Run eksctl utils associate-iam-oidc-provider --cluster <name> --approve.
InvalidIdentityToken: Incorrect token audience — aud claim ≠ sts.amazonaws.com. Check the OIDC provider's audiences list in IAM.
InvalidIdentityToken: Issuer does not match — token issuer (cluster OIDC URL) doesn't match what's registered in IAM. Cluster was rebuilt? OIDC ID changed.
AccessDenied: User: arn:aws:sts::...:assumed-role/.../botocore-session-... is not authorized to perform: dynamodb:GetItem — IRSA worked! But the role's permission policy doesn't allow DynamoDB. Different problem.
Debugging IRSA — kubectl & AWS CLI commands
# 1. Verify SA has the annotation
kubectl get sa <name> -n <ns> -o yaml | grep role-arn
# 2. Check that webhook injected env vars
kubectl exec <pod> -- env | grep AWS_
# 3. Verify projected token exists
kubectl exec <pod> -- ls -la /var/run/secrets/eks.amazonaws.com/serviceaccount/
# 4. Decode the JWT to see claims (paste at jwt.io)
kubectl exec <pod> -- cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token
# 5. List OIDC providers registered in IAM
aws iam list-open-id-connect-providers
# 6. Inspect the role's trust policy
aws iam get-role --role-name <name> --query 'Role.AssumeRolePolicyDocument'
# 7. Test from inside the pod
kubectl exec <pod> -- aws sts get-caller-identity
EKS Pod Identity (the 2023 alternative)
AWS introduced EKS Pod Identity as a simpler alternative — no OIDC provider, no trust policy with cluster IDs. Instead, an EKS Pod Identity Agent DaemonSet acts like the EC2 IMDS for pods.
IRSA still has its place: cross-account roles, EKS Anywhere, self-managed Kubernetes, or any time you need OIDC federation. Most existing fleets are still IRSA.