Managed Identities for AKS
Connecting pods to Azure services like Key Vault, Storage, or Service Bus traditionally meant storing connection strings or client secrets in your Kubernetes Secrets. Managed Identities eliminate that entirely. Your pods get a real Azure identity and request short-lived tokens from Azure directly — no credentials to rotate, no secrets to leak.
Why credentials in Kubernetes Secrets are not enough
Storing a service principal secret in a Kubernetes Secret feels secure, but it has real problems:
- Kubernetes Secrets are base64-encoded, not encrypted, by default. Anyone with cluster access can decode them.
- Secrets must be rotated when they expire. In a large cluster with many services, this becomes an operational burden.
- Leaked secrets remain valid until manually revoked. A leaked credential in a container image or log file compromises your Azure resources until someone notices and rotates it.
Managed Identities solve all three problems. There is no secret — just an identity. Tokens are short-lived and rotated automatically. Even if a pod is compromised, the attacker cannot extract a reusable credential.
The cluster managed identity
Every AKS cluster uses a managed identity for its own operations — creating load balancers, managing VMs, updating DNS records. Enable it at cluster creation:
# Create cluster with system-assigned managed identity (recommended)
az aks create \
--resource-group my-aks-rg \
--name my-cluster \
--enable-managed-identity \
--generate-ssh-keys
# Or use a user-assigned identity you control
az identity create \
--resource-group my-aks-rg \
--name aks-cluster-identity
IDENTITY_ID=$(az identity show \
--resource-group my-aks-rg \
--name aks-cluster-identity \
--query id -o tsv)
az aks create \
--resource-group my-aks-rg \
--name my-cluster \
--enable-managed-identity \
--assign-identity $IDENTITY_ID \
--generate-ssh-keysThe cluster identity needs specific Azure roles to function. AKS assigns these automatically for the managed resource group (where nodes live). If you bring your own VNet or other pre-existing resources, you may need to assign roles manually.
Workload Identity — giving pods Azure identities
Workload Identity uses OpenID Connect federation to link a Kubernetes service account to an Azure User-Assigned Managed Identity. When a pod uses that service account, the Azure Identity SDK automatically requests tokens from Azure AD on its behalf.
The flow:
- AKS acts as an OIDC issuer — it can sign JWT tokens for service accounts
- You create a User-Assigned Managed Identity in Azure
- You create a federated credential that trusts tokens from your AKS cluster for a specific service account
- Pods using that service account can request Azure AD tokens by calling a local endpoint
Setting up Workload Identity
# Step 1: Enable OIDC issuer and Workload Identity on the cluster
az aks update \
--resource-group my-aks-rg \
--name my-cluster \
--enable-oidc-issuer \
--enable-workload-identity
# Get the OIDC issuer URL (needed for federation)
OIDC_ISSUER=$(az aks show \
--resource-group my-aks-rg \
--name my-cluster \
--query "oidcIssuerProfile.issuerUrl" -o tsv)
echo $OIDC_ISSUER
# Step 2: Create a user-assigned managed identity
az identity create \
--resource-group my-aks-rg \
--name pod-identity
IDENTITY_CLIENT_ID=$(az identity show \
--resource-group my-aks-rg \
--name pod-identity \
--query clientId -o tsv)
IDENTITY_OBJECT_ID=$(az identity show \
--resource-group my-aks-rg \
--name pod-identity \
--query principalId -o tsv)
# Step 3: Assign Azure roles to the identity
# Example: give it access to read Key Vault secrets
az role assignment create \
--assignee-object-id $IDENTITY_OBJECT_ID \
--role "Key Vault Secrets User" \
--scope /subscriptions/{sub}/resourceGroups/my-rg/providers/Microsoft.KeyVault/vaults/my-vault
# Step 4: Create a Kubernetes service account annotated with the identity
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"
EOF
# Step 5: Create the federated credential linking SA to the managed identity
az identity federated-credential create \
--name my-app-federation \
--identity-name pod-identity \
--resource-group my-aks-rg \
--issuer $OIDC_ISSUER \
--subject "system:serviceaccount:default:my-app-sa" \
--audience api://AzureADTokenExchangeUsing Workload Identity in a pod
After setup, pods that reference the annotated service account automatically get the Azure identity injected as environment variables:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
azure.workload.identity/use: "true" # Required label
spec:
serviceAccountName: my-app-sa # The annotated SA
containers:
- name: app
image: myapp:1.0The Workload Identity webhook injects three environment variables into the pod:
AZURE_CLIENT_ID— the managed identity’s client IDAZURE_TENANT_ID— your Azure tenant IDAZURE_FEDERATED_TOKEN_FILE— path to a projected volume with the OIDC token
The Azure Identity SDK reads these automatically. In Python, Go, Java, or .NET, using DefaultAzureCredential will pick them up and authenticate without any code changes:
# Python example using azure-identity
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
credential = DefaultAzureCredential() # Automatically uses workload identity
client = SecretClient(
vault_url="https://my-vault.vault.azure.net",
credential=credential
)
secret = client.get_secret("my-db-password")Managed Identity for ACR access
AKS can use a managed identity to pull images from Azure Container Registry without any image pull secrets:
# Attach an ACR to AKS using managed identity
az aks update \
--resource-group my-aks-rg \
--name my-cluster \
--attach-acr my-container-registry
# Verify the role assignment was created
az role assignment list \
--scope $(az acr show --name my-container-registry --query id -o tsv) \
--query "[?roleDefinitionName=='AcrPull']"After attachment, pods can reference images from the ACR without any imagePullSecrets in the pod spec. The node kubelet uses the cluster managed identity to authenticate with ACR.
Accessing Key Vault secrets via CSI driver
The Secrets Store CSI driver mounts Azure Key Vault secrets directly into pods as files or environment variables:
# Enable the CSI driver add-on
az aks enable-addons \
--addons azure-keyvault-secrets-provider \
--resource-group my-aks-rg \
--name my-cluster# SecretProviderClass linking to Key Vault
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv-provider
spec:
provider: azure
parameters:
usePodIdentity: "false"
clientID: "<managed-identity-client-id>"
keyvaultName: "my-vault"
tenantID: "<your-tenant-id>"
objects: |
array:
- |
objectName: db-password
objectType: secret
objectVersion: ""
---
# Pod mounting the secret
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: myapp:1.0
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: "azure-kv-provider"The secret value appears at /mnt/secrets/db-password inside the container. When the secret rotates in Key Vault, the mounted value updates automatically.
Common mistakes
- Forgetting the azure.workload.identity/use: “true” pod label. The Workload Identity mutating webhook only injects environment variables into pods with this label. Without it, the pod does not get the token file and authentication fails.
- Using the cluster identity for application workloads. The cluster identity has broad permissions to manage Azure infrastructure. Application pods should use a separate, scoped managed identity with only the permissions the app needs.
- Not verifying the federated credential subject matches exactly. The subject string must exactly match
system:serviceaccount:NAMESPACE:SA-NAME. A typo in the namespace or service account name means the federation never works. - Granting Owner or Contributor roles to the pod identity. Pod identities should follow least privilege. A pod that reads Key Vault secrets only needs Key Vault Secrets User. Never grant broad subscription-level roles.
- Using AAD Pod Identity (deprecated) for new setups. AAD Pod Identity is deprecated and has known reliability issues. All new workloads should use the Workload Identity approach described on this page.
Summary
- Managed Identities eliminate the need to store credentials in Kubernetes. Pods get short-lived Azure AD tokens automatically.
- Workload Identity federates Kubernetes service accounts with Azure User-Assigned Managed Identities via OIDC. Enable OIDC issuer and Workload Identity on the cluster first.
- Pods must have the
azure.workload.identity/use: “truelabel and use the annotated service account to receive the injected identity credentials. - The Secrets Store CSI driver mounts Key Vault secrets directly into pods as files, with automatic rotation when values change in Key Vault.
- Attach ACR to AKS using
az aks update —attach-acrto enable credential-free image pulls.
Frequently asked questions
What is the difference between a cluster managed identity and workload identity?
The cluster managed identity is used by AKS itself to manage Azure resources like load balancers and node VMs. Workload Identity is what your application pods use to call Azure APIs like Key Vault or Storage. They are separate identities with separate roles.
Can I give different pods different Azure permissions?
Yes, that is exactly the point of Workload Identity. Each service account can be federated with a different managed identity, so your frontend pod might have read-only access to Blob Storage while your backend pod has read/write access to Key Vault.
What is the older alternative to Workload Identity?
AAD Pod Identity was the previous approach. It worked by intercepting IMDS (instance metadata service) calls on each node. Microsoft has deprecated AAD Pod Identity in favor of the newer Workload Identity approach, which uses federated credentials and is more reliable.