Pods on Azure Kubernetes Service still reach Azure with client secrets in Kubernetes Secrets or through node-level identities that every pod on the node can borrow, when the platform can federate each workload to its own identity instead. Microsoft Entra Workload ID lets a pod present a Kubernetes service account token, signed by the cluster's OIDC issuer, and exchange it for a Microsoft Entra access token without any stored secret. This guide is the step-by-step setup: enabling the issuer and webhook, creating the managed identity and federated credential, wiring the service account and pod, testing, migrating off pod-managed identity, and rolling back. It is written for platform engineers and Kubernetes administrators who run AKS Standard clusters. Cluster-wide identity hardening, Entra integration for human access and local account control are covered in the AKS Identity Hardening Guide.
This is an independent guide from NHI Mgmt Group, based on Microsoft's public AKS and Microsoft Entra Workload ID documentation as of November 2026. It is not sponsored or reviewed by Microsoft. Product features change, so confirm settings against Microsoft's current documentation.
Key takeaways
- Three objects make the trust: the cluster OIDC issuer URL, a federated identity credential on a user-assigned managed identity (or app registration), and a Kubernetes service account whose namespace and name form the subject
system:serviceaccount:<namespace>:<name>. - The pod must opt in: the mutating webhook only acts on pods labelled
azure.workload.identity/use: "true", and only then injectsAZURE_FEDERATED_TOKEN_FILEand the projected token volume. - The subject is the security boundary: anyone who can run a pod under that service account in that namespace gets the identity, so namespace RBAC and service account creation rights matter as much as Azure roles.
- Expect propagation delays: federated credentials take seconds to minutes to replicate and Azure role assignments up to 10 minutes, so build retries into tests and pipelines rather than treating the first failure as a misconfiguration.
- Pod-managed identity is finished: the open source project was deprecated on 24 October 2022 and the AKS add-on was supported through September 2025, so migrate with the parallel deployment path and treat the proxy sidecar as a short bridge only.
How the token exchange works
Workload ID uses Kubernetes service account token volume projection. The cluster acts as an OIDC issuer and publishes two endpoints: {IssuerURL}/.well-known/openid-configuration (discovery document) and {IssuerURL}/openid/v1/jwks (the RS256 public signing keys). When the application asks for a token, the Azure Identity library reads the projected service account token from the file named in AZURE_FEDERATED_TOKEN_FILE and sends it to Microsoft Entra ID as a client assertion. Entra ID fetches the cluster's keys, validates the signature, then checks the token's iss, sub and aud claims against a federated identity credential on the target identity. If all three match, it returns an access token for the requested resource.
| Component | Where it lives | What it contributes | Value to record |
|---|---|---|---|
| OIDC issuer | AKS cluster property oidcIssuerProfile.issuerUrl | Signs projected service account tokens and publishes the keys | Issuer URL, format https://{region}.oic.prod-aks.azure.com/{tenant_id}/{uuid} |
| Mutating admission webhook | Cluster add-on, enabled with --enable-workload-identity | Injects environment variables and the token volume into labelled pods | Webhook configuration ConfigMap azure-wi-webhook-config |
| User-assigned managed identity | Azure resource group | The Entra identity the pod becomes; holds Azure role assignments | Client ID and principal (object) ID |
| Federated identity credential | Child resource of the managed identity | Declares which issuer, subject and audience may sign in as the identity | Name, issuer, subject, audience api://AzureADTokenExchange |
| Kubernetes service account | Namespace in the cluster | Carries the azure.workload.identity/client-id annotation and defines the token subject | Namespace and name |
| Pod | Deployment, StatefulSet or Job | Uses the service account and the opt-in label | serviceAccountName and label |
Two lifetimes are in play. The projected service account token defaults to 3,600 seconds (configurable from 3,600 to 86,400 with an annotation) and Kubernetes refreshes the file in place, so code must re-read the file each time rather than cache it. The Entra access token is separate, and Microsoft's overview states it expires 24 hours after issue. Scopes must use the v2 form <resource>/.default, for example https://management.azure.com/.default; a bare resource URI can fail. The general model, including other platforms that use the same pattern, is explained in the Workload Identity Federation Guide.
Prerequisites and design decisions before you start
- Versions: Kubernetes 1.22 or later and Azure CLI 2.47.0 or later for Workload ID. On AKS Automatic clusters the OIDC issuer and Workload ID are preconfigured. For new AKS Standard clusters on Kubernetes 1.34 and later the OIDC issuer is enabled by default; existing clusters need it enabled manually.
- Client libraries: minimum Azure Identity versions are Azure.Identity 1.9.0 (.NET), azidentity 1.3.0 (Go), azure-identity 1.9.0 (Java), @azure/identity 3.2.0 (Node.js), azure-identity 1.13.0 (Python) and azure-identity-cpp 1.6.0 (C++). Older code is the main reason migrations stall.
- Permissions: you need rights to create the managed identity and federated credential, and a role that can create role assignments at the target scope, such as Role Based Access Control Administrator. Microsoft notes that Contributor and Key Vault Contributor cannot create role assignments.
- Unsupported cases: the virtual nodes add-on is not supported, and creating federated credentials on user-assigned managed identities is not supported in the Malaysia South region (create the identity in a supported region instead).
Choosing the identity mapping
| Mapping | How it is built | Use when | Trade-off |
|---|---|---|---|
| One-to-one | One service account, one managed identity, one federated credential | Default for every production workload | More identities to manage, but each role assignment reflects one workload |
| Many-to-one | Several service accounts each have a federated credential on the same identity | Replicas of the same app across namespaces or clusters | Counts against the limit of 20 federated credentials per identity; a breach in any namespace reaches the shared permissions |
| One-to-many | One service account, several identities, selected by changing the client ID annotation | A tool that genuinely needs different identities at different times | Harder to reason about in reviews; changing the annotation needs a pod restart |
Prefer a user-assigned managed identity over an app registration for workloads in your own tenant: there is no secret or certificate to manage alongside the federated credential, and the identity lives in a resource group with normal Azure RBAC over who can change it. Keep the kubelet identity and cluster identity out of application access entirely; their roles and IMDS exposure are dealt with in the Azure Managed Identities Security Guide.
Step-by-step configuration
The commands below follow Microsoft's deployment tutorial. Replace the variables with your own naming standard; examples here use a dedicated namespace rather than default.
- Enable the OIDC issuer and Workload ID. For a new cluster:
For an existing cluster runaz aks create --resource-group "$RG" --name "$CLUSTER" \ --enable-oidc-issuer --enable-workload-identity --generate-ssh-keysaz aks update --resource-group "$RG" --name "$CLUSTER" --enable-oidc-issuer --enable-workload-identity. Enabling the issuer on an existing cluster restarts the API server, causing brief downtime, and the issuer cannot be disabled once enabled. Schedule it in a change window. - Record the issuer URL.
The trailing slash is part of the value. Copy it exactly: leading or trailing whitespace in the federated credential issuer blocks the exchange.export AKS_OIDC_ISSUER="$(az aks show --name "$CLUSTER" --resource-group "$RG" \ --query "oidcIssuerProfile.issuerUrl" --output tsv)" - Create the user-assigned managed identity and capture its client ID and principal ID:
az identity create --name "$UAMI" --resource-group "$RG" --location "$LOCATION" export CLIENT_ID="$(az identity show -g "$RG" -n "$UAMI" --query clientId -o tsv)" export PRINCIPAL_ID="$(az identity show -g "$RG" -n "$UAMI" --query principalId -o tsv)" - Create the service account with the client ID annotation:
TheapiVersion: v1 kind: ServiceAccount metadata: name: orders-api namespace: orders annotations: azure.workload.identity/client-id: "<CLIENT_ID>"azure.workload.identity/tenant-idannotation is optional and defaults to the tenant in the webhook ConfigMap. - Create the federated identity credential.
Microsoft describesaz identity federated-credential create --name orders-api-aks-prod \ --identity-name "$UAMI" --resource-group "$RG" \ --issuer "$AKS_OIDC_ISSUER" \ --subject system:serviceaccount:orders:orders-api \ --audience api://AzureADTokenExchangeapi://AzureADTokenExchangeas the default and recommended audience and advises against changing it. The credential name is 3 to 120 characters and cannot be changed later, so encode cluster and workload in it. Mismatched values are accepted at creation and only fail at sign-in. - Grant least-privilege Azure roles to the identity's principal ID at the narrowest scope, for example a single key vault rather than its resource group:
Microsoft's tutorial uses a vault with Azure RBAC authorisation and purge protection enabled.az role assignment create --assignee-object-id "$PRINCIPAL_ID" \ --assignee-principal-type ServicePrincipal \ --role "Key Vault Secrets User" --scope "$KEYVAULT_ID" - Label the pod and reference the service account. In the Deployment's pod template set
serviceAccountName: orders-apiand the labelazure.workload.identity/use: "true". In application code useDefaultAzureCredentialorWorkloadIdentityCredentialfrom a supported library; never hard-code the token path, which the docs show only as an illustration. - Verify. Run
kubectl describe podand confirm the injectedAZURE_CLIENT_ID,AZURE_TENANT_IDandAZURE_FEDERATED_TOKEN_FILEvariables and the projected volume. Then check application logs for a successful call. Microsoft's sample imageghcr.io/azure/azure-workload-identity/msal-goreads a Key Vault secret and logs "successfully got secret" when the chain works.
Optional annotations worth knowing
| Annotation | Set on | Default | Use |
|---|---|---|---|
azure.workload.identity/client-id | Service account | None | Selects the identity; required in practice |
azure.workload.identity/tenant-id | Service account | AZURE_TENANT_ID from the webhook ConfigMap | Identity registered in another tenant |
azure.workload.identity/service-account-token-expiration | Service account or pod (pod wins) | 3600 seconds | Keep the default unless a workload cannot refresh; maximum 86,400 |
azure.workload.identity/skip-containers | Pod | Token added to all containers | Keep the token out of sidecars that do not need Azure, for example istio-proxy |
azure.workload.identity/inject-proxy-sidecar | Pod | false | Temporary migration proxy for code that still calls IMDS |
azure.workload.identity/proxy-sidecar-port | Pod | 8000 | Change only if the port clashes |
Use skip-containers deliberately. Every container that receives the token file can become the identity, so a logging or service mesh sidecar that never calls Azure should not hold it.
Infrastructure as code and automation
- Create credentials sequentially: concurrent creation of several federated credentials under the same managed identity can return HTTP 409 Conflict. The AzureRM Terraform provider from version 3.40.0 creates them sequentially; in ARM or Bicep loops use serial mode or
dependsOnchains. - Respect throttling: Microsoft documents create and update limits of 10 per second per tenant, 2 per second per subscription and 0.25 per second per resource, with HTTP 429 when exceeded.
- Keep the subject in code review: treat the namespace and service account name in the federated credential as a security-relevant value, owned by the same repository that defines the Kubernetes service account, so a rename in one place is caught in the other.
- Add retries: early token requests can fail with
AADSTS70021: No matching federated identity record found for presented assertionwhile the credential replicates. Pipelines that deploy and immediately smoke-test should wait and retry. - Restrict who can add credentials: Microsoft publishes an Azure Policy rule that denies the resource type
Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials. Assign it with exemptions for the subscriptions or resource groups where platform pipelines are allowed to create them. See the Azure Policy and Management Groups Guide.
Migrating from pod-managed identity
Microsoft Entra pod-managed identity worked by running Node Managed Identity (NMI) on every node to intercept calls to the Instance Metadata Service and decide which pod could have which identity, selected by the aadpodidbinding label. Microsoft states that the open source project was deprecated on 24 October 2022 and archived in September 2023, and that the AKS add-on was patched and supported through September 2025. Clusters still running it are past that support window.
| Situation | Migration path | Steps | Limits |
|---|---|---|---|
| Application already on a supported Azure Identity version | Parallel deployment | Create identity, federated credential and service account; restart the deployment; verify; remove pod identity annotations; remove the add-on | Lowest risk; recommended default |
| Older SDK, Linux containers, need time | Migration sidecar | Add inject-proxy-sidecar: "true"; webhook adds proxy-init and proxy containers that translate IMDS calls; verify; schedule SDK upgrade; remove sidecar | Linux only; Microsoft states it is not intended for long-term production use |
| Older SDK, long-term fix | Rewrite | Upgrade the library; test with pod identity; restart; verify Workload ID; remove pod identity | Most effort; no proxy overhead and works for Windows containers |
The most common code change is the scope format. ManagedIdentityCredential used the IMDS resource parameter, while WorkloadIdentityCredential calls the Entra v2 endpoint with scope, so https://vault.azure.net becomes https://vault.azure.net/.default. Once every workload is verified, remove the pod identity with az aks pod-identity delete for each binding and then disable the add-on, so NMI stops intercepting IMDS traffic.
Testing, rollback and day-two operations
- Pilot one non-critical workload in a non-production cluster, with the old credential still in place, and confirm the new identity in Entra sign-in logs before removing anything.
- Cut over by restart: annotation changes on the service account only take effect after a pod restart, which also makes rollback straightforward: revert the manifest and restart.
- Rollback per workload: remove the label and restore the previous credential source. Keep any secret you are replacing disabled rather than deleted until the workload has run cleanly through at least one token refresh cycle, then delete and revoke it. For secrets held in Kubernetes see How to migrate Kubernetes secrets to an external Secrets Manager.
- Cluster-level rollback:
az aks update --disable-workload-identityremoves the webhook. The OIDC issuer stays, because it cannot be disabled. - Rotate the issuer signing keys with
az aks oidc-issuer rotate-signing-keys. Old and new keys stay valid for 24 hours; to invalidate the old key at once, Microsoft says to rotate twice and restart pods that use projected tokens. Do this after any suspected key exposure and on a planned schedule. - Identity bindings: if you adopt AKS identity bindings, the webhook sets the token audience to
api://AKSIdentityBinding. A workload that also needs direct federation must project a second token with audienceapi://AzureADTokenExchange; reusing the binding token fails withAADSTS700212.
Detection and measurement
- Azure Activity Log: alert on writes and deletes to
federatedIdentityCredentialsresources and on role assignment writes for workload identities. A new federated credential with an unfamiliar issuer is a persistence path, because whoever controls that issuer can sign in as the identity. Route the logs as described in the Azure Monitor, Activity Log and Diagnostic Settings Guide. - Entra sign-in logs: managed identity sign-ins show which identities are used and from which resources; a workload identity that stops appearing after a cutover is a candidate for removal.
- Kubernetes audit logs: watch creation of service accounts, changes to
azure.workload.identity/client-idannotations and pods created with an unexpectedserviceAccountNamein namespaces that hold federated subjects. - Periodic audit: list every federated credential, its issuer and subject, and confirm the issuer is a cluster you still run. The process is set out in How to audit Entra workload identity federated credentials.
| Metric | Target | Why it matters |
|---|---|---|
| Share of AKS workloads calling Azure that use Workload ID | 100% for new workloads; rising quarter on quarter | Measures removal of stored secrets |
| Client secrets or certificates still mounted for Azure access | Falling to zero | Each one is a long-lived credential that can leak |
| Federated credentials whose issuer is not a live cluster | Zero | Orphaned trust left after cluster deletion |
| Identities shared by more than one namespace | Documented exceptions only | Shared identities widen blast radius |
| Workload identities with roles above resource-level scope | Zero without approval | Subscription-wide roles defeat per-pod identity |
Common failure patterns
- Missing pod label: the service account is annotated but pods lack
azure.workload.identity/use: "true", so nothing is injected and the library falls back to another credential, sometimes the node's. - Subject drift: a namespace or service account is renamed and the federated credential is not, producing silent sign-in failures; or a broad shared service account is reused across apps, so one subject grants every app's permissions.
- Namespace rights ignored: developers who can create pods in a namespace can run any pod under any service account there. Restrict pod creation in namespaces whose service accounts map to privileged identities; see the Kubernetes RBAC Guide.
- Testing too fast: pipelines fail immediately after creating the credential or role assignment, engineers widen roles to "fix" it, and the wider role is never reverted.
- Sidecar left in place: the migration proxy becomes permanent, keeping IMDS-style calls alive and delaying the library upgrade it was meant to buy time for.
- Orphaned trust: clusters are deleted but their federated credentials remain on identities that still hold roles.
Framework mapping
| Framework | Reference | How Workload ID supports it |
|---|---|---|
| NIST CSF 2.0 | PR.AA-01, PR.AA-05 | Workload credentials are managed by the organisation; permissions scoped per workload |
| NIST SP 800-53 Rev 5 | IA-9 Service Identification and Authentication; IA-5 Authenticator Management; AC-6 Least Privilege; AU-6 Audit Record Review | Each service authenticates with its own short-lived token; no stored authenticator; narrow role scope; review of federated credential changes |
| MITRE ATT&CK | T1552.005 Cloud Instance Metadata API; T1528 Steal Application Access Token; T1078.004 Cloud Accounts | Removes reliance on node metadata credentials; short token lifetimes limit stolen token value; monitoring of identity use and new trusts |
Practitioner checklist
- Enable the OIDC issuer and Workload ID on each AKS Standard cluster in a change window, and record the issuer URL in your identity inventory.
- Create one user-assigned managed identity per workload by default, with a federated credential whose name encodes cluster and workload.
- Use the subject
system:serviceaccount:<namespace>:<name>and the audienceapi://AzureADTokenExchangeexactly. - Assign Azure roles to the identity at resource scope, never subscription scope without approval.
- Label pods with
azure.workload.identity/use: "true", useskip-containersfor sidecars, and upgrade to the minimum Azure Identity versions. - Restrict pod creation and service account changes in namespaces that hold federated subjects.
- Migrate every pod-managed identity binding, remove the add-on, and set a removal date for any migration sidecar.
- Assign an Azure Policy deny on federated credential creation outside platform-owned scopes.
- Alert on federated credential writes and audit issuers quarterly for clusters that no longer exist.
- Rotate OIDC issuer signing keys on a schedule and twice after any suspected compromise.
Standards and references
- Microsoft Learn: Use Microsoft Entra Workload ID with Azure Kubernetes Service
- Microsoft Learn: Deploy and configure an AKS cluster with workload identity
- Microsoft Learn: Migrate from pod-managed identity to workload identity
- Microsoft Learn: Create an OpenID Connect provider on AKS
- Microsoft Learn: Important considerations and restrictions for federated identity credentials
- Microsoft Learn: Use Microsoft Entra pod-managed identities in AKS
- NIST: SP 800-53 Rev. 5 Security and Privacy Controls
Related NHI Mgmt Group resources: AKS Identity Hardening Guide · Workload Identity Federation Guide · Kubernetes Service Account Token Guide · Azure Managed Identities Security Guide · Kubernetes Workload Identity Checklist