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

Source: https://startwithidentity.com/recipes/kubernetes-workload-identity-eks-gke/
Last updated: 2026-11-05
License: content by Start with Identity. Cite the source URL.

---

Kubernetes [workload identity](https://startwithidentity.com/glossary/workload-identity/) maps a Kubernetes [service account](https://startwithidentity.com/glossary/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](https://startwithidentity.com/guides/machine-identity/workload-identity-101/) and the [Kubernetes identity security guide](https://startwithidentity.com/guides/machine-identity/kubernetes-identity-security-guide/).

## Before you start: Pod Identity or IRSA on EKS

[AWS recommends EKS Pod Identity](https://docs.aws.amazon.com/eks/latest/userguide/service-accounts.html) 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](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html) 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](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-agent-setup.html)).

```bash
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](https://docs.aws.amazon.com/eks/latest/userguide/pod-id-association.html):

```json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Service": "pods.eks.amazonaws.com" },
    "Action": ["sts:AssumeRole", "sts:TagSession"]
  }]
}
```

```bash
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](https://docs.aws.amazon.com/eks/latest/userguide/associate-service-account-role.html)):

```bash
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](https://startwithidentity.com/glossary/instance-metadata-service/), pods can still use the node IAM role and possibly other pods' credentials on that node ([credential isolation](https://docs.aws.amazon.com/eks/latest/userguide/pod-identities.html)). Apply the [EKS best-practices guidance](https://docs.aws.amazon.com/eks/latest/best-practices/identity-and-access-management.html) 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](https://docs.cloud.google.com/kubernetes-engine/docs/how-to/workload-identity)):

```bash
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:

```bash
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](https://docs.cloud.google.com/kubernetes-engine/docs/concepts/workload-identity) 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:

```bash
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"`):

```bash
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.

## Related

- [Workload identity 101](https://startwithidentity.com/guides/machine-identity/workload-identity-101/)
- [Kubernetes identity security guide](https://startwithidentity.com/guides/machine-identity/kubernetes-identity-security-guide/)
- [GitHub Actions OIDC to AWS and Azure](https://startwithidentity.com/recipes/github-actions-oidc-aws-azure/)
- [Workload identity](https://startwithidentity.com/glossary/workload-identity/), [service account](https://startwithidentity.com/glossary/service-account/), [instance metadata service](https://startwithidentity.com/glossary/instance-metadata-service/)
- [WIMSE](https://startwithidentity.com/standards/wimse/)
