Service account tokens are the cornerstone of pod authentication in Kubernetes. With the introduction of projected service account tokens, Kubernetes has significantly improved security and flexibility in how pods authenticate to the API server and external services. projected service account tokens What Are Projected Service Account Tokens? Projected service account tokens are time-bound, audience-scoped JSON Web Tokens (JWTs) that replace the legacy non-expiring service account tokens. They provide enhanced security through: Time-bound expiration: Tokens automatically expire and are rotated Audience binding: Tokens can be scoped to specific audiences Automatic rotation: The kubelet automatically refreshes tokens before expiration Time-bound expiration: Tokens automatically expire and are rotated Time-bound expiration Audience binding: Tokens can be scoped to specific audiences Audience binding Automatic rotation: The kubelet automatically refreshes tokens before expiration Automatic rotation The Problem with Legacy Service Account Tokens Before projected tokens, Kubernetes used legacy service account tokens that had several security limitations: legacy service account tokens Never expire: Once created, they remain valid indefinitely unless manually revoked No audience restriction: Can be used to authenticate to any service that accepts them Stored as Secrets: Persisted in etcd, increasing the attack surface Broad scope: If compromised, provide unrestricted access to the API server Manual rotation: Required manual intervention to refresh or rotate Never expire: Once created, they remain valid indefinitely unless manually revoked Never expire No audience restriction: Can be used to authenticate to any service that accepts them No audience restriction Stored as Secrets: Persisted in etcd, increasing the attack surface Stored as Secrets Broad scope: If compromised, provide unrestricted access to the API server Broad scope Manual rotation: Required manual intervention to refresh or rotate Manual rotation These limitations meant that if a token was leaked or a pod was compromised, attackers could potentially maintain persistent access to your cluster. Projected tokens solve these problems by being short-lived, automatically rotated, and scoped to specific audiences. How Projected Tokens Work Understanding the TokenRequest API The TokenRequest API is a Kubernetes API (not provided by cloud providers) that generates service account tokens on-demand. It’s part of the core Kubernetes API server and was introduced in Kubernetes 1.12 (stable in 1.20). TokenRequest API Key characteristics: Endpoint: /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token Purpose: Creates short-lived, audience-bound tokens for service accounts Parameters: Accepts expiration time and audience claims Signature: Tokens are signed by the Kubernetes API server’s private key Endpoint: /api/v1/namespaces/{namespace}/serviceaccounts/{name}/token Endpoint Purpose: Creates short-lived, audience-bound tokens for service accounts Purpose Parameters: Accepts expiration time and audience claims Parameters Signature: Tokens are signed by the Kubernetes API server’s private key Signature When you use a projected volume, the kubelet automatically calls this API on your behalf to request tokens, eliminating the need for manual token management. What is a Projected Volume? A projected volume is a special volume type in Kubernetes that can project (combine) multiple volume sources into a single directory. Think of it as a way to mount different types of data into your pod from various sources. projected volume Common sources that can be projected: serviceAccountToken: Dynamically generated tokens via TokenRequest API configMap: Configuration data secret: Sensitive data downwardAPI: Pod metadata serviceAccountToken: Dynamically generated tokens via TokenRequest API serviceAccountToken configMap: Configuration data configMap secret: Sensitive data secret downwardAPI: Pod metadata downwardAPI For service account tokens, projected volumes enable the kubelet to: Request fresh tokens from the TokenRequest API Automatically refresh tokens before expiration Mount tokens as files in the pod’s filesystem Handle all the complexity of token lifecycle management Request fresh tokens from the TokenRequest API Automatically refresh tokens before expiration Mount tokens as files in the pod’s filesystem Handle all the complexity of token lifecycle management This is different from the legacy approach where tokens were stored as static Secrets and mounted directly. Token Generation Flow Projected tokens use the TokenRequest API to generate short-lived tokens on-demand. Here’s the typical flow: Basic Configuration Here’s a simple example of configuring a projected service account token: apiVersion: v1 kind: Pod metadata: name: token-demo spec: serviceAccountName: my-service-account containers: - name: app image: nginx volumeMounts: - name: token mountPath: /var/run/secrets/tokens readOnly: true volumes: - name: token projected: sources: - serviceAccountToken: path: token expirationSeconds: 3600 audience: my-app apiVersion: v1 kind: Pod metadata: name: token-demo spec: serviceAccountName: my-service-account containers: - name: app image: nginx volumeMounts: - name: token mountPath: /var/run/secrets/tokens readOnly: true volumes: - name: token projected: sources: - serviceAccountToken: path: token expirationSeconds: 3600 audience: my-app Using Projected Tokens with AKS (Azure Kubernetes Service) AKS leverages projected tokens for Workload Identity, enabling pods to authenticate to Azure services without storing credentials. Workload Identity Azure-Side Configuration Before using Workload Identity in AKS, you need to set up the Azure side: # 1. Create an Azure AD application (or Managed Identity) az ad sp create-for-rbac --name "myapp-workload-identity" # 2. Get the application's client ID export APPLICATION_CLIENT_ID="<your-client-id>" # 3. Create federated identity credential that trusts your AKS cluster az ad app federated-credential create \ --id $APPLICATION_CLIENT_ID \ --parameters '{ "name": "myapp-federated-credential", "issuer": "https://oidc.prod-aks.azure.com/<tenant-id>/<cluster-oidc-issuer-id>/", "subject": "system:serviceaccount:default:workload-identity-sa", "audiences": ["api://AzureADTokenExchange"] }' # 4. Assign Azure RBAC roles to the application az role assignment create \ --assignee $APPLICATION_CLIENT_ID \ --role "Storage Blob Data Contributor" \ --scope "/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.Storage/storageAccounts/<storage-account>" # 1. Create an Azure AD application (or Managed Identity) az ad sp create-for-rbac --name "myapp-workload-identity" # 2. Get the application's client ID export APPLICATION_CLIENT_ID="<your-client-id>" # 3. Create federated identity credential that trusts your AKS cluster az ad app federated-credential create \ --id $APPLICATION_CLIENT_ID \ --parameters '{ "name": "myapp-federated-credential", "issuer": "https://oidc.prod-aks.azure.com/<tenant-id>/<cluster-oidc-issuer-id>/", "subject": "system:serviceaccount:default:workload-identity-sa", "audiences": ["api://AzureADTokenExchange"] }' # 4. Assign Azure RBAC roles to the application az role assignment create \ --assignee $APPLICATION_CLIENT_ID \ --role "Storage Blob Data Contributor" \ --scope "/subscriptions/<subscription-id>/resourceGroups/<rg-name>/providers/Microsoft.Storage/storageAccounts/<storage-account>" Key Configuration Points: Key Configuration Points: Issuer: Your AKS cluster’s OIDC issuer URL (unique per cluster) Subject: Must match the format system:serviceaccount:<namespace>:<service-account-name> Audiences: Must be api://AzureADTokenExchange for Workload Identity Issuer: Your AKS cluster’s OIDC issuer URL (unique per cluster) Issuer Subject: Must match the format system:serviceaccount:<namespace>:<service-account-name> Subject system:serviceaccount:<namespace>:<service-account-name> Audiences: Must be api://AzureADTokenExchange for Workload Identity Audiences api://AzureADTokenExchange AKS Workload Identity Setup apiVersion: v1 kind: ServiceAccount metadata: name: workload-identity-sa namespace: default annotations: azure.workload.identity/client-id: "YOUR_AZURE_CLIENT_ID" --- apiVersion: v1 kind: Pod metadata: name: aks-workload-identity-demo namespace: default labels: azure.workload.identity/use: "true" # This label triggers the webhook to inject volumes spec: serviceAccountName: workload-identity-sa containers: - name: app image: mcr.microsoft.com/azure-cli command: ["sleep", "infinity"] # Note: The following are automatically injected by the AKS Workload Identity webhook # when the pod has the label "azure.workload.identity/use: true": # # Environment variables: # - AZURE_CLIENT_ID # - AZURE_TENANT_ID # - AZURE_FEDERATED_TOKEN_FILE # - AZURE_AUTHORITY_HOST # # Volume mounts: # - name: azure-identity-token # mountPath: /var/run/secrets/azure/tokens # readOnly: true # # Volumes: # - name: azure-identity-token # projected: # sources: # - serviceAccountToken: # path: azure-identity-token # expirationSeconds: 3600 # audience: api://AzureADTokenExchange apiVersion: v1 kind: ServiceAccount metadata: name: workload-identity-sa namespace: default annotations: azure.workload.identity/client-id: "YOUR_AZURE_CLIENT_ID" --- apiVersion: v1 kind: Pod metadata: name: aks-workload-identity-demo namespace: default labels: azure.workload.identity/use: "true" # This label triggers the webhook to inject volumes spec: serviceAccountName: workload-identity-sa containers: - name: app image: mcr.microsoft.com/azure-cli command: ["sleep", "infinity"] # Note: The following are automatically injected by the AKS Workload Identity webhook # when the pod has the label "azure.workload.identity/use: true": # # Environment variables: # - AZURE_CLIENT_ID # - AZURE_TENANT_ID # - AZURE_FEDERATED_TOKEN_FILE # - AZURE_AUTHORITY_HOST # # Volume mounts: # - name: azure-identity-token # mountPath: /var/run/secrets/azure/tokens # readOnly: true # # Volumes: # - name: azure-identity-token # projected: # sources: # - serviceAccountToken: # path: azure-identity-token # expirationSeconds: 3600 # audience: api://AzureADTokenExchange Important: In practice, when using AKS Workload Identity, you typically only need to: Important Annotate your service account with azure.workload.identity/client-id Add the label azure.workload.identity/use: "true" to your pod Reference that service account in your pod spec Annotate your service account with azure.workload.identity/client-id azure.workload.identity/client-id Add the label azure.workload.identity/use: "true" to your pod azure.workload.identity/use: "true" Reference that service account in your pod spec The pod spec would look like this: apiVersion: v1 kind: Pod metadata: name: aks-workload-identity-demo namespace: default labels: azure.workload.identity/use: "true" spec: serviceAccountName: workload-identity-sa containers: - name: app image: mcr.microsoft.com/azure-cli command: ["sleep", "infinity"] # Everything else is auto-injected! apiVersion: v1 kind: Pod metadata: name: aks-workload-identity-demo namespace: default labels: azure.workload.identity/use: "true" spec: serviceAccountName: workload-identity-sa containers: - name: app image: mcr.microsoft.com/azure-cli command: ["sleep", "infinity"] # Everything else is auto-injected! AKS will automatically inject the environment variables, volume mounts, and projected volumes for you through its mutating admission webhook. How it works in AKS: How it works in AKS: Using Projected Tokens with EKS (Elastic Kubernetes Service) EKS uses projected tokens for IAM Roles for Service Accounts (IRSA), allowing pods to assume AWS IAM roles. IAM Roles for Service Accounts (IRSA) AWS-Side Configuration Before using IRSA in EKS, you need to configure AWS IAM: # 1. Get your EKS cluster's OIDC provider URL aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text # Output: https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE # 2. Create an IAM OIDC identity provider for your cluster # Note: If you created your cluster with eksctl or with OIDC enabled, this may already exist # You can verify with: aws iam list-open-id-connect-providers eksctl utils associate-iam-oidc-provider --cluster my-cluster --approve # 3. Create an IAM policy for S3 access cat > s3-policy.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ] } ] } EOF aws iam create-policy --policy-name S3AccessPolicy --policy-document file://s3-policy.json # 4. Create an IAM role with a trust policy that allows the service account cat > trust-policy.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:s3-access-sa", "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com" } } } ] } EOF aws iam create-role --role-name s3-access-role --assume-role-policy-document file://trust-policy.json # 5. Attach the policy to the role aws iam attach-role-policy \ --role-name s3-access-role \ --policy-arn arn:aws:iam::ACCOUNT_ID:policy/S3AccessPolicy # 1. Get your EKS cluster's OIDC provider URL aws eks describe-cluster --name my-cluster --query "cluster.identity.oidc.issuer" --output text # Output: https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE # 2. Create an IAM OIDC identity provider for your cluster # Note: If you created your cluster with eksctl or with OIDC enabled, this may already exist # You can verify with: aws iam list-open-id-connect-providers eksctl utils associate-iam-oidc-provider --cluster my-cluster --approve # 3. Create an IAM policy for S3 access cat > s3-policy.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:GetObject", "s3:ListBucket" ], "Resource": [ "arn:aws:s3:::my-bucket", "arn:aws:s3:::my-bucket/*" ] } ] } EOF aws iam create-policy --policy-name S3AccessPolicy --policy-document file://s3-policy.json # 4. Create an IAM role with a trust policy that allows the service account cat > trust-policy.json <<EOF { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE" }, "Action": "sts:AssumeRoleWithWebIdentity", "Condition": { "StringEquals": { "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:sub": "system:serviceaccount:default:s3-access-sa", "oidc.eks.us-west-2.amazonaws.com/id/EXAMPLED539D4633E53DE1B71EXAMPLE:aud": "sts.amazonaws.com" } } } ] } EOF aws iam create-role --role-name s3-access-role --assume-role-policy-document file://trust-policy.json # 5. Attach the policy to the role aws iam attach-role-policy \ --role-name s3-access-role \ --policy-arn arn:aws:iam::ACCOUNT_ID:policy/S3AccessPolicy Key Configuration Points: Key Configuration Points: Trust Policy Condition: Must match system:serviceaccount:<namespace>:<service-account-name> Audience: Must be sts.amazonaws.com for IRSA OIDC Provider: Must be registered as a trusted identity provider in IAM Trust Policy Condition: Must match system:serviceaccount:<namespace>:<service-account-name> Trust Policy Condition system:serviceaccount:<namespace>:<service-account-name> Audience: Must be sts.amazonaws.com for IRSA Audience sts.amazonaws.com OIDC Provider: Must be registered as a trusted identity provider in IAM OIDC Provider EKS IRSA Configuration apiVersion: v1 kind: ServiceAccount metadata: name: s3-access-sa namespace: default annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/s3-access-role --- apiVersion: v1 kind: Pod metadata: name: eks-irsa-demo namespace: default spec: serviceAccountName: s3-access-sa containers: - name: app image: amazon/aws-cli command: ["sleep", "infinity"] # Note: The following are automatically injected by the EKS Pod Identity Webhook # when the service account has the annotation "eks.amazonaws.com/role-arn": # # Environment variables: # - AWS_ROLE_ARN: arn:aws:iam::ACCOUNT_ID:role/s3-access-role # - AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token # # Volume mounts: # - name: aws-iam-token # mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount # readOnly: true # # Volumes: # - name: aws-iam-token # projected: # sources: # - serviceAccountToken: # path: token # expirationSeconds: 86400 # audience: sts.amazonaws.com apiVersion: v1 kind: ServiceAccount metadata: name: s3-access-sa namespace: default annotations: eks.amazonaws.com/role-arn: arn:aws:iam::ACCOUNT_ID:role/s3-access-role --- apiVersion: v1 kind: Pod metadata: name: eks-irsa-demo namespace: default spec: serviceAccountName: s3-access-sa containers: - name: app image: amazon/aws-cli command: ["sleep", "infinity"] # Note: The following are automatically injected by the EKS Pod Identity Webhook # when the service account has the annotation "eks.amazonaws.com/role-arn": # # Environment variables: # - AWS_ROLE_ARN: arn:aws:iam::ACCOUNT_ID:role/s3-access-role # - AWS_WEB_IDENTITY_TOKEN_FILE: /var/run/secrets/eks.amazonaws.com/serviceaccount/token # # Volume mounts: # - name: aws-iam-token # mountPath: /var/run/secrets/eks.amazonaws.com/serviceaccount # readOnly: true # # Volumes: # - name: aws-iam-token # projected: # sources: # - serviceAccountToken: # path: token # expirationSeconds: 86400 # audience: sts.amazonaws.com Important: In practice, when using EKS with IRSA, you typically only need to: Important Annotate your service account with eks.amazonaws.com/role-arn Reference that service account in your pod spec Annotate your service account with eks.amazonaws.com/role-arn eks.amazonaws.com/role-arn Reference that service account in your pod spec The pod spec would look like this: apiVersion: v1 kind: Pod metadata: name: eks-irsa-demo namespace: default spec: serviceAccountName: s3-access-sa containers: - name: app image: amazon/aws-cli command: ["sleep", "infinity"] # Everything else is auto-injected! apiVersion: v1 kind: Pod metadata: name: eks-irsa-demo namespace: default spec: serviceAccountName: s3-access-sa containers: - name: app image: amazon/aws-cli command: ["sleep", "infinity"] # Everything else is auto-injected! EKS will automatically inject the environment variables, volume mounts, and projected volumes for you. The full configuration above is shown to illustrate what happens behind the scenes. How it works in EKS: How it works in EKS: Using Projected Tokens with GKE (Google Kubernetes Engine) GKE uses projected tokens for Workload Identity, enabling pods to authenticate as Google Cloud service accounts. Workload Identity GCP-Side Configuration Before using Workload Identity in GKE, you need to configure Google Cloud: # 1. Enable Workload Identity on your GKE cluster (if not already enabled) gcloud container clusters update my-cluster \ --workload-pool=PROJECT_ID.svc.id.goog # 2. Create a Google Cloud service account gcloud iam service-accounts create gcs-access-sa \ --display-name="GCS Access Service Account" # 3. Grant the GCP service account permissions to Cloud resources gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:gcs-access-sa@PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/storage.objectViewer" # 4. Create the IAM policy binding between the Kubernetes SA and GCP SA gcloud iam service-accounts add-iam-policy-binding \ gcs-access-sa@PROJECT_ID.iam.gserviceaccount.com \ --role="roles/iam.workloadIdentityUser" \ --member="serviceAccount:PROJECT_ID.svc.id.goog[default/gke-workload-identity-sa]" # 1. Enable Workload Identity on your GKE cluster (if not already enabled) gcloud container clusters update my-cluster \ --workload-pool=PROJECT_ID.svc.id.goog # 2. Create a Google Cloud service account gcloud iam service-accounts create gcs-access-sa \ --display-name="GCS Access Service Account" # 3. Grant the GCP service account permissions to Cloud resources gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:gcs-access-sa@PROJECT_ID.iam.gserviceaccount.com" \ --role="roles/storage.objectViewer" # 4. Create the IAM policy binding between the Kubernetes SA and GCP SA gcloud iam service-accounts add-iam-policy-binding \ gcs-access-sa@PROJECT_ID.iam.gserviceaccount.com \ --role="roles/iam.workloadIdentityUser" \ --member="serviceAccount:PROJECT_ID.svc.id.goog[default/gke-workload-identity-sa]" Key Configuration Points: Key Configuration Points: Workload Identity Pool: Format is PROJECT_ID.svc.id.goog Member Binding: Must match serviceAccount:PROJECT_ID.svc.id.goog[<namespace>/<ksa-name>] Role: The GCP service account needs roles/iam.workloadIdentityUser for the K8s SA Workload Identity Pool: Format is PROJECT_ID.svc.id.goog Workload Identity Pool PROJECT_ID.svc.id.goog Member Binding: Must match serviceAccount:PROJECT_ID.svc.id.goog[<namespace>/<ksa-name>] Member Binding serviceAccount:PROJECT_ID.svc.id.goog[<namespace>/<ksa-name>] Role: The GCP service account needs roles/iam.workloadIdentityUser for the K8s SA Role roles/iam.workloadIdentityUser The member format breaks down as: PROJECT_ID.svc.id.goog – Your workload identity pool [default/gke-workload-identity-sa] – [namespace/kubernetes-service-account] PROJECT_ID.svc.id.goog – Your workload identity pool PROJECT_ID.svc.id.goog [default/gke-workload-identity-sa] – [namespace/kubernetes-service-account] [default/gke-workload-identity-sa] [namespace/kubernetes-service-account] GKE Workload Identity Setup apiVersion: v1 kind: ServiceAccount metadata: name: gke-workload-identity-sa namespace: default annotations: iam.gke.io/gcp-service-account: my-gsa@PROJECT_ID.iam.gserviceaccount.com --- apiVersion: v1 kind: Pod metadata: name: gke-workload-identity-demo namespace: default spec: serviceAccountName: gke-workload-identity-sa containers: - name: app image: google/cloud-sdk:slim command: ["sleep", "infinity"] # Note: GKE Workload Identity automatically configures the GCP metadata server # in the pod. Application Default Credentials (ADC) will automatically work # without needing explicit volume mounts or environment variables. apiVersion: v1 kind: ServiceAccount metadata: name: gke-workload-identity-sa namespace: default annotations: iam.gke.io/gcp-service-account: my-gsa@PROJECT_ID.iam.gserviceaccount.com --- apiVersion: v1 kind: Pod metadata: name: gke-workload-identity-demo namespace: default spec: serviceAccountName: gke-workload-identity-sa containers: - name: app image: google/cloud-sdk:slim command: ["sleep", "infinity"] # Note: GKE Workload Identity automatically configures the GCP metadata server # in the pod. Application Default Credentials (ADC) will automatically work # without needing explicit volume mounts or environment variables. How it works in GKE: How it works in GKE: Note on GKE and Projected Volumes: Unlike AKS and EKS, GKE’s Workload Identity primarily works through metadata server emulation. You can optionally use projected service account tokens with a specific audience if you need direct access to the Kubernetes token, but this is rarely necessary. Most applications using Google Cloud client libraries will authenticate automatically through the metadata server without any explicit volume configuration. Note on GKE and Projected Volumes Cloud Provider Comparison Trust Relationship Overview All three cloud providers use a similar pattern: establishing trust between the Kubernetes service account and cloud provider IAM system through OIDC federation. Provider-Specific Comparison Feature AKS EKS GKE Trust Mechanism Federated Identity Credential IAM OIDC Provider + Trust Policy Workload Identity Pool Binding Subject Format system:serviceaccount:ns:sa system:serviceaccount:ns:sa serviceAccount:PROJECT.svc.id.goog[ns/sa] Audience api://AzureADTokenExchange sts.amazonaws.com https://iam.googleapis.com/... K8s Annotation azure.workload.identity/client-id eks.amazonaws.com/role-arn iam.gke.io/gcp-service-account Pod Label Required azure.workload.identity/use: "true" No No Auto-Injection Yes (via webhook) Yes (via webhook) Yes (metadata server) Env Variables Injected AZURE_CLIENT_ID, AZURE_TENANT_ID, etc. AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE None (uses metadata server) Volume Auto-Mount Yes Yes Typically not needed Cloud IAM Setup Federated credential on App/MI IAM Role with trust policy IAM binding with workloadIdentityUser Feature AKS EKS GKE Trust Mechanism Federated Identity Credential IAM OIDC Provider + Trust Policy Workload Identity Pool Binding Subject Format system:serviceaccount:ns:sa system:serviceaccount:ns:sa serviceAccount:PROJECT.svc.id.goog[ns/sa] Audience api://AzureADTokenExchange sts.amazonaws.com https://iam.googleapis.com/... K8s Annotation azure.workload.identity/client-id eks.amazonaws.com/role-arn iam.gke.io/gcp-service-account Pod Label Required azure.workload.identity/use: "true" No No Auto-Injection Yes (via webhook) Yes (via webhook) Yes (metadata server) Env Variables Injected AZURE_CLIENT_ID, AZURE_TENANT_ID, etc. AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE None (uses metadata server) Volume Auto-Mount Yes Yes Typically not needed Cloud IAM Setup Federated credential on App/MI IAM Role with trust policy IAM binding with workloadIdentityUser Feature AKS EKS GKE Feature Feature AKS AKS EKS EKS GKE GKE Trust Mechanism Federated Identity Credential IAM OIDC Provider + Trust Policy Workload Identity Pool Binding Trust Mechanism Trust Mechanism Trust Mechanism Federated Identity Credential Federated Identity Credential IAM OIDC Provider + Trust Policy IAM OIDC Provider + Trust Policy Workload Identity Pool Binding Workload Identity Pool Binding Subject Format system:serviceaccount:ns:sa system:serviceaccount:ns:sa serviceAccount:PROJECT.svc.id.goog[ns/sa] Subject Format Subject Format Subject Format system:serviceaccount:ns:sa system:serviceaccount:ns:sa system:serviceaccount:ns:sa system:serviceaccount:ns:sa system:serviceaccount:ns:sa system:serviceaccount:ns:sa serviceAccount:PROJECT.svc.id.goog[ns/sa] serviceAccount:PROJECT.svc.id.goog[ns/sa] serviceAccount:PROJECT.svc.id.goog[ns/sa] Audience api://AzureADTokenExchange sts.amazonaws.com https://iam.googleapis.com/... Audience Audience Audience api://AzureADTokenExchange api://AzureADTokenExchange api://AzureADTokenExchange sts.amazonaws.com sts.amazonaws.com sts.amazonaws.com https://iam.googleapis.com/... https://iam.googleapis.com/... https://iam.googleapis.com/... K8s Annotation azure.workload.identity/client-id eks.amazonaws.com/role-arn iam.gke.io/gcp-service-account K8s Annotation K8s Annotation K8s Annotation azure.workload.identity/client-id azure.workload.identity/client-id azure.workload.identity/client-id eks.amazonaws.com/role-arn eks.amazonaws.com/role-arn eks.amazonaws.com/role-arn iam.gke.io/gcp-service-account iam.gke.io/gcp-service-account iam.gke.io/gcp-service-account Pod Label Required azure.workload.identity/use: "true" No No Pod Label Required Pod Label Required Pod Label Required azure.workload.identity/use: "true" azure.workload.identity/use: "true" azure.workload.identity/use: "true" No No No No Auto-Injection Yes (via webhook) Yes (via webhook) Yes (metadata server) Auto-Injection Auto-Injection Auto-Injection Yes (via webhook) Yes (via webhook) Yes (via webhook) Yes (via webhook) Yes (metadata server) Yes (metadata server) Env Variables Injected AZURE_CLIENT_ID, AZURE_TENANT_ID, etc. AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE None (uses metadata server) Env Variables Injected Env Variables Injected Env Variables Injected AZURE_CLIENT_ID, AZURE_TENANT_ID, etc. AZURE_CLIENT_ID, AZURE_TENANT_ID, etc. AZURE_CLIENT_ID AZURE_TENANT_ID AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE AWS_ROLE_ARN AWS_WEB_IDENTITY_TOKEN_FILE None (uses metadata server) None (uses metadata server) Volume Auto-Mount Yes Yes Typically not needed Volume Auto-Mount Volume Auto-Mount Volume Auto-Mount Yes Yes Yes Yes Typically not needed Typically not needed Cloud IAM Setup Federated credential on App/MI IAM Role with trust policy IAM binding with workloadIdentityUser Cloud IAM Setup Cloud IAM Setup Cloud IAM Setup Federated credential on App/MI Federated credential on App/MI IAM Role with trust policy IAM Role with trust policy IAM binding with workloadIdentityUser IAM binding with workloadIdentityUser Key Benefits Across All Platforms No Long-Lived Credentials: Tokens expire automatically, reducing security risk Automatic Rotation: The kubelet handles token refresh transparently Fine-Grained Access: Audience scoping limits token usage Cloud Integration: Seamless authentication to cloud provider services Least Privilege: Each pod gets only the permissions it needs No Long-Lived Credentials: Tokens expire automatically, reducing security risk No Long-Lived Credentials Automatic Rotation: The kubelet handles token refresh transparently Automatic Rotation Fine-Grained Access: Audience scoping limits token usage Fine-Grained Access Cloud Integration: Seamless authentication to cloud provider services Cloud Integration Least Privilege: Each pod gets only the permissions it needs Least Privilege Best Practices Set appropriate expiration times: Balance between security (shorter) and performance (fewer rotations) Use specific audiences: Scope tokens to their intended use Monitor token usage: Track authentication patterns for security insights Follow cloud provider guides: Each platform has specific setup requirements Test token rotation: Ensure your applications handle token refresh gracefully Set appropriate expiration times: Balance between security (shorter) and performance (fewer rotations) Set appropriate expiration times Use specific audiences: Scope tokens to their intended use Use specific audiences Monitor token usage: Track authentication patterns for security insights Monitor token usage Follow cloud provider guides: Each platform has specific setup requirements Follow cloud provider guides Test token rotation: Ensure your applications handle token refresh gracefully Test token rotation Conclusion Projected service account tokens represent a significant security improvement in Kubernetes authentication. Whether you’re running on AKS, EKS, or GKE, understanding how these tokens work enables you to build secure, cloud-native applications that follow the principle of least privilege without managing long-lived credentials. The integration with cloud provider IAM systems makes projected tokens essential for modern Kubernetes workloads, providing a secure bridge between your containerized applications and cloud services.