IAM Roles for Service Accounts (IRSA) in EKS: Secure Pod Access to AWS
IAM Roles for Service Accounts (IRSA) in EKS gives individual Kubernetes pods access to AWS services like S3, DynamoDB, and Secrets Manager, without storing access keys and without giving the whole node broad permissions. Each pod gets exactly the AWS permissions it needs, backed by short-lived credentials that expire automatically.
Simple explanation
When a pod in EKS needs to call an AWS API, it needs credentials. IRSA provides those credentials automatically and securely, scoped to just that workload.
The way it works: an IAM role is associated with a Kubernetes service account. When a pod starts using that service account, the AWS SDK inside the pod detects and uses temporary credentials tied to that role. The credentials expire after a short period and refresh automatically. Nobody needs to create, store, or rotate static access keys.
The IAM role is not attached directly to the pod. It is attached to the Kubernetes service account, and the pod inherits it by referencing that service account. A pod that does not specify the annotated service account receives no IRSA credentials.
In one sentence: IRSA lets a Kubernetes pod call AWS APIs using temporary, scoped IAM credentials, with no static keys anywhere in the chain.
Think of IRSA like a hotel key card system. Instead of handing every guest a master key to the whole building, each guest gets a card that opens only their room and expires at checkout. The hotel front desk (AWS IAM) issues the card. The door reader (STS) validates it. If the card is copied, it stops working at checkout with no permanent access granted.
Why IRSA exists
When pods need to call AWS services, there are a few ways to provide credentials, and they have very different security outcomes.
Node IAM roles: Every EC2 node in an Amazon EKS cluster runs with an IAM instance profile. Any pod on that node can reach the instance metadata endpoint and use those permissions. If you grant the node permission to read an S3 bucket so one pod can access it, every pod on that node gets the same access across every namespace. Applying the principle of least privilege at the pod level is not possible this way.
Storing AWS access keys as environment variables or Kubernetes Secrets is error-prone and difficult to audit. Long-lived credentials never expire and can be misused months or years after they leak. They can end up in container logs, CI/CD artifacts, or crash dumps without any warning.
IRSA: Associate a Kubernetes service account with an IAM role. Only pods that explicitly use that service account can assume the role. The credentials are short-lived tokens, automatically rotated, and scoped to exactly the permissions in that role. This is the recommended approach for production workloads.
How IRSA works
IRSA uses OpenID Connect (OIDC) federation between your EKS cluster and AWS IAM. Here is the flow step by step:
- OIDC provider enabled. Your EKS cluster is associated with an OIDC identity provider. This is a one-time per-cluster setup that tells AWS IAM to trust tokens the cluster issues.
- IAM role with a scoped trust policy. You create an IAM role whose trust policy allows the EKS OIDC provider to assume it, but only when the token belongs to a specific service account in a specific namespace.
- Service account annotated. The Kubernetes service account is annotated with the IAM role ARN. This is the link between the Kubernetes identity and the AWS identity.
- Pod uses that service account. The pod spec references the annotated service account. A mutating webhook automatically injects a projected token file path and two environment variables (
AWS_ROLE_ARNandAWS_WEB_IDENTITY_TOKEN_FILE) into the pod. - SDK exchanges the token. The AWS SDK reads those environment variables, loads the projected JWT, and calls
sts:AssumeRoleWithWebIdentity. AWS validates the token against the OIDC provider and returns temporary credentials. - Credentials refresh automatically. The pod gets short-lived credentials that expire in hours. The SDK refreshes them before they expire, without any intervention needed.
The trust chain is: EKS issues a signed token, IAM trusts that OIDC issuer, STS validates the token and returns temporary credentials. No permanent keys exist anywhere in this flow.
How to set up IRSA with eksctl
The fastest path is to use eksctl, which handles the IAM role, trust policy, and service account annotation in a single command.
Step 1: Enable the OIDC provider for your cluster
This associates your cluster with an OIDC provider in IAM. Run this once per cluster before creating any IRSA resources.
eksctl utils associate-iam-oidc-provider \
--cluster my-cluster \
--region eu-west-1 \
--approveStep 2: Create the IAM service account
This creates the IAM role, writes a scoped trust policy, and creates the annotated Kubernetes service account, all in one step.
eksctl create iamserviceaccount \
--name my-app-sa \
--namespace production \
--cluster my-cluster \
--region eu-west-1 \
--attach-policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
--approveStep 3: Reference the service account in your Deployment
Set serviceAccountName in the pod spec. Without this, the pod uses the default service account and receives no IRSA credentials.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: production
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
serviceAccountName: my-app-sa
containers:
- name: my-app
image: my-app:1.0.0The AWS SDK in your application detects and uses the injected credentials automatically. No code changes are needed.
You can attach a custom IAM policy instead of a managed one. Use —attach-policy-arn arn:aws:iam::123456789012:policy/MyCustomPolicy to scope permissions to exactly what your pod needs rather than using a broad AWS managed policy.
Manual setup: trust policy and service account annotation
If you manage the IAM role yourself through Terraform, CloudFormation, or the AWS CLI, here is what you need to configure.
IAM trust policy
Replace the OIDC provider URL and account ID with your own values. The OIDC provider URL is available in the EKS cluster details in the AWS console or via aws eks describe-cluster.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:production:my-app-sa",
"oidc.eks.eu-west-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com"
}
}
}
]
}Two conditions matter here:
sub(subject): scopes the role to one specific service account in one namespace. The format issystem:serviceaccount:<namespace>:<service-account-name>. Without this condition, any service account in the cluster could assume the role.aud(audience): restricts which STS endpoint can be used. This should always bests.amazonaws.com.
Kubernetes service account with the role ARN annotation
After creating the IAM role, create the service account and add the eks.amazonaws.com/role-arn annotation. This annotation is the bridge between the Kubernetes identity and the IAM role.
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: production
annotations:
eks.amazonaws.com/role-arn: arn:aws:iam::123456789012:role/my-app-irsa-roleHow to verify IRSA is working
Exec into the running pod and check which identity STS returns:
kubectl exec -it deploy/my-app -n production -- aws sts get-caller-identityThe output should show the IAM role ARN you created, not the EC2 node’s instance profile ARN. If the instance profile appears, something in the setup is misconfigured.
Troubleshooting checklist:
- Pod is using the right service account. Run
kubectl get pod <pod-name> -n production -o jsonpath='{.spec.serviceAccountName}'. It should returnmy-app-sa, notdefault. - Service account has the annotation. Run
kubectl get sa my-app-sa -n production -o yamland confirmeks.amazonaws.com/role-arnis present. - OIDC provider is enabled. Run
aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer". If the result is empty, the OIDC provider has not been associated with the cluster. - Trust policy matches exactly. The
subcondition is an exact string match againstsystem:serviceaccount:production:my-app-sa. A typo in either the namespace or service account name silently breaks the credential exchange.
If the pod was previously running with static credentials in its environment, the AWS SDK may use those first. Remove any AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY environment variables from the pod spec before testing IRSA.
When to use IRSA
IRSA is appropriate any time a pod needs to call an AWS API and you want access scoped to that specific workload. Common scenarios:
- A pod reads objects from a specific S3 bucket. The IAM role allows
s3:GetObjecton that bucket only. - An application fetches one secret from Secrets Manager at startup. The role allows
secretsmanager:GetSecretValueon that one secret ARN. See Managing Secrets in Kubernetes on AWS for how IRSA integrates with the External Secrets Operator. - A workload writes records to a single DynamoDB table. The role is scoped to
dynamodb:PutItemon that table. - Different teams run separate workloads in the same cluster and each needs different AWS permissions. Each team gets its own service account and IAM role with no credential sharing between them.
IRSA is especially useful in multi-tenant clusters where workload isolation matters. Each service account maps to a distinct IAM role, so you enforce permission boundaries through IAM rather than relying solely on Kubernetes RBAC. See Securing EKS Clusters for how IRSA fits into a broader cluster security posture.
IRSA vs node IAM roles vs EKS Pod Identity
There are three main ways to give pods access to AWS services. Understanding the differences helps you pick the right approach.
Node IAM roles assign permissions to the EC2 instance rather than to individual pods. All pods on a node share the same permissions. This is simple to set up but violates least privilege. You cannot scope access to one pod without granting the same access to every pod on that node.
IRSA ties an IAM role to a Kubernetes service account via OIDC federation. Pods using the annotated service account receive temporary STS credentials scoped to that role. Setup requires associating an OIDC provider with the cluster, writing a scoped trust policy, and annotating the service account. IRSA is stable, widely supported, and works with all recent AWS SDK versions.
EKS Pod Identity (introduced in 2023) removes the need to configure a per-cluster OIDC provider. The EKS Pod Identity Agent, a DaemonSet running on each node, intercepts credential requests from pods and returns temporary credentials via the EKS Auth service. You associate an IAM role with a service account using a single EKS API call. EKS Pod Identity simplifies setup on new clusters, but IRSA is not obsolete. It remains the right choice for clusters already using OIDC or environments where the Pod Identity agent is unavailable.
| Node IAM role | IRSA | EKS Pod Identity | |
|---|---|---|---|
| Scope | All pods on the node | Per service account | Per service account |
| Credential type | EC2 instance profile | Short-lived STS (OIDC) | Short-lived STS (EKS Auth) |
| Least privilege | No | Yes | Yes |
| OIDC provider required | No | Yes, per cluster | No |
| Trust policy | EC2 service trust | OIDC issuer trust | EKS service trust |
| Setup complexity | Minimal | Moderate | Low (EKS API call) |
| Cluster version | All | All | EKS 1.24+ |
For a deeper look at how AssumeRoleWithWebIdentity and the STS trust model work, see AWS Role Assumption Explained.
EKS Pod Identity is worth considering for new clusters. For existing clusters already configured with OIDC, IRSA works well and there is no need to migrate unless the simpler setup is a priority.
Common mistakes
- Missing
serviceAccountNamein the pod spec. The most common mistake. The pod uses thedefaultservice account, which has no IRSA annotation, and falls through to the node’s instance profile credentials. Always setserviceAccountNameexplicitly in the Deployment spec. - Service account not annotated with the role ARN. Creating the IAM role is not enough. The Kubernetes service account must have the
eks.amazonaws.com/role-arnannotation. eksctl handles this automatically; manual setups frequently miss it. - OIDC provider not associated with the cluster. Token exchange fails silently if no OIDC provider is configured. Run
eksctl utils associate-iam-oidc-providerbefore creating any IRSA resources. This is a one-time step per cluster. - Trust policy does not match the actual namespace or service account name. The
subcondition is an exact string match againstsystem:serviceaccount:<namespace>:<name>. A typo in either value silently breaks credential exchange with no clear error message. - Trust policy too broad (missing
subcondition). Omitting the subject condition means any service account in the cluster can assume the role. Always scope the trust policy to the specific namespace and service account. - Earlier credentials taking precedence in the SDK chain. If the pod spec includes
AWS_ACCESS_KEY_IDorAWS_SECRET_ACCESS_KEY, the SDK uses those before checking for the IRSA token. Remove any static credential environment variables from the pod spec. - Treating IRSA as a complete workload isolation boundary. IRSA scopes AWS API access to a service account. It does not isolate pods at the network level or prevent a pod from accessing Kubernetes resources via the API. Pair IRSA with Kubernetes RBAC and network policies for complete isolation.
What to remember
- IRSA attaches an IAM role to a Kubernetes service account, not directly to a pod. A pod gets the role by referencing that service account in its spec.
- It uses OIDC federation: the EKS cluster issues signed tokens, STS validates them via
AssumeRoleWithWebIdentity, and returns short-lived credentials. - Setup requires three things: OIDC provider on the cluster, IAM role with a scoped trust policy, and the
eks.amazonaws.com/role-arnannotation on the Kubernetes service account. - eksctl automates all three with
eksctl create iamserviceaccount. - EKS Pod Identity is a newer alternative that removes the OIDC provider requirement. Worth considering for new clusters.
- IRSA is not a substitute for Kubernetes RBAC or network policies. Use all three together.
Frequently asked questions
What is the difference between IRSA and EKS Pod Identity?
IRSA uses OIDC federation: the EKS cluster has an OIDC identity provider, and pods exchange a projected JWT for temporary STS credentials via AssumeRoleWithWebIdentity. EKS Pod Identity (introduced in 2023) uses the EKS Pod Identity Agent running on each node. Pods request credentials through the EKS Auth service instead of going directly to STS with a web identity token. EKS Pod Identity removes the need to configure a per-cluster OIDC provider for this use case, which simplifies setup on new clusters. Both give pods short-lived credentials scoped to a specific IAM role. IRSA remains valid and is widely deployed.
Does every pod need its own IAM role?
No. The IAM role is attached to a Kubernetes service account, not directly to a pod. Any number of pods using the same service account share the same IAM role. The decision is really about how many distinct permission sets your workloads need. A group of pods that all require the same S3 access can share one service account and one role.
Why is my pod still using the node IAM role?
Most likely the pod is not using the IRSA service account. Check that the Deployment spec sets serviceAccountName to the annotated service account and not default. Also confirm the service account has the eks.amazonaws.com/role-arn annotation. If both look correct, verify the OIDC provider is associated with the cluster and that the trust policy sub condition matches the exact namespace and service account name.
Can multiple deployments share one IRSA service account?
Yes. Multiple Deployments in the same namespace can reference the same service account and will all assume the same IAM role. The service account is the permission boundary, not the Deployment. If two workloads need different AWS permissions, give them different service accounts and different IAM roles.
Do I still need Kubernetes RBAC if I use IRSA?
Yes. IRSA controls what AWS resources a pod can access. Kubernetes RBAC controls what a pod can do inside the cluster — reading Secrets, listing pods, creating ConfigMaps. These are separate permission systems and both matter for a well-secured cluster.