Amazon EKS

Goodbye IRSA, hello EKS Pod Identity: why you should migrate in 2026

IRSA was the gold standard for zero-trust pod-to-AWS access. But an OIDC provider per cluster and trust-policy sprawl across dozens of clusters is an operational tax no platform team wants to keep paying. Here is what Pod Identity actually changes — and where IRSA still earns its keep.

Deep dive·platform & cloud engineers·

Scope & versions. Amazon EKS as documented mid-2026. Pod Identity requires the eks-pod-identity-agent add-on on Linux EC2 nodes and a cluster at Kubernetes 1.24+ (platform eks.4 on 1.28). Native cross-account chaining (source + target role) shipped June 2025. Re-verify the current add-on version, per-cluster association limit (5,000), and which EKS managed add-ons support Pod Identity against the AWS docs before you rely on them.

You are staring at a pod that will not talk to S3. CreateOIDCProvider ran in a different team's Terraform, the role's trust policy is a wall of oidc.eks.<region>.amazonaws.com/id/…:sub conditions, and you just spun up cluster number fourteen. Every new cluster means a new OIDC provider, a new set of trust-policy edits on a dozen shared roles, and a fresh chance to fat-finger a sub claim and lock a workload out. IAM Roles for Service Accounts (IRSA) was — and still is — a genuinely good security model. But at fleet scale the plumbing around it is the thing that pages you. EKS Pod Identity keeps the zero-trust guarantee and deletes most of the plumbing. This is what it does under the hood, and where it is not yet a drop-in replacement.

TL;DR

  • IRSA federates through IAM; Pod Identity federates through EKS. IRSA hands the pod a projected JWT and the SDK calls sts:AssumeRoleWithWebIdentity against a per-cluster OIDC provider. Pod Identity moves the exchange to a node-local agent that calls the new AssumeRoleForPodIdentity action on the EKS Auth API — no OIDC provider, no thumbprints.
  • One trust policy, reused everywhere. A Pod Identity role trusts a single principal, pods.eks.amazonaws.com, with no cluster-specific conditions — so the same role drops into cluster after cluster untouched. IRSA's trust policy is cluster-scoped and capped near four relationships by the 2,048-character limit.
  • Identity outlives the cluster. Associations are plain EKS API objects (CreatePodIdentityAssociation), decoupled from cluster lifecycle. Blue/green cluster replacement becomes "recreate the associations on green" — seconds of API calls, not an OIDC-provider rebuild and trust-policy migration.
  • Cross-account is now native. Since June 2025 an association can carry a source and a target role; EKS Auth does the sts:AssumeRole chaining for you, and the pod gets the other account's credentials with zero code change.
  • It is not a total replacement — yet. Pod Identity is EKS-on-Linux-EC2 only: no Fargate, no Windows, no EKS Anywhere/Outposts, and several EKS managed add-ons still authenticate via IRSA. The node-local agent is also a new dependency in the credential path. Expect both to coexist in one cluster for a while.
One idea: IRSA asks IAM to trust each cluster's identity provider. Pod Identity asks IAM to trust EKS once, and lets EKS vouch for the pod. That single move is where nearly all the operational savings come from.

How IRSA really works — and why it taxes platform teams

IRSA is OpenID Connect federation wearing a Kubernetes hat. When you enable it, EKS exposes an OIDC issuer URL for the cluster, and you register that issuer as an IAM OIDC identity provider in your account. From then on, the pieces line up like any web-identity flow:

  • A projected service-account token. The kubelet mounts a signed, short-lived JWT into the pod at /var/run/secrets/eks.amazonaws.com/serviceaccount/token, with audience sts.amazonaws.com and a sub claim of system:serviceaccount:<namespace>:<serviceaccount>. The token is auto-rotated by the projected-volume machinery.
  • An SDK that knows the web-identity dance. The AWS SDK finds two env vars EKS injects — AWS_ROLE_ARN and AWS_WEB_IDENTITY_TOKEN_FILE — reads the token, and calls sts:AssumeRoleWithWebIdentity.
  • A role whose trust policy names the cluster's provider. STS validates the JWT signature against the OIDC provider, checks the trust policy's sub/aud conditions, and returns temporary credentials scoped to that role.

That trust policy is the part that does not scale gracefully. It is bound to one cluster's OIDC provider ARN:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": {
      "Federated": "arn:aws:iam::111122223333:oidc-provider/oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE"
    },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": {
        "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com",
        "oidc.eks.us-east-1.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:payments:checkout-sa"
      }
    }
  }]
}

