Home / Azure

AzureHow-toIntermediate

AKS Workload Identity - setting it up and fixing pods that can't get an Entra token

Workload Identity lets AKS pods use a managed identity without secrets. Enable the OIDC issuer, create a federated credential matching the service account, annotate it and label the pod. Most failures are a subject or issuer mismatch.

Deepak Bhardwaj4 min read

Applies toAzure Kubernetes Service with Microsoft Entra Workload ID; Azure CLI 2.x; Azure Identity SDKs

How it works

  1. AKS publishes an OIDC issuer URL.
  2. A user-assigned managed identity gets a federated credential that trusts tokens from that issuer for a specific Kubernetes service account (system:serviceaccount:<namespace>:<name>).
  3. The workload identity webhook injects environment variables and a projected token file into pods that use that service account and carry the azure.workload.identity/use: "true" label.
  4. The Azure Identity SDK (DefaultAzureCredential / WorkloadIdentityCredential) exchanges the Kubernetes token for an Entra token.

No secrets are stored in the cluster.

1. Enable on the cluster

az aks update -g rg-aks -n aks-prod --enable-oidc-issuer --enable-workload-identity

ISSUER=$(az aks show -g rg-aks -n aks-prod --query "oidcIssuerProfile.issuerUrl" -o tsv)
echo $ISSUER

2. Create the identity and federated credential

az identity create -g rg-aks -n id-orders-api
CLIENT_ID=$(az identity show -g rg-aks -n id-orders-api --query clientId -o tsv)

az identity federated-credential create -g rg-aks --identity-name id-orders-api \
  -n fc-orders-api --issuer "$ISSUER" \
  --subject "system:serviceaccount:orders:orders-api" \
  --audience api://AzureADTokenExchange

Grant the identity what it needs, for example read secrets from Key Vault:

az role assignment create --assignee $CLIENT_ID --role "Key Vault Secrets User" \
  --scope $(az keyvault show -n kv-orders --query id -o tsv)

3. Service account and pod

apiVersion: v1
kind: ServiceAccount
metadata:
  name: orders-api
  namespace: orders
  annotations:
    azure.workload.identity/client-id: "<CLIENT_ID>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: orders-api
  namespace: orders
spec:
  replicas: 2
  selector:
    matchLabels: { app: orders-api }
  template:
    metadata:
      labels:
        app: orders-api
        azure.workload.identity/use: "true"
    spec:
      serviceAccountName: orders-api
      containers:
        - name: api
          image: acrprod.azurecr.io/orders-api:1.4.2

The use label goes on the pod template, not only on the Deployment metadata.

4. Application code

from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient

cred = DefaultAzureCredential()
client = SecretClient("https://kv-orders.vault.azure.net", cred)
print(client.get_secret("db-password").name)

Use a recent Azure Identity SDK version. Old versions don't know the workload identity environment variables.

Troubleshooting

First check that the webhook injected what it should:

kubectl -n orders get pod -l app=orders-api -o name | head -1 | \
  xargs -I{} kubectl -n orders exec {} -- env | grep AZURE_
# expect AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE, AZURE_AUTHORITY_HOST
Symptom / error Cause Fix
No AZURE_* variables in the pod Label missing on pod template, or webhook not enabled Add the label and restart pods; check --enable-workload-identity
AADSTS70021: No matching federated identity record found Subject or issuer in the federated credential doesn't match Subject must be exactly system:serviceaccount:<ns>:<sa>; issuer must match the cluster's URL including the trailing slash
AADSTS700016: Application ... not found Wrong client ID in the annotation Use the identity's clientId, not principalId
Token works, but 403 on the resource RBAC role missing or still propagating Assign the role on the right scope; wait a few minutes
Worked, then broke after cluster rebuild New cluster has a new issuer URL Update or add a federated credential for the new issuer
Pod started before the SA annotation was added Webhook mutates at pod creation only kubectl rollout restart deployment/orders-api -n orders

Compare the federated credential with what the pod actually presents:

az identity federated-credential list -g rg-aks --identity-name id-orders-api \
  --query "[].{issuer:issuer, subject:subject, aud:audiences[0]}" -o table

Tip

One identity can have several federated credentials, which is useful for blue/green clusters. Check the current limit per identity in the documentation before you plan many clusters.

Migrating from pod-managed identity (AAD Pod Identity)

AAD Pod Identity is deprecated. For each AzureIdentity/AzureIdentityBinding:

  1. Create a federated credential on the same managed identity.
  2. Add the service account annotation and the pod label.
  3. Upgrade the SDKs in the app if needed.
  4. Remove the pod identity binding after the pods run successfully with workload identity.

Checklist

  • OIDC issuer and workload identity enabled
  • Federated credential subject = system:serviceaccount:<ns>:<sa>
  • Service account annotated with client ID
  • Pod template labelled azure.workload.identity/use: "true"
  • Role assignments on target resources
  • Pods restarted and AZURE_* variables present