Least-Privilege Pod IAM: IRSA & Pod Identity
Give each workload exactly the AWS permissions it needs — and know when to reach for the newer EKS Pod Identity instead of IRSA.
The single biggest security win on EKS is refusing to attach AWS permissions to the node. If the node role can read every S3 bucket, then so can every pod that lands on it — including the one running someone else's sidecar. Bind permissions to the workload, not the host.
Two mechanisms, one goal
- IRSA (IAM Roles for Service Accounts) — the battle-tested approach. Each cluster has an OIDC provider; a service account is annotated with a role ARN, and the pod's projected token is exchanged with STS for short-lived credentials.
- EKS Pod Identity (newer) — no per-cluster OIDC provider to wire up, no trust-policy editing. You install the Pod Identity Agent add-on once and create associations that map a namespace + service account to a role. The same role can be reused across many clusters.
How I choose
Reach for Pod Identity on new clusters — it removes the fiddly OIDC and per-role trust-policy work and scales cleanly across a fleet. Stick with IRSA where you already have it wired, or where a tool only documents the IRSA path. Both give you the same prize: rotating, workload-scoped credentials with no secrets in the pod.
The least-privilege habit
- Start from an empty policy and add actions until the app stops erroring — never start from a managed
*FullAccesspolicy. - Scope every statement to specific resource ARNs, not
"Resource": "*". - One role per workload. Shared roles quietly become god-roles.
Example
# Modern path: EKS Pod Identity (no OIDC provider to manage)
aws eks create-addon --cluster-name demo \
--addon-name eks-pod-identity-agent
# Map a namespace + service account to a scoped IAM role
aws eks create-pod-identity-association \
--cluster-name demo \
--namespace prod \
--service-account s3-reader \
--role-arn arn:aws:iam::111122223333:role/prod-s3-reader
# Classic path: IRSA via eksctl (creates the role + trust policy for you)
eksctl create iamserviceaccount \
--cluster demo --namespace prod --name s3-reader \
--attach-policy-arn arn:aws:iam::111122223333:policy/prod-s3-read \
--approveWhen to use it
- A security team replaces node instance profile permissions with per-pod IRSA roles so a compromised pod can only access its own S3 bucket, not all buckets on the node.
- A platform team migrates from kube2iam to IRSA to eliminate the metadata proxy dependency and reduce per-request IAM credential latency.
- A compliance officer audits IAM role trust policies to verify each IRSA role is scoped to exactly one service account in one namespace.
More examples
Scoped IRSA role trust policy
An IAM trust policy with exact-match conditions that locks the role to a single service account in the 'payments' namespace, preventing other pods from assuming it.
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/ABCD1234"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.us-east-1.amazonaws.com/id/ABCD1234:sub": "system:serviceaccount:payments:payment-processor",
"oidc.eks.us-east-1.amazonaws.com/id/ABCD1234:aud": "sts.amazonaws.com"
}
}
}]
}Create least-privilege IRSA role
Creates a service account mapped to a narrow custom policy that grants only the specific S3 actions the payment service needs.
eksctl create iamserviceaccount \
--name payment-processor \
--namespace payments \
--cluster prod \
--attach-policy-arn arn:aws:iam::123456789012:policy/PaymentsBucketReadOnly \
--approve \
--override-existing-serviceaccountsVerify pod receives STS credentials
Runs aws sts get-caller-identity inside the pod to verify it is using the IRSA role ARN and not inheriting the broad node instance profile.
kubectl exec -n payments payment-processor-pod -- \
aws sts get-caller-identity
# Confirm the ARN matches the IRSA role, not the node instance profile
Discussion