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.

Analogy

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.

Security risk

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:

  1. 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.
  2. 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.
  3. 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.
  4. 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_ARN and AWS_WEB_IDENTITY_TOKEN_FILE) into the pod.
  5. 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.
  6. 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 \
  --approve

Step 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 \
  --approve

Step 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.0

The AWS SDK in your application detects and uses the injected credentials automatically. No code changes are needed.

Tip

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 is system: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 be sts.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-role

How 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-identity

The 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 return my-app-sa, not default.
  • Service account has the annotation. Run kubectl get sa my-app-sa -n production -o yaml and confirm eks.amazonaws.com/role-arn is 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 sub condition is an exact string match against system:serviceaccount:production:my-app-sa. A typo in either the namespace or service account name silently breaks the credential exchange.
Watch out

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:GetObject on that bucket only.
  • An application fetches one secret from Secrets Manager at startup. The role allows secretsmanager:GetSecretValue on 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:PutItem on 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 roleIRSAEKS Pod Identity
ScopeAll pods on the nodePer service accountPer service account
Credential typeEC2 instance profileShort-lived STS (OIDC)Short-lived STS (EKS Auth)
Least privilegeNoYesYes
OIDC provider requiredNoYes, per clusterNo
Trust policyEC2 service trustOIDC issuer trustEKS service trust
Setup complexityMinimalModerateLow (EKS API call)
Cluster versionAllAllEKS 1.24+

For a deeper look at how AssumeRoleWithWebIdentity and the STS trust model work, see AWS Role Assumption Explained.

Note

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

  1. Missing serviceAccountName in the pod spec. The most common mistake. The pod uses the default service account, which has no IRSA annotation, and falls through to the node’s instance profile credentials. Always set serviceAccountName explicitly in the Deployment spec.
  2. 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-arn annotation. eksctl handles this automatically; manual setups frequently miss it.
  3. OIDC provider not associated with the cluster. Token exchange fails silently if no OIDC provider is configured. Run eksctl utils associate-iam-oidc-provider before creating any IRSA resources. This is a one-time step per cluster.
  4. Trust policy does not match the actual namespace or service account name. The sub condition is an exact string match against system:serviceaccount:<namespace>:<name>. A typo in either value silently breaks credential exchange with no clear error message.
  5. Trust policy too broad (missing sub condition). 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.
  6. Earlier credentials taking precedence in the SDK chain. If the pod spec includes AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY, the SDK uses those before checking for the IRSA token. Remove any static credential environment variables from the pod spec.
  7. 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.

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.

Last verified: 4 April 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.