And the paired ServiceAccount that opts a workload into it:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: checkout-sa
  namespace: payments
  annotations:
    # IRSA: the SDK reads this to know which role to assume
    eks.amazonaws.com/role-arn: arn:aws:iam::111122223333:role/checkout-irsa

Now multiply. Every cluster has its own issuer, so every cluster needs its own IAM OIDC provider — and IAM caps you at 100 OIDC providers per account by default. Every role a workload uses in a new cluster needs that cluster's provider ARN and sub conditions added to its trust policy. IAM trust policies top out at 2,048 characters, which in practice holds only about four of these relationships before you are creating duplicate roles just to hold more trust statements. Multi-tenant platform teams running dozens of ephemeral clusters end up with trust-policy sprawl, provider-limit anxiety, and a migration chore every time a cluster is born or replaced.

Myth check — thumbprints. Older IRSA guides told you to fetch and pin the issuer's TLS thumbprint. AWS now manages OIDC thumbprints for EKS issuers automatically, so that particular chore has mostly evaporated — but the per-cluster provider and the trust-policy editing have not. Those are the real tax.

The paradigm shift — a node agent and the EKS Auth API

Pod Identity keeps the good part of IRSA (short-lived, per-workload credentials, no long-lived keys) and swaps out the federation mechanics. Two new pieces do the work.

The eks-pod-identity-agent DaemonSet

Installed as an EKS add-on, the agent runs one pod per node using the node's hostNetwork. It listens on the link-local address 169.254.170.23 (and [fd00:ec2::23] for IPv6) on ports 80 and 2703. This is the same "container credential provider" pattern ECS uses with the amazon-ecs-agent — it is a step in the AWS SDK's standard credential chain, which is why supported SDKs pick it up with no application changes.

The association, and the env vars EKS injects

When a pod uses a service account that has a Pod Identity association, EKS mutates the pod at admission time. It projects a service-account token with audience pods.eks.amazonaws.com (24-hour expiry) and injects two env vars the SDK understands:

# injected automatically into the pod's containers
AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials
AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE=/var/run/secrets/pods.eks.amazonaws.com/serviceaccount/eks-pod-identity-token

The credential resolution then runs entirely below your code:

  1. The SDK reads AWS_CONTAINER_CREDENTIALS_FULL_URI and does an HTTP GET to the agent on 169.254.170.23, presenting the projected token as the authorization.
  2. The agent calls AssumeRoleForPodIdentity on the EKS Auth API, which validates the token, looks up the association, and returns temporary credentials for the mapped IAM role.
  3. The agent hands those credentials back to the SDK. Because the EKS Auth service performs the assume-role, the expensive exchange happens once per node rather than once per pod — that is the scalability story AWS calls out.

The role side collapses to a single, cluster-agnostic trust policy naming the new EKS service principal. Notice it also allows sts:TagSession, which is what lets EKS attach session tags for ABAC (more on that below):

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "pods.eks.amazonaws.com" },
    "Action": ["sts:AssumeRole", "sts:TagSession"]
  }]
}
Safe, incremental migration. The container-credential provider sits after most other sources in the SDK's default chain. If a workload already has IRSA credentials, those keep winning even after you create a Pod Identity association for the same service account — so you can create associations first, verify, and remove the IRSA annotation only when you are ready. No flag day.

Setting it up — add-on, association, workload

Three moves: install the agent once per cluster, create an association per set of permissions, and point a service account at it. Here is the whole thing across the CLI, Terraform, and eksctl.

1 · Install the Pod Identity Agent add-on (once per cluster)

aws eks create-addon \
  --cluster-name prod-use1 \
  --addon-name eks-pod-identity-agent

# confirm it is ACTIVE, then check the DaemonSet
aws eks describe-addon --cluster-name prod-use1 \
  --addon-name eks-pod-identity-agent --query 'addon.status'
kubectl get daemonset eks-pod-identity-agent -n kube-system
resource "aws_eks_addon" "pod_identity_agent" {
  cluster_name = aws_eks_cluster.this.name
  addon_name   = "eks-pod-identity-agent"
  # pin a version in production; omit to take the cluster default
  # addon_version = "v1.3.8-eksbuild.2"
}
eksctl create addon \
  --cluster prod-use1 \
  --name eks-pod-identity-agent
EKS Auto Mode clusters ship the agent built in — skip this step there.

2 · Create the role + association

The role trusts pods.eks.amazonaws.com (the trust policy above) and carries whatever permissions the workload needs. Then map it to a namespace/service-account pair:

