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.
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:AssumeRoleWithWebIdentityagainst a per-cluster OIDC provider. Pod Identity moves the exchange to a node-local agent that calls the newAssumeRoleForPodIdentityaction 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:AssumeRolechaining 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.
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 audiencests.amazonaws.comand asubclaim ofsystem: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_ARNandAWS_WEB_IDENTITY_TOKEN_FILE— reads the token, and callssts: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/audconditions, 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.
aud=sts.amazonaws.com, exchanged at STS
② per-cluster OIDC provider + trust-policy sprawl
③ node-local agent — one credential path per node
④ one reusable trust policy: pods.eks.amazonaws.com
workload / SDK token & STS EKS-managed identity plane IAM role (target) cluster infrastructure
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:
- The SDK reads
AWS_CONTAINER_CREDENTIALS_FULL_URIand does an HTTPGETto the agent on169.254.170.23, presenting the projected token as the authorization. - The agent calls
AssumeRoleForPodIdentityon the EKS Auth API, which validates the token, looks up the association, and returns temporary credentials for the mapped IAM role. - 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"]
}]
}
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
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
--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
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.
sts:AssumeRole to the target
③ target role in account B trusts the source role
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.23toNO_PROXYso 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.
| Dimension | IRSA | EKS Pod Identity |
|---|---|---|
| Federation mechanism | IAM OIDC provider + sts:AssumeRoleWithWebIdentity | EKS Auth API + AssumeRoleForPodIdentity |
| Per-cluster setup | One IAM OIDC provider per cluster (100/account cap) | Install the agent add-on once; no OIDC provider |
| Role trust policy | Cluster-scoped; ~4 relationships per role (2,048-char limit) | One statement trusting pods.eks.amazonaws.com, reused across clusters |
| Cluster-lifecycle coupling | Tight — new cluster means new provider + trust edits | Decoupled — associations are EKS API objects |
| Cross-account | Manual role chaining | Native source + target role (since June 2025) |
| Session tags / ABAC | Not populated automatically | Cluster/namespace/SA tags attached automatically |
| Where it runs | EKS EC2 & Fargate, Windows, EKS Anywhere, self-managed | EKS on Linux EC2 only |
| Credential exchange cost | Once per pod (SDK calls STS) | Once per node (EKS Auth), agent fans out |
| Scale limit | Bounded by OIDC providers & trust-policy size | Up 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.23and[fd00:ec2::23]tono_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-arnannotation 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.
References
- AWS EKS User Guide — Learn how EKS Pod Identity grants pods access to AWS services (benefits, setup overview, limits, restrictions, cluster versions).
- AWS EKS User Guide — Understand how EKS Pod Identity works (agent, injected env vars,
AssumeRoleForPodIdentity, credential chain). - AWS EKS API Reference — AssumeRoleForPodIdentity action.
- AWS CLI Reference — create-pod-identity-association.
- AWS EKS User Guide — Set up the Amazon EKS Pod Identity Agent.
- 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). - AWS Containers Blog — Amazon EKS Pod Identity streamlines cross-account access, and the target IAM role docs.
- AWS What's New (June 2025) — Amazon EKS Pod Identity simplifies the experience for cross-account access.
- AWS EKS User Guide — IAM roles for service accounts (IRSA: OIDC provider, projected token,
sts:AssumeRoleWithWebIdentity). - Datadog Security Labs — Deep dive into the new Amazon EKS Pod Identity feature (agent internals, session tags, credential path).
- eksctl User Guide — Pod Identity Associations.