How it works
- AKS publishes an OIDC issuer URL.
- 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>). - 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. - 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:
- Create a federated credential on the same managed identity.
- Add the service account annotation and the pod label.
- Upgrade the SDKs in the app if needed.
- 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