aws eks create-pod-identity-association \
  --cluster-name prod-use1 \
  --namespace payments \
  --service-account checkout-sa \
  --role-arn arn:aws:iam::111122223333:role/checkout-pod-identity
resource "aws_eks_pod_identity_association" "checkout" {
  cluster_name    = aws_eks_cluster.this.name
  namespace       = "payments"
  service_account = "checkout-sa"
  role_arn        = aws_iam_role.checkout.arn
}

data "aws_iam_policy_document" "trust" {
  statement {
    effect  = "Allow"
    actions = ["sts:AssumeRole", "sts:TagSession"]
    principals {
      type        = "Service"
      identifiers = ["pods.eks.amazonaws.com"]
    }
  }
}

resource "aws_iam_role" "checkout" {
  name               = "checkout-pod-identity"
  assume_role_policy = data.aws_iam_policy_document.trust.json
}
eksctl create podidentityassociation \
  --cluster prod-use1 \
  --namespace payments \
  --service-account-name checkout-sa \
  --permission-policy-arns arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
  --role-name checkout-pod-identity
eksctl can create the role for you from --permission-policy-arns; the CLI/Terraform paths expect a role you already made.

3 · The workload — a plain ServiceAccount, no annotation

This is the quiet part of the win: the service account has no IAM annotation at all. The binding lives in the association, not on the object.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: checkout-sa
  namespace: payments
  # note: no eks.amazonaws.com/role-arn annotation needed
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: checkout
  namespace: payments
spec:
  replicas: 3
  selector: { matchLabels: { app: checkout } }
  template:
    metadata:
      labels: { app: checkout }
    spec:
      serviceAccountName: checkout-sa      # <- the association matches on this
      containers:
        - name: checkout
          image: 111122223333.dkr.ecr.us-east-1.amazonaws.com/checkout:1.14
          # AWS SDK finds credentials via the injected env vars automatically
Checkpoint: kubectl exec into a pod and run env | grep AWS_CONTAINER. If you see AWS_CONTAINER_CREDENTIALS_FULL_URI, the association matched and the agent is serving credentials. If not, the pod started before the association existed — restart it, since env injection happens at pod admission.

The SRE win — identity that outlives the cluster

The reason platform teams care is not the setup ergonomics; it is what happens on the bad days. Because associations are EKS API objects and the role trusts EKS rather than a specific cluster's issuer, IAM configuration is fully decoupled from cluster lifecycle. That changes two operations that used to be miserable.

Blue/green cluster replacement becomes an API loop

The modern way to upgrade or re-platform an EKS cluster is not in place — it is to stand up a parallel "green" cluster, shift traffic, and tear down "blue." Under IRSA, green is a brand-new OIDC issuer: you register a new IAM OIDC provider, then edit every shared role's trust policy to add green's provider ARN and sub conditions, and reverse it later. Under Pod Identity, the roles never change. You just recreate the same associations against the green cluster — a scripted loop of create-pod-identity-association calls — and the identical workloads get the identical permissions the moment they schedule.

Cross-account access, now native

Historically, cross-account was the case people cited to keep IRSA, because getting a pod into another account meant manual role chaining. As of June 2025, a Pod Identity association can carry both a source role (in the cluster's account) and a target role (in the resource's account). EKS Auth performs the sts:AssumeRole chaining, and the pod receives the target account's credentials with no application change — you set the target role's trust to allow the source role, and you are done.

Session tags for ABAC come for free

