AKS Workload Identity Federation: migrate from deprecated aad-pod-identity to OIDC-based authentication
Complete migration guide from deprecated aad-pod-identity to AKS Workload Identity Federation using OIDC issuer, managed identities, and federated credentials.
aad-pod-identity in favour of Workload Identity Federation. The new system uses OIDC tokens projected into pods - no more NMI DaemonSet, no more AzureIdentity CRDs. Here is the complete migration path with az CLI and kubectl commands.
Symptoms
You are still running aad-pod-identity (NMI pods, AzureIdentity and AzureIdentityBinding CRDs) and experiencing:
# NMI pods consuming resources on every node
kubectl get pods -n kube-system -l app=nmi
# NAME READY STATUS AGE
# nmi-abc12 1/1 Running 45d
# nmi-def34 1/1 Running 45d
# nmi-ghi56 1/1 Running 45d
# Deprecation warnings in AKS upgrade notifications
az aks show --resource-group my-rg --name my-cluster \
--query "agentPoolProfiles[0].currentOrchestratorVersion"
# Warning: aad-pod-identity add-on is deprecated. Migrate to Workload Identity.
# Intermittent token acquisition failures
kubectl logs -n kube-system -l app=nmi --tail=20 | grep -i error
# E0707 token request failed: timeout waiting for token response from IMDS
The old system uses host-networking NMI pods to intercept IMDS calls, which is fragile, adds latency, and will stop receiving security patches.
Cause
Microsoft deprecated aad-pod-identity because of fundamental architectural issues:
- NMI DaemonSet intercepts all IMDS traffic at the node level using iptables rules. This creates a single point of failure and adds latency to every Azure SDK call from any pod.
- Race conditions during pod startup. The AzureAssignedIdentity binding is eventually consistent - pods can start before their identity is assigned, causing auth failures.
- Security concerns. NMI requires host networking and elevated privileges. Any pod on the same node can potentially access identities assigned to other pods.
- No support for user-assigned managed identities at scale. The CRD-based system does not scale beyond hundreds of identity bindings without performance degradation.
Workload Identity Federation replaces all of this with projected OIDC tokens that Azure AD validates directly - no node-level interception needed.
Fix
Step 1: Enable OIDC issuer and Workload Identity on the AKS cluster
# Enable OIDC issuer (required for Workload Identity)
az aks update \
--resource-group my-rg \
--name my-cluster \
--enable-oidc-issuer
# Enable Workload Identity feature
az aks update \
--resource-group my-rg \
--name my-cluster \
--enable-workload-identity
# Get the OIDC issuer URL (needed for federated credential)
export AKS_OIDC_ISSUER=$(az aks show \
--resource-group my-rg \
--name my-cluster \
--query "oidcIssuerProfile.issuerUrl" -o tsv)
echo $AKS_OIDC_ISSUER
# https://oidc.prod-aks.azure.com/abc123-def456/
Step 2: Create a user-assigned managed identity
# Create managed identity for your workload
az identity create \
--resource-group my-rg \
--name my-app-identity \
--location westeurope
# Get the client ID
export IDENTITY_CLIENT_ID=$(az identity show \
--resource-group my-rg \
--name my-app-identity \
--query "clientId" -o tsv)
# Assign roles (example: Storage Blob Reader)
export IDENTITY_PRINCIPAL_ID=$(az identity show \
--resource-group my-rg \
--name my-app-identity \
--query "principalId" -o tsv)
az role assignment create \
--assignee-object-id $IDENTITY_PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--role "Storage Blob Data Reader" \
--scope /subscriptions/YOUR_SUB/resourceGroups/my-rg/providers/Microsoft.Storage/storageAccounts/mystorageaccount
Step 3: Create the federated credential
This links the Kubernetes service account to the Azure managed identity:
# Define the Kubernetes namespace and service account name
export SERVICE_ACCOUNT_NAMESPACE="default"
export SERVICE_ACCOUNT_NAME="my-app-sa"
# Create federated credential
az identity federated-credential create \
--name my-app-federated-cred \
--identity-name my-app-identity \
--resource-group my-rg \
--issuer $AKS_OIDC_ISSUER \
--subject system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME} \
--audience api://AzureADTokenExchange
Step 4: Create the annotated Kubernetes service account
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: default
annotations:
azure.workload.identity/client-id: "${IDENTITY_CLIENT_ID}"
labels:
azure.workload.identity/use: "true"
EOF
Step 5: Update pod spec to use the new service account
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
azure.workload.identity/use: "true" # Required label
spec:
serviceAccountName: my-app-sa # Points to annotated SA
containers:
- name: my-app
image: myregistry.azurecr.io/my-app:latest
env:
# These are automatically injected by the mutating webhook,
# but you can reference them explicitly if needed:
- name: AZURE_CLIENT_ID
value: "" # Injected automatically
- name: AZURE_TENANT_ID
value: "" # Injected automatically
- name: AZURE_FEDERATED_TOKEN_FILE
value: "" # Injected automatically
The Workload Identity mutating webhook automatically injects the environment variables and projected token volume. No code changes needed if you use Azure SDK (DefaultAzureCredential picks up the federated token automatically).
Step 6: Remove old aad-pod-identity resources
Once the new identity is working:
# Remove AzureIdentity and AzureIdentityBinding CRDs
kubectl delete azureidentity my-app-identity -n default
kubectl delete azureidentitybinding my-app-identity-binding -n default
# If using the managed add-on, disable it
az aks update \
--resource-group my-rg \
--name my-cluster \
--disable-pod-identity
# If using Helm-installed aad-pod-identity
helm uninstall aad-pod-identity -n kube-system
# Verify NMI pods are gone
kubectl get pods -n kube-system -l app=nmi
# Expected: No resources found
Validation
After migration, verify the workload can authenticate to Azure resources:
# 1. Verify the service account has correct annotations
kubectl get serviceaccount my-app-sa -o yaml | grep -A2 annotations
# Expected: azure.workload.identity/client-id: "<your-client-id>"
# 2. Verify the mutating webhook is injecting environment variables
kubectl describe pod -l app=my-app | grep -A3 "AZURE_"
# Expected:
# AZURE_CLIENT_ID: <client-id>
# AZURE_TENANT_ID: <tenant-id>
# AZURE_FEDERATED_TOKEN_FILE: /var/run/secrets/azure/tokens/azure-identity-token
# 3. Verify the projected token volume exists
kubectl exec -it deploy/my-app -- ls /var/run/secrets/azure/tokens/
# Expected: azure-identity-token
# 4. Test Azure resource access from the pod
kubectl exec -it deploy/my-app -- az storage blob list \
--account-name mystorageaccount \
--container-name mycontainer \
--auth-mode login
# Expected: list of blobs (no auth errors)
# 5. Verify no NMI pods remain
kubectl get pods -A -l app=nmi
# Expected: No resources found
# 6. Check workload identity webhook is healthy
kubectl get pods -n kube-system -l azure-workload-identity.io/system=true
# Expected: webhook pods Running
Running deprecated identity infrastructure is a security liability. The old NMI DaemonSet requires host networking and privileged access on every node. Workload Identity Federation eliminates that attack surface entirely while providing faster, more reliable token acquisition. Every week you delay migration is a week your cluster runs with known vulnerabilities in a component that will never be patched again.
Migrating AKS identity at scale?
Book a free 30-minute call. We handle the full migration from aad-pod-identity to Workload Identity Federation across all your clusters and workloads.