Azure AD & Workload Identity
Azure Workload Identity is the recommended method for granting pods access to Azure services without storing credentials. It replaces the deprecated AAD Pod Identity and uses Kubernetes' native ServiceAccount token projection with Azure AD federation.
How It Works
Pod → ServiceAccount token → Azure AD OIDC federation → Managed Identity → Azure API
- AKS exposes an OIDC issuer URL
- A Federated Identity Credential is created on the Managed Identity, trusting the AKS OIDC issuer for a specific namespace/service account
- The pod's ServiceAccount token is automatically projected and exchanged for an Azure AD access token via the Azure Identity SDK
1. Create a Managed Identity
terraform/environments/production/workload-identity.tf
resource "azurerm_user_assigned_identity" "payments_api" {
name = "payments-api-production"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
}
# Grant the identity access to Key Vault secrets
resource "azurerm_role_assignment" "payments_api_kv" {
scope = azurerm_key_vault.main.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_user_assigned_identity.payments_api.principal_id
}
# Federated credential — trusts the AKS OIDC issuer for the payments-api SA
resource "azurerm_federated_identity_credential" "payments_api" {
name = "payments-api-federated"
resource_group_name = azurerm_resource_group.main.name
audience = ["api://AzureADTokenExchange"]
issuer = azurerm_kubernetes_cluster.main.oidc_issuer_url
parent_id = azurerm_user_assigned_identity.payments_api.id
subject = "system:serviceaccount:team-payments:payments-api"
}
2. Annotate the Kubernetes ServiceAccount
kubernetes/namespaces/team-payments/serviceaccount.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: payments-api
namespace: team-payments
annotations:
azure.workload.identity/client-id: "<MANAGED_IDENTITY_CLIENT_ID>"
labels:
azure.workload.identity/use: "true"
3. Label the Pod
kubernetes/apps/payments-api/deployment.yaml
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: payments-api
4. Access Azure Resources in Code
The Azure Identity SDK automatically picks up the projected token — no code changes from AAD Pod Identity or managed identity:
src/services/secretsClient.ts
import { SecretClient } from "@azure/keyvault-secrets";
import { DefaultAzureCredential } from "@azure/identity";
// DefaultAzureCredential automatically uses Workload Identity in AKS
const credential = new DefaultAzureCredential();
const client = new SecretClient(
"https://devopsgenie-production.vault.azure.net/",
credential
);
const secret = await client.getSecret("payments-db-password");
Azure Key Vault CSI Integration
Alternatively, sync Key Vault secrets directly into Kubernetes Secrets using the CSI Secrets Store Provider:
kubernetes/secrets/payments-keyvault.yaml
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: payments-keyvault
namespace: team-payments
spec:
provider: azure
parameters:
usePodIdentity: "false"
clientID: "<MANAGED_IDENTITY_CLIENT_ID>"
keyvaultName: "devopsgenie-production"
tenantID: "<AZURE_TENANT_ID>"
objects: |
array:
- |
objectName: payments-db-password
objectType: secret
objectVersion: ""
- |
objectName: payments-api-key
objectType: secret
secretObjects:
- secretName: payments-secrets
type: Opaque
data:
- objectName: payments-db-password
key: db-password
- objectName: payments-api-key
key: api-key
Verification
# Confirm the webhook injected the token
kubectl describe pod <pod-name> -n team-payments | grep -A5 "azure-workload-identity"
# Test identity from inside the pod
kubectl exec -it deploy/payments-api -n team-payments -- \
curl -H "Metadata: true" \
"http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net/"