Azure AKS Kubernetes identity

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.

·
Azure has deprecated 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.

 

Jerzy Kopaczewski

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.