Because the trust policy allows sts:TagSession, EKS attaches session tags to the credentials it mints — including the cluster name, namespace, service-account name, and pod details. You can write attribute-based access control (ABAC) conditions against those tags in your permission policies (for example, only allow access to objects prefixed with the workload's namespace). IRSA can do ABAC too, but it does not populate these tags automatically — you build the plumbing yourself.

Trade-offs and what still needs IRSA

Pod Identity is the right default for new EC2-backed workloads on EKS, but it is not universal. Keep IRSA in your toolbox for these cases:

  • Anything that is not EKS-on-Linux-EC2. Pod Identity requires the DaemonSet agent, and Fargate does not run DaemonSets — so Fargate pods cannot use it and must stay on IRSA. Same story for Windows nodes, EKS Anywhere, AWS Outposts, and any self-managed Kubernetes on EC2. IRSA works in all of those.
  • Several EKS managed add-ons still use IRSA. Add-ons installed through the EKS API historically wire their permissions via IRSA; support for Pod Identity is arriving add-on by add-on (the FSx and EBS/EFS CSI drivers, for instance, now support it). Check each add-on before assuming it can drop its OIDC provider.
  • The agent is a new dependency in the hot path. If the DaemonSet is unhealthy on a node, pods there cannot fetch credentials. It is one more thing to monitor, keep patched, and (for proxied environments) add 169.254.170.23 to NO_PROXY so requests to it are not intercepted.
  • Eventual consistency. Associations can take a few seconds to propagate after creation. Don't create or mutate associations inside a critical, high-availability code path — do it in setup/init routines.
  • External or non-AWS OIDC consumers. If something outside AWS already relies on the cluster's OIDC issuer for federation, that lives on independently of how your pods get AWS credentials.
IRSA vs EKS Pod Identity, side by side. Figures are AWS-documented defaults as of mid-2026 — re-verify before relying on them.
DimensionIRSAEKS Pod Identity
Federation mechanismIAM OIDC provider + sts:AssumeRoleWithWebIdentityEKS Auth API + AssumeRoleForPodIdentity
Per-cluster setupOne IAM OIDC provider per cluster (100/account cap)Install the agent add-on once; no OIDC provider
Role trust policyCluster-scoped; ~4 relationships per role (2,048-char limit)One statement trusting pods.eks.amazonaws.com, reused across clusters
Cluster-lifecycle couplingTight — new cluster means new provider + trust editsDecoupled — associations are EKS API objects
Cross-accountManual role chainingNative source + target role (since June 2025)
Session tags / ABACNot populated automaticallyCluster/namespace/SA tags attached automatically
Where it runsEKS EC2 & Fargate, Windows, EKS Anywhere, self-managedEKS on Linux EC2 only
Credential exchange costOnce per pod (SDK calls STS)Once per node (EKS Auth), agent fans out
Scale limitBounded by OIDC providers & trust-policy sizeUp to 5,000 associations per cluster

Pitfalls to check before you cut over

  • SDK too old. The container-credential provider only exists in recent AWS SDK versions. A stale SDK will ignore the injected env vars and silently fall back — or fail. Bump the SDK before you remove IRSA.
  • Pods started before the association. Env-var injection happens at pod admission, so an association created after a pod is running does nothing until the pod restarts. Roll the workload.
  • Proxy swallowing the agent call. In egress-proxied clusters, add 169.254.170.23 and [fd00:ec2::23] to no_proxy/NO_PROXY, or credential fetches time out.
  • IPv4/IPv6 mismatch. If your cluster has IPv6 disabled, disable it in the agent too, or it may bind the wrong link-local address.
  • Leftover IRSA still winning. Because IRSA sits earlier in the credential chain, a workload can keep using its old role even after you add an association — great for safe migration, confusing when you expected the new role. Remove the eks.amazonaws.com/role-arn annotation to finish the cut-over.
  • IMDS fallback. If the instance metadata service is reachable from pods, a misconfigured workload can fall through to the node role instead of failing loudly. Restrict IMDS access so Pod Identity is the only path to credentials.
Bottom line. Default new EC2 workloads to Pod Identity, migrate shared roles cluster-by-cluster behind the credential-chain safety net, and keep IRSA for Fargate, Windows, non-EKS, and the managed add-ons that still need it. You will delete a lot of OIDC providers and trust-policy JSON — and stop getting paged when a new cluster is born.

References

  1. AWS EKS User Guide — Learn how EKS Pod Identity grants pods access to AWS services (benefits, setup overview, limits, restrictions, cluster versions).
  2. AWS EKS User Guide — Understand how EKS Pod Identity works (agent, injected env vars, AssumeRoleForPodIdentity, credential chain).
  3. AWS EKS API Reference — AssumeRoleForPodIdentity action.
  4. AWS CLI Reference — create-pod-identity-association.
  5. AWS EKS User Guide — Set up the Amazon EKS Pod Identity Agent.
  6. AWS Containers Blog — Amazon EKS Pod Identity: a new way for applications on EKS to obtain IAM credentials (launch; OIDC/trust-policy limits, pods.eks.amazonaws.com).
  7. AWS Containers Blog — Amazon EKS Pod Identity streamlines cross-account access, and the target IAM role docs.
  8. AWS What's New (June 2025) — Amazon EKS Pod Identity simplifies the experience for cross-account access.
  9. AWS EKS User Guide — IAM roles for service accounts (IRSA: OIDC provider, projected token, sts:AssumeRoleWithWebIdentity).
  10. Datadog Security Labs — Deep dive into the new Amazon EKS Pod Identity feature (agent internals, session tags, credential path).
  11. eksctl User Guide — Pod Identity Associations.