Static credentials are easy to create and hard to defend. One secret lands in a Kubernetes manifest, another gets copied into a pipeline variable, and before long nobody can tell which workload still needs which password.
Azure Kubernetes Service (AKS) has a better pattern: Microsoft Entra Workload ID. Instead of storing long-lived client secrets in pods, a Kubernetes service account can exchange a projected token for a Microsoft Entra access token. The workload gets the Azure access it needs, and you stop treating Kubernetes Secrets like a password vault.
In this tutorial, you’ll configure workload identity for an AKS workload, connect it to a user-assigned managed identity, and verify the pod can access Azure without a static secret.
Prerequisites
To follow along, you’ll need:
- An Azure subscription where you can create or modify an AKS cluster.
- Azure CLI installed and authenticated with
az login. kubectlconfigured for your AKS cluster.- Permission to create managed identities and federated identity credentials.
- An AKS cluster that supports workload identity.
Related: Microsoft Entra Workload ID for AKS
Why Workload Identity Matters
A Kubernetes pod often needs to talk to Azure services. It might read a secret from Key Vault, write to a Storage account, or query a database. The old shortcut was to put a client ID and client secret somewhere the pod could read.
That shortcut creates three problems:
- Secrets are copied into too many places.
- Rotation becomes an outage risk.
- Incident response gets harder because nobody knows which pod used which credential.
Workload identity changes the trust model. The pod proves who it is through its Kubernetes service account. Microsoft Entra trusts that service account through a federated identity credential. Azure returns a token without the workload ever storing a password.
| Old pattern | Workload identity pattern |
|---|---|
| Store client secret in Kubernetes | Project a short-lived service account token |
| Rotate secret manually | Let token exchange handle credential freshness |
| Hard to map secret to workload | Bind identity to namespace and service account |
| Secret leak can persist | Trust is scoped and revocable |
The goal is not just fewer secrets. The goal is a clearer identity boundary.
Enable Workload Identity on AKS
If you are creating a new cluster, enable workload identity and the OIDC issuer during cluster creation.
az aks create \
--resource-group rg-aks-demo \
--name aks-workload-demo \
--enable-oidc-issuer \
--enable-workload-identity \
--generate-ssh-keys
For an existing cluster, enable both features.
az aks update \
--resource-group rg-aks-demo \
--name aks-workload-demo \
--enable-oidc-issuer \
--enable-workload-identity
Then retrieve the OIDC issuer URL. You’ll need this URL when creating the federated identity credential.
AKS_OIDC_ISSUER=$(az aks show \
--resource-group rg-aks-demo \
--name aks-workload-demo \
--query oidcIssuerProfile.issuerUrl \
--output tsv)
echo $AKS_OIDC_ISSUER
If this command returns an empty value, stop. The issuer is not enabled correctly, and the token exchange will not work.
Create a User-Assigned Managed Identity
Next, create the Azure identity your pod will use.
az identity create \
--resource-group rg-aks-demo \
--name id-aks-reader
Capture the identity values.
IDENTITY_CLIENT_ID=$(az identity show \
--resource-group rg-aks-demo \
--name id-aks-reader \
--query clientId \
--output tsv)
IDENTITY_PRINCIPAL_ID=$(az identity show \
--resource-group rg-aks-demo \
--name id-aks-reader \
--query principalId \
--output tsv)
echo $IDENTITY_CLIENT_ID
Assign the identity the smallest role it needs. For a quick read-only test, scope the role to a resource group.
SUBSCRIPTION_ID=$(az account show --query id --output tsv)
az role assignment create \
--assignee $IDENTITY_PRINCIPAL_ID \
--role Reader \
--scope /subscriptions/$SUBSCRIPTION_ID/resourceGroups/rg-aks-demo
Do not assign Contributor just because it makes the demo easier. Workload identity removes static secrets, but it does not fix over-permissioned roles.
Create the Kubernetes Service Account
A workload identity binding starts in Kubernetes with a service account. Create a namespace and service account annotated with the managed identity client ID.
apiVersion: v1
kind: Namespace
metadata:
name: workload-demo
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: workload-reader
namespace: workload-demo
annotations:
azure.workload.identity/client-id: "REPLACE_WITH_CLIENT_ID"
Save the file as service-account.yaml, replace REPLACE_WITH_CLIENT_ID, and apply it.
kubectl apply -f service-account.yaml
The service account name and namespace matter. Microsoft Entra will trust this exact subject string later:
system:serviceaccount:workload-demo:workload-reader
If the namespace or service account name changes, the federated credential must change too.
Create the Federated Identity Credential
The federated identity credential connects Microsoft Entra to the Kubernetes service account.
az identity federated-credential create \
--resource-group rg-aks-demo \
--identity-name id-aks-reader \
--name fic-workload-reader \
--issuer $AKS_OIDC_ISSUER \
--subject system:serviceaccount:workload-demo:workload-reader \
--audience api://AzureADTokenExchange
This command says: tokens issued by this AKS OIDC issuer for this Kubernetes service account can be exchanged for tokens as this managed identity.
That is the core trust relationship. Everything else is just getting the pod to use it.
Related: Configure workload identity federation
Deploy a Pod That Uses the Identity
Now create a test pod. The important parts are the service account and the workload identity label.
apiVersion: v1
kind: Pod
metadata:
name: azure-cli-test
namespace: workload-demo
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: workload-reader
containers:
- name: azure-cli
image: mcr.microsoft.com/azure-cli:latest
command: ["/bin/sh", "-c"]
args:
- sleep 3600
Apply the pod.
kubectl apply -f pod.yaml
kubectl wait --for=condition=Ready pod/azure-cli-test -n workload-demo --timeout=120s
The label tells the workload identity webhook to inject the environment variables and token file the Azure SDK and CLI expect.
Verify the Pod Can Use Azure Without a Secret
Exec into the pod and sign in with the managed identity.
kubectl exec -n workload-demo -it azure-cli-test -- /bin/sh
Inside the container, run:
az login --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" \
--service-principal \
--username $AZURE_CLIENT_ID \
--tenant $AZURE_TENANT_ID
az group show --name rg-aks-demo --query name --output tsv
If the role assignment is correct, the command returns the resource group name. No client secret was mounted. No password was copied into a Kubernetes Secret. The workload authenticated through the federated service account token.
Troubleshoot Common Failures
Workload identity has a few predictable failure modes. Use the table below before changing random pieces.
| Symptom | Likely cause | Fix |
|---|---|---|
AZURE_FEDERATED_TOKEN_FILE is missing |
Pod label or service account binding is missing | Add azure.workload.identity/use: "true" and confirm serviceAccountName |
| Token exchange fails | Federated credential subject does not match | Check namespace and service account name exactly |
| Azure command returns authorization error | Identity authenticated but lacks permissions | Fix the Azure RBAC role assignment |
| OIDC issuer is blank | Cluster was not enabled correctly | Enable OIDC issuer and workload identity on the AKS cluster |
| Works in one namespace only | Federated credential is scoped to one service account | Create another credential or use the correct service account |
A good troubleshooting order is:
kubectl get pod azure-cli-test -n workload-demo -o yaml
kubectl get serviceaccount workload-reader -n workload-demo -o yaml
az identity federated-credential list \
--resource-group rg-aks-demo \
--identity-name id-aks-reader
az role assignment list --assignee $IDENTITY_PRINCIPAL_ID --all
If the Kubernetes objects look right, check the federated credential. If the credential looks right, check Azure RBAC.
Replace Static Secrets Gradually
Do not migrate every workload in one sprint. Start with one pod that reads one Azure service. Prove the identity path, then repeat the pattern.
For each workload, document:
- Namespace and service account name.
- Managed identity name.
- Federated identity credential name.
- Azure role assignment and scope.
- Azure service the workload accesses.
That list becomes your ownership map. It also makes incident response easier because you can revoke one workload’s trust without hunting for leaked secrets.
Clean Up the Demo
When you are done testing, remove the demo resources.
kubectl delete namespace workload-demo
az identity delete \
--resource-group rg-aks-demo \
--name id-aks-reader
If you created the AKS cluster just for this tutorial, remove the resource group.
az group delete --name rg-aks-demo --yes --no-wait
Stop Shipping Passwords in Pods
Workload identity is not just another AKS feature to toggle on. It is a better boundary between Kubernetes workloads and Azure resources.
The pattern is simple: enable the OIDC issuer, create a managed identity, bind it to a Kubernetes service account with a federated identity credential, and assign the identity only the Azure permissions it needs. After that, the pod can get Azure tokens without carrying a static client secret.
Start with one workload. Remove one stored secret. Prove the token exchange. Then repeat until static credentials become the exception instead of the default.