Skip to content
Start with Identity
Recipe · Advanced · 60 min

Kubernetes workload identity: EKS Pod Identity or IRSA, and GKE Workload Identity Federation

Give each Kubernetes service account its own short-lived cloud identity on EKS and GKE, so pods stop using node credentials or mounted keys.

KubernetesAmazon EKSGKEIAM
Before you start
  • An EKS cluster on Linux EC2 nodes and/or a GKE Standard or Autopilot cluster
  • kubectl access to the cluster and IAM admin rights in the AWS account or Google Cloud project
  • Current AWS CLI, eksctl, and gcloud
  • A test namespace and a workload that uses the AWS SDK or Google Cloud client libraries
Implements

Kubernetes workload identity maps a Kubernetes service account to a cloud IAM identity, so each pod gets short-lived credentials for exactly its own permissions instead of borrowing the node's role or reading a mounted key file. On Amazon EKS you configure this with EKS Pod Identity (or the older IRSA), and on GKE with Workload Identity Federation for GKE. The end state: aws sts get-caller-identity inside a pod returns the pod's own role, a pod on GKE gets tokens only for its own principal, and node credentials are out of reach. For the concepts, see workload identity 101 and the Kubernetes identity security guide.

Before you start: Pod Identity or IRSA on EKS

AWS recommends EKS Pod Identity whenever possible. The differences that decide it:

Factor EKS Pod Identity IRSA
Trust setup One trust to pods.eks.amazonaws.com, reusable in any cluster Trust policy edited per cluster OIDC provider
Scale limits Up to 5,000 associations per cluster 100 OIDC providers per account by default; trust policy size typically fits about 4 to 8 service accounts
Session tags (ABAC) Yes: cluster, namespace, service account No
Where it runs EKS on Linux EC2 nodes only EKS, EKS Anywhere, Red Hat OpenShift Service on AWS, self-managed on EC2

Pod Identity is not available on Fargate, Windows EC2 nodes, Outposts, or EKS Anywhere. Those workloads stay on IRSA.

1. EKS: install the Pod Identity Agent

Skip this on EKS Auto Mode, where the agent is built in. Otherwise the node role needs eks-auth:AssumeRoleForPodIdentity (included in AmazonEKSWorkerNodePolicy), and private-subnet nodes need a PrivateLink endpoint for the EKS Auth API (agent setup).

aws eks create-addon --cluster-name my-cluster --addon-name eks-pod-identity-agent
kubectl get pods -n kube-system | grep 'eks-pod-identity-agent'

Check: one eks-pod-identity-agent-* pod per node in Running. Common failure: the agent fails to start on nodes with IPv6 disabled. It listens on 169.254.170.23 and [fd00:ec2::23]; AWS documents a setting to disable its IPv6 listener. Rollback: aws eks delete-addon --cluster-name my-cluster --addon-name eks-pod-identity-agent.

2. EKS: create the role and association

trust-relationship.json, from the association guide:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "pods.eks.amazonaws.com" },
    "Action": ["sts:AssumeRole", "sts:TagSession"]
  }]
}
aws iam create-role --role-name orders-reader \
    --assume-role-policy-document file://trust-relationship.json
aws iam attach-role-policy --role-name orders-reader \
    --policy-arn arn:aws:iam::111122223333:policy/orders-read
aws eks create-pod-identity-association --cluster-name my-cluster \
    --namespace orders --service-account orders-api \
    --role-arn arn:aws:iam::111122223333:role/orders-reader

No annotation goes on the service account. Use the session tags in trust or permission conditions if one role serves several namespaces.

Check: aws eks list-pod-identity-associations --cluster-name my-cluster lists the namespace, service account, and role. Common failure: the role is missing from the console picker because its trust policy does not match the one above (both sts:AssumeRole and sts:TagSession). Associations are also eventually consistent, so allow several seconds before testing. Rollback: aws eks delete-pod-identity-association --cluster-name my-cluster --association-id <id>.

3. EKS alternative: IRSA

Where Pod Identity is unavailable, create the cluster's IAM OIDC provider and a role trusted by it (IRSA guide):

eksctl utils associate-iam-oidc-provider --cluster my-cluster --approve
eksctl create iamserviceaccount --name orders-api --namespace orders \
    --cluster my-cluster --role-name orders-reader-irsa \
    --attach-policy-arn arn:aws:iam::111122223333:policy/orders-read --approve

eksctl writes a trust policy with StringEquals on <oidc-provider>:aud = sts.amazonaws.com and <oidc-provider>:sub = system:serviceaccount:orders:orders-api, and annotates the service account with eks.amazonaws.com/role-arn.

