IAM roles for service accounts (IRSA) ties every role's trust policy to a specific cluster's OIDC provider, so roles sprawl, trust policies fill up and nobody is sure which clusters can still assume which roles. EKS Pod Identity moves the binding out of IAM trust policies and into EKS associations: one role trusts the pods.eks.amazonaws.com service principal, and EKS decides which namespace and service account receive it. This guide is the migration: checking which workloads can move, installing the agent, creating associations alongside IRSA, cutting each workload over, proving the new path in CloudTrail, and only then removing the OIDC trust. It is for platform engineers, Kubernetes administrators and cloud security teams running Amazon EKS on Linux EC2 nodes. The wider EKS identity model is in the EKS Identity Hardening Guide; this page goes deeper on the move itself.
Key takeaways
- Not every workload can move. Pod Identity supports EKS clusters with Linux EC2 worker nodes; Fargate, Windows nodes, Outposts, EKS Anywhere and self-managed Kubernetes stay on IRSA.
- The cutover is safe by design. AWS SDKs stop at the first valid credential in the default chain, so existing credentials keep being used after you create an association; you can stage the change and switch per workload.
- The security boundary moves from IAM to EKS. Anyone who can create or update Pod Identity associations in a cluster can hand any role that trusts pods.eks.amazonaws.com to any service account; restrict that permission and add trust policy conditions on session tags.
- Session tags enable one role for many workloads. eks-cluster-name, kubernetes-namespace and kubernetes-service-account can be used in ABAC conditions, but they also widen a role's reach if you rely on tags you have not tested.
- Finish the job. Remove the OIDC provider trust, the eks.amazonaws.com/role-arn annotation and, eventually, the IAM OIDC provider, or you are running two trust paths instead of one.
When you need this
- Role sprawl: the same role is needed in several clusters and its trust policy, at the default 2,048 characters, fits roughly four OIDC providers (about eight with an increased quota), according to AWS.
- Provider limits: each IRSA cluster needs its own IAM OIDC provider, with a default limit of 100 per account.
- STS throttling: with IRSA, pods call AssumeRoleWithWebIdentity themselves; with Pod Identity, EKS assumes the role and the node agent distributes credentials.
- Separation of duties: you want IAM teams to own permissions and platform teams to own which workloads get them, instead of both editing trust policies.
- Cluster rebuilds: blue/green cluster upgrades with IRSA require every role's trust policy to be updated for the new cluster's OIDC issuer.
Before you start
| Check | Requirement | How to confirm (read-only) |
|---|---|---|
| Node type | Linux EC2 nodes (managed, self-managed node groups or Auto Mode) | kubectl get nodes -o wide; exclude Fargate profiles and Windows node groups |
| Cluster version | All supported versions; Kubernetes 1.28 needs platform version eks.4 or later | aws eks describe-cluster --name <cluster> --query cluster.platformVersion |
| SDK version in each image | For example Boto3/botocore 1.34.41, AWS CLI v2 2.15.0, Java v2 2.21.30, Go v2 release-2023-11-14, JavaScript v3 3.458.0, .NET 3.7.734.0 or later | Software bill of materials or dependency lock files per image |
| Node role permission | eks-auth:AssumeRoleForPodIdentity (included in AmazonEKSWorkerNodePolicy) | aws iam list-attached-role-policies on the node role |
| Network | Nodes reach the EKS Auth API; private subnets need an AWS PrivateLink interface endpoint for EKS Auth | VPC endpoint list; test from a node |
| Proxies and IPv6 | 169.254.170.23 and [fd00:ec2::23] in NO_PROXY; agent cannot start if IPv6 localhost is blocked unless IPv6 is disabled for it | Review proxy environment in pod specs and node configuration |
| Security groups for pods | VPC CNI with ENABLE_POD_ENI=true needs POD_SECURITY_GROUP_ENFORCING_MODE=standard | kubectl -n kube-system get ds aws-node -o yaml |
IRSA and Pod Identity compared
| Attribute | IRSA | EKS Pod Identity |
|---|---|---|
| Trust | Role trusts the cluster's IAM OIDC provider; condition on the service account subject | Role trusts pods.eks.amazonaws.com with sts:AssumeRole and sts:TagSession; EKS association names namespace and service account |
| Binding lives in | IAM trust policy plus service account annotation | EKS Pod Identity association (up to 5,000 per cluster) |
| Credential delivery | Pod exchanges a projected token with STS AssumeRoleWithWebIdentity | Agent DaemonSet on each node (ports 80 and 2703 on 169.254.170.23) calls AssumeRoleForPodIdentity |
| Pod environment | Web identity token file and role ARN variables | AWS_CONTAINER_CREDENTIALS_FULL_URI=http://169.254.170.23/v1/credentials and AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE (token audience pods.eks.amazonaws.com, 24-hour expiry) |
| ABAC | No session tags | Session tags: eks-cluster-arn, eks-cluster-name, kubernetes-namespace, kubernetes-service-account, kubernetes-pod-name, kubernetes-pod-uid |
| Cross-account | Role in the other account trusts the cluster's OIDC provider | Association role in the cluster's account, with an optional target role in another account (targetRoleARN in eksctl) |
| Environments | EKS, EKS Anywhere, Red Hat OpenShift Service on AWS, self-managed Kubernetes on EC2 | EKS on Linux EC2 nodes only |
Step-by-step
Steps 1 and 2 are read-only. Steps 3 to 6 are additive. Steps 7 and 8 remove the old trust path and are destructive.
- Inventory IRSA bindings. List service accounts carrying the IRSA annotation and the roles they reference:
For each role, read its trust policy (aws iam get-role --role-name <role> --query Role.AssumeRolePolicyDocument) and record every OIDC provider and subject condition. Roles trusted by several clusters, and conditions using StringLike with wildcards, are the ones to tighten during migration.kubectl get sa -A -o json | jq -r '.items[] | select(.metadata.annotations["eks.amazonaws.com/role-arn"] != null) | [.metadata.namespace, .metadata.name, .metadata.annotations["eks.amazonaws.com/role-arn"]] | @tsv' - Plan with eksctl if you use it.
eksctl utils migrate-to-pod-identity --cluster <cluster>without --approve only prints a plan: install the agent add-on, add the pods.eks.amazonaws.com trust to identified roles, create associations for iamserviceaccounts and migrate supported add-ons. Review the plan rather than approving it blind, because it edits role trust policies, including roles eksctl does not own. - Install the agent. Skip this on EKS Auto Mode, where it is preinstalled.
Pin an add-on version you have tested; AWS's example uses v1.0.0-eksbuild.1 and newer builds exist.aws eks create-addon --cluster-name <cluster> --addon-name eks-pod-identity-agent kubectl get pods -n kube-system | grep eks-pod-identity-agent - Add the Pod Identity trust to the role, scoped by session tags. Add a statement alongside the existing OIDC statement, so both paths work during the cutover:
The condition means a mistaken association in any cluster in the account cannot hand this role to a different workload. Add an aws:RequestTag/eks-cluster-name condition if the role should be usable from named clusters only.{ "Sid": "AllowEksAuthToAssumeRoleForPodIdentity", "Effect": "Allow", "Principal": { "Service": "pods.eks.amazonaws.com" }, "Action": ["sts:AssumeRole", "sts:TagSession"], "Condition": { "StringEquals": { "aws:RequestTag/kubernetes-namespace": ["payments"], "aws:RequestTag/kubernetes-service-account": ["payments-api"] } } } - Create the association.
Running pods are unaffected at this point: the pod spec changes are added when a pod is created, and SDKs keep using the IRSA credentials found earlier in the chain. Allow a few seconds for the association to become consistent.aws eks create-pod-identity-association --cluster-name <cluster> \ --namespace payments --service-account payments-api \ --role-arn arn:aws:iam::111122223333:role/payments-api - Cut the workload over. Remove the eks.amazonaws.com/role-arn annotation from the service account and restart the workload (for example kubectl rollout restart deployment/payments-api -n payments) in a pilot environment first. Then verify inside a new pod:
The first command should show AWS_CONTAINER_CREDENTIALS_FULL_URI and AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE and no web identity variables; the second needs the AWS CLI in the image. Roll out by environment and keep the old trust statement until production has run for a full cycle.kubectl exec -n payments deploy/payments-api -- env | grep AWS_CONTAINER kubectl exec -n payments deploy/payments-api -- aws sts get-caller-identity - Remove the OIDC trust (destructive). When no AssumeRoleWithWebIdentity calls for the role have appeared in CloudTrail over the observation window, delete the OIDC statement from the trust policy. With eksctl, --remove-oidc-provider-trust-relationship does this for iamserviceaccounts; for add-ons eksctl always removes it.
- Retire the OIDC provider (destructive). Once no role in the account trusts the cluster's IAM OIDC provider, delete the provider. Keep it if anything else (for example a non-AWS consumer of the cluster's tokens) depends on it.
Decision guidance
| Situation | Choice |
|---|---|
| Linux EC2 nodes, supported SDKs, one AWS account | Migrate; the main gain is reusable roles and simpler cluster rebuilds |
| Fargate, Windows nodes, EKS Anywhere, ROSA or self-managed clusters | Stay on IRSA for those workloads; tighten trust conditions to exact subjects |
| Image with an old SDK you cannot rebuild yet | Stay on IRSA until rebuilt; an old SDK will not find the container credential provider |
| Many similar workloads needing the same access to their own data | One role with ABAC on kubernetes-namespace or eks-cluster-name session tags, tested with real requests |
| Session policies or very large policies | Disable session tags on the association (needed for session policies, and the fix for PackedPolicyTooLarge), and use one role per workload |
| Resources in another account | Use the target role option rather than putting cross-account trust on the association role |
Small estates with a few clusters can migrate by hand in a day per cluster. Large estates should manage associations in infrastructure as code next to the role definitions, and treat the eks:CreatePodIdentityAssociation and eks:UpdatePodIdentityAssociation permissions as privileged, because that is now where workload-to-role binding is decided.
Verify it worked
- Pods: new pods carry the Pod Identity variables and token volume; get-caller-identity returns the expected role session.
- CloudTrail: the role is assumed through EKS Pod Identity, and AssumeRoleWithWebIdentity events for the role stop after cutover.
- Associations:
aws eks list-pod-identity-associations --cluster-name <cluster>matches the inventory from step 1, one entry per migrated service account. - Negative test: a pod in another namespace using a service account without an association receives no role credentials.
Rollback and common errors
- Workload fails after cutover: while the OIDC trust is still on the role, re-add the eks.amazonaws.com/role-arn annotation and restart; the pod returns to IRSA.
- Pod still uses IRSA after the association: expected until the annotation is removed and the pod is recreated, because earlier credentials in the chain win.
- No credentials, connection refused to 169.254.170.23: the agent is not running on that node, a proxy is intercepting the call, or IPv6 is blocked and the agent failed to start.
- Access denied on AssumeRole: the trust policy lacks sts:TagSession, or a session tag condition does not match the namespace or service account.
- Agent cannot get credentials in private subnets: missing PrivateLink endpoint for EKS Auth, or the node role lacks eks-auth:AssumeRoleForPodIdentity.
- PackedPolicyTooLarge: disable session tags on that association.
Common failure patterns
- Two trust paths forever: associations are added but the OIDC trust statements and providers are never removed, so a compromised or recreated cluster can still use IRSA.
- Unconditioned pods.eks.amazonaws.com trust: a role trusting the service principal with no session tag conditions can be bound to any service account in any cluster in the account by whoever can create associations.
- Broad association rights: eks:CreatePodIdentityAssociation granted to all cluster operators makes them able to give any eligible role to their own pods.
- Ignoring the node: containers are not a security boundary and Pod Identity does not change that. Pods with hostNetwork: true can still reach IMDS and the node role; restrict them with admission policy and IMDSv2 hop limits. See the EC2 Instance Metadata Service Hardening Guide.
- ABAC without testing: an S3 policy keyed on ${aws:PrincipalTag/eks-cluster-name} silently grants every workload in the cluster access to the cluster's data if the resource tags are broad.
Detection and measurement
- Count of IRSA-annotated service accounts per cluster (target: only workloads that cannot migrate).
- Roles that still trust an IAM OIDC provider after migration, and OIDC providers with no trusting roles.
- Roles trusting pods.eks.amazonaws.com without namespace and service account conditions (target zero).
- Pod Identity association changes outside infrastructure as code, from CloudTrail (target zero).
- AssumeRoleWithWebIdentity calls per day for migrated roles (should reach zero).
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev 5 | IA-9 Service Identification and Authentication; AC-6 Least Privilege | Workload-specific roles bound by namespace and service account |
| NIST SP 800-53 Rev 5 | AC-2 Account Management; CM-8 System Component Inventory | Association inventory and removal of unused trust paths |
| NIST CSF 2.0 | PR.AA-01; PR.AA-05 | Service identities managed; permissions enforced and reviewed |
| MITRE ATT&CK v20 | T1552.005 Cloud Instance Metadata API; T1078.004 Cloud Accounts | Node credential theft and abuse of workload roles |
Practitioner checklist
- Inventory IRSA annotations and every trust policy statement per role.
- Exclude Fargate, Windows and non-EKS workloads, and images below the minimum SDK versions.
- Install and pin the eks-pod-identity-agent add-on; add the EKS Auth PrivateLink endpoint for private clusters.
- Add pods.eks.amazonaws.com trust with kubernetes-namespace and kubernetes-service-account conditions.
- Create associations, then remove annotations and restart per workload, pilot first.
- Confirm new credentials in pods and CloudTrail before removing OIDC trust.
- Remove OIDC trust statements and unused IAM OIDC providers.
- Restrict who can create or update Pod Identity associations and manage them as code.
Standards and references
- AWS: Learn how EKS Pod Identity grants pods access to AWS services
- AWS: Comparing EKS Pod Identity and IAM roles for service accounts
- AWS: Understand how EKS Pod Identity works
- AWS: Set up the Amazon EKS Pod Identity Agent
- AWS: Create IAM role with trust policy required by EKS Pod Identity
- AWS: Grant pods access to AWS resources based on tags
- AWS: Use pod identity with the AWS SDK
- eksctl: EKS Pod Identity Associations
Related NHI Mgmt Group resources: EKS Identity Hardening Guide · Amazon EKS Security Hardening Guide · Cloud Workload Identity Guide · Multi-Cloud Workload Identity Troubleshooting Guide