Skip to main content

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
  1. AKS exposes an OIDC issuer URL
  2. A Federated Identity Credential is created on the Managed Identity, trusting the AKS OIDC issuer for a specific namespace/service account
  3. 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/"