Check: kubectl describe serviceaccount orders-api -n orders shows the eks.amazonaws.com/role-arn annotation. Common failure: a StringLike sub of system:serviceaccount:orders:* lets every service account in the namespace assume the role. Keep it exact unless that is intended. Rollback: eksctl delete iamserviceaccount --name orders-api --namespace orders --cluster my-cluster.

4. EKS: block node credentials

AWS notes that if you don't restrict pod access to the node's instance metadata service, pods can still use the node IAM role and possibly other pods' credentials on that node (credential isolation). Apply the EKS best-practices guidance to restrict IMDS from pods. Pods with hostNetwork: true always keep IMDS access.

Check: from a non-host-network pod, a request to the IMDS credentials path fails. Common failure: node add-ons that relied on the node role break. Move them to their own association first. Rollback: restore the previous launch template metadata settings.

5. GKE: enable Workload Identity Federation for GKE

Autopilot clusters always have it on. For Standard clusters (how-to):

gcloud container clusters update my-cluster --location=us-central1 \
    --workload-pool=PROJECT_ID.svc.id.goog
gcloud container node-pools update default-pool --cluster=my-cluster \
    --location=us-central1 --workload-metadata=GKE_METADATA

Enabling it on the cluster leaves existing node pools unaffected; new pools get it. Updating a pool takes effect immediately and stops its workloads from using the node's service account, so update pools one at a time.

Check: gcloud container node-pools describe default-pool --cluster=my-cluster --location=us-central1 shows workloadMetadataConfig mode GKE_METADATA. Common failure: workloads that silently relied on the node service account start getting 403s after the pool update. Rollback: update the node pool with --workload-metadata=GCE_METADATA to return to node credentials while you fix grants.

6. GKE: grant roles to the Kubernetes service account

Grant IAM roles directly to the principal identifier:

kubectl create serviceaccount orders-api --namespace orders
gcloud projects add-iam-policy-binding projects/PROJECT_ID \
    --role=roles/storage.objectViewer \
    --member=principal://iam.googleapis.com/projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/orders/sa/orders-api \
    --condition=None

Where a Google Cloud API has limitations with federated tokens, use the older linking method instead: grant roles/iam.workloadIdentityUser on an IAM service account to serviceAccount:PROJECT_ID.svc.id.goog[orders/orders-api] and annotate the Kubernetes service account with iam.gke.io/gcp-service-account=IAM_SA_NAME@PROJECT_ID.iam.gserviceaccount.com. Both pieces are required.

Identity sameness is the trap: the same namespace and service account name in any cluster of the project is the same principal. Trust every cluster in the project or split sensitive workloads into their own project.

Check: gcloud projects get-iam-policy PROJECT_ID lists the principal:// member. Common failure: using the project ID where the identifier needs the project number, which yields a binding that never matches. Rollback: gcloud projects remove-iam-policy-binding with the same role and member.

Verify it works

On EKS, run a pod as the service account and check its identity:

kubectl run awscli -n orders --rm -it --image=amazon/aws-cli \
    --overrides='{"spec":{"serviceAccountName":"orders-api"}}' -- sts get-caller-identity

Expect arn:aws:sts::111122223333:assumed-role/orders-reader/.... A node role ARN means the association or SDK version is wrong. kubectl describe pod should show AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE for Pod Identity. Recreate pods that started before the association.

On GKE, from a pod using orders-api (Standard clusters add nodeSelector: iam.gke.io/gke-metadata-server-enabled: "true"):

curl -H "Metadata-Flavor: Google" \
  http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token

A token response means the GKE metadata server is serving the pod's identity. Then call an API the role allows and one it does not; expect success and a 403. The metadata server takes a few seconds to start on a new pod, so retry before declaring failure.

Frequently asked questions

Should I use EKS Pod Identity or IRSA?
AWS recommends EKS Pod Identity whenever possible. Use IRSA where Pod Identity is not available, such as EKS Anywhere or self-managed Kubernetes on EC2; Pod Identity also does not support Fargate pods or Windows nodes.
How do I give a GKE pod access to Google Cloud APIs without a service account key?
Enable Workload Identity Federation for GKE on the cluster and node pool, then grant IAM roles directly to the principal identifier for the namespace and Kubernetes service account. The GKE metadata server hands the pod short-lived tokens.
Do pods still get the node's IAM role with workload identity?
On EKS, yes, unless you restrict pod access to the instance metadata service. On GKE node pools with GKE_METADATA, workloads can no longer use the node's service account.
Are workload identities shared across clusters?
On GKE, workloads with the same namespace and service account name in different clusters of one project share an identity. On EKS, Pod Identity associations are per cluster, while one IAM role can be reused across clusters without trust policy changes.
Last reviewed By SWI Community TeamSuggest a correctionHow we research
Free to read and use. Code is illustrative, vendor-neutral, and built on the open standards. Review it against your provider's docs and your threat model before shipping. See more in the recipes index and the standards library.