Deploy and configure an application using workload identity (preview)

Azure Workload Identity enables your pods to authenticate to Azure services by using short-lived, automatically rotated federation tokens instead of stored credentials. This approach means you don't need to manually rotate secrets, and it reduces the risk of credential leaks.

Azure Red Hat OpenShift with hosted control planes clusters deploy the azure-workload-identity-webhook webhook (MutatingWebhookConfiguration) by default. When you annotate a service account and label your pods, the webhook automatically injects the federation tokens and environment variables your application needs to authenticate.

Prepare your environment

Set the required environment variables and verify that your cluster’s OIDC configuration and workload identity webhook are in place before creating Azure identity resources.

  1. Set the OIDC issuer URL.

    The OIDC issuer URL is used to verify that a token presented by a pod actually came from the expected OpenShift cluster, completing the trust chain between a Kubernetes service account and an Azure managed identity without any shared secrets.

    OIDC_ISSUER=$(oc get authentication.config.openshift.io/cluster -o jsonpath='{.spec.serviceAccountIssuer}')
    
  2. Verify the OIDC issuer URL.

    echo $OIDC_ISSUER
    

    Example output:

    https://<region>.oic.aro-hcp.azure.com/<tenant-id>/<cluster-id>
    
  3. Set the Azure tenant and subscription IDs.

    TENANT_ID=$(az account show --query tenantId -o tsv)
    SUBSCRIPTION_ID=$(az account show --query id -o tsv) 
    RESOURCE_GROUP="my-resource-group"
    
  4. Verify the OIDC discovery endpoint.

    oc get --raw /.well-known/openid-configuration | jq .
    
  5. Confirm that the issuer field matches OIDC_ISSUER, and that the jwks_uri is reachable.

  6. Confirm the webhook is running.

    oc get mutatingwebhookconfigurations azure-workload-identity-webhook -o yaml | grep -E 'name:|namespaceSelector|failurePolicy'
    

Example output:

 name: azure-workload-identity-webhook
 failurePolicy: Fail
 name: pod-identity-webhook.azure.mutate.io
 namespaceSelector: {}

Create Azure identity resources

Create the Azure resources that link your Kubernetes service account to an Azure identity: a user-assigned managed identity, a role assignment that grants it permissions, and a federated credential that establishes trust with your cluster’s OIDC issuer.

  1. Create a user-assigned managed identity.

    IDENTITY_NAME="test-identity"
    
    az identity create --name "${IDENTITY_NAME}" \
     --resource-group "${RESOURCE_GROUP}" \
     --location "$(az group show --name ${RESOURCE_GROUP} --query location -o tsv)"
    
    MSI_CLIENT_ID=$(az identity show --name "${IDENTITY_NAME}" \
     --resource-group "${RESOURCE_GROUP}" \
     --query clientId -o tsv)
    
  2. Assign the required Azure role to the managed identity.

    The role you assign depends on the Azure resources your application needs to access.

    The following example assigns the Reader role scoped to a resource group:

    MSI_OBJECT_ID=$(az identity show --name "${IDENTITY_NAME}" \
       -resource-group "${RESOURCE_GROUP}" \
     --query principalId -o tsv)
    
    az role assignment create --assignee-object-id "${MSI_OBJECT_ID}" \
     --assignee-principal-type ServicePrincipal \
     --role "Reader" \
     --scope "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}"
    
  3. Create a federated identity credential.

    The federated identity credential links a Kubernetes service account to the Azure managed identity. The --audiences value must match the audience that the webhook injects (default: api://AzureADTokenExchange).

    TEST_NAMESPACE="wi-test"
    TEST_SERVICE_ACCOUNT="wi-test-sa"
    
    az identity federated-credential create \
     --name "aro-hcp-${TEST_NAMESPACE}-${TEST_SERVICE_ACCOUNT}" \
     --identity-name "${IDENTITY_NAME}" \
     --resource-group "${RESOURCE_GROUP}" \
     --issuer "${OIDC_ISSUER}" \
     --subject "system:serviceaccount:${TEST_NAMESPACE}:${TEST_SERVICE_ACCOUNT}" \
     --audiences "api://AzureADTokenExchange"
    
  4. Display the federated credential and verify that the issuer, subject, and audiences fields match the values you specified.

    az identity federated-credential show --name "aro-hcp-${TEST_NAMESPACE}-${TEST_SERVICE_ACCOUNT}" \
     --identity-name "${IDENTITY_NAME}" \
     --resource-group "${RESOURCE_GROUP}"
    

Deploy an application with workload identity

Create the Kubernetes resources your application needs to use workload identity: a labeled service account that triggers the webhook, and a pod that uses that service account.

  1. Create a namespace and an annotated service account.

    The azure.workload.identity/use: "true" label on the service account triggers the webhook to mutate pods that use this service account. Without this label, the webhook ignores the pod.

    oc create namespace "${TEST_NAMESPACE}" 2>/dev/null || true
    
    cat <<EOF | oc apply -f -
    piVersion: v1
    kind: ServiceAccount
    metadata:
     name: ${TEST_SERVICE_ACCOUNT}
     namespace: ${TEST_NAMESPACE}
     annotations:
       azure.workload.identity/client-id: "${MSI_CLIENT_ID}"
       azure.workload.identity/tenant-id: "${TENANT_ID}"
     labels:
       azure.workload.identity/use: "true"
    EOF
    
  2. Deploy a pod that uses the annotated service account.

    The pod must also have the azure.workload.identity/use: "true" label.

    cat <<EOF | oc apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
     name: wi-test-pod
     namespace: ${TEST_NAMESPACE}
     labels:
       azure.workload.identity/use: "true"
    spec:
     serviceAccountName: ${TEST_SERVICE_ACCOUNT}
     containers:
     - name: azure-cli
       image: mcr.microsoft.com/azure-cli:latest
       command: ["sleep", "3600"]
       resources:
         requests:
           cpu: 100m
           memory: 128Mi
         limits:
           cpu: 200m
           memory: 256Mi
     restartPolicy: Never
    EOF
    
  3. Wait for the pod to be ready.

    oc wait --for=condition=Ready pod/wi-test-pod -n "${TEST_NAMESPACE}" \
     --timeout=120s
    

Verify webhook injection

Confirm that the workload identity webhook correctly mutates your pod by checking that the expected environment variables, projected token volume, and token file are present.

  1. Check the injected environment variables.

    oc describe pod wi-test-pod -n "${TEST_NAMESPACE}" | \
     grep -A1 -E "AZURE_|azure"
    

    The webhook injects the following environment variables:

    Variable Expected value
    AZURE_CLIENT_ID The client ID of your managed identity.
    AZURE_TENANT_ID Your Azure tenant ID.
    AZURE_FEDERATED_TOKEN_FILE /var/run/secrets/azure/tokens/azure-identity-token
    AZURE_AUTHORITY_HOST https://login.microsoftonline.com/
    1. Verify that the audience for the projected token is api://AzureADTokenExchange.
    oc get pod wi-test-pod -n "${TEST_NAMESPACE}" -o json | \
     jq '.spec.volumes[] | select(.name | contains("azure"))'
    

    Example output:

    {
     "name": "azure-identity-token",
     "projected": {
       "defaultMode": 420,
       "sources": [
     {
       "serviceAccountToken": {
             "audience": "api://AzureADTokenExchange",
             "expirationSeconds": 3600,
             "path": "azure-identity-token"
           }
         }
       ]
     }
    }
    
  2. Verify that the volume is mounted at /var/run/secrets/azure/tokens.

    oc get pod wi-test-pod -n "${TEST_NAMESPACE}" -o json | \
     jq '.spec.containers[0].volumeMounts[] | select(.mountPath | contains("azure"))'
    

    Example output:

    {
     "mountPath": "/var/run/secrets/azure/tokens",
     "name": "azure-identity-token",
     "readOnly": true
    }
    
  3. Verify the token file exists inside the pod.

    oc exec -n "${TEST_NAMESPACE}" wi-test-pod \
     -- ls -la /var/run/secrets/azure/tokens/
    

    The output should list the azure-identity-token file.

Verify Azure authentication

Test that the injected workload identity credentials can successfully authenticate to Azure services, using both the Azure CLI and the Azure SDK.

Verify Azure authentication by using the Azure CLI

Use the Azure CLI inside the pod to verify that the workload identity credentials are valid end-to-end. This step confirms that the injected environment variables and federated token can successfully authenticate to Azure and access resources.

  1. Start an interactive shell on the wi-test-pod.

    oc exec -it -n "${TEST_NAMESPACE}" wi-test-pod -- bash
    
  2. Log in by using workload identity. The webhook already sets all required environment variables.

    az login --federated-token "$(cat $AZURE_FEDERATED_TOKEN_FILE)" \
     --service-principal \
     -u "$AZURE_CLIENT_ID" \
     -t "$AZURE_TENANT_ID"
    
  3. Verify the authentication.

    az account show
    
  4. Test an Azure API call.

    az group show --name "<resource-group-name>"
    

    If the authentication succeeds and the API call returns results, workload identity is fully functional.

Verify Azure authentication by using the Azure SDK

Deploy a Python pod to verify that applications using the Azure SDK can authenticate automatically through workload identity. This procedure validates the programmatic authentication path that your real applications use. The DefaultAzureCredential class discovers the injected credentials without any explicit configuration, confirming that your workloads can authenticate to Azure services without code changes.

  1. Deploy a Python pod.

    cat <<'PYEOF' | oc apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
     name: wi-test-sdk
     namespace: wi-test
     labels:
       azure.workload.identity/use: "true"
    spec:
     serviceAccountName: wi-test-sa
     containers:
     - name: python
       image: python:3.11-slim
       command:
       - bash
       - -c
       - |
         pip install azure-identity azure-mgmt-resource -q
         python3 -c "
         from azure.identity import DefaultAzureCredential
         from azure.mgmt.resource import ResourceManagementClient
         import os
         credential = DefaultAzureCredential()
         token = credential.get_token('https://management.azure.com/.default')
         print(f'Token acquired! Expires: {token.expires_on}')
         print('SUCCESS: Workload Identity is fully functional!')
         "
       env:
       - name: AZURE_SUBSCRIPTION_ID
         value: "<SUBSCRIPTION_ID>"
     restartPolicy: Never
    PYEOF
    
  2. Wait for the pod to be ready.

    oc wait --for=condition=Ready pod/wi-test-sdk -n wi-test --timeout=180s || true
    
  3. Get the logs for the pod.

    oc logs wi-test-sdk -n wi-test
    

    Example output:

    Token acquired! Expires: 1774644054
    SUCCESS: Workload Identity is fully functional!