Integrate Workload Identity with AKS
A step-by-step guide to integrating Azure AD Workload Identity with AKS, so pods can access Azure resources securely using managed identities — no secrets required.
In this guide, we will explore how to integrate Azure AD Workload Identity with Azure Kubernetes Service (AKS). This integration allows AKS workloads to securely access Azure resources using managed identities, without needing to manage secrets or credentials directly within your applications.
Prerequisites
Account Permission
Before we get started, ensure the following:
- You are logged in with the Azure CLI as a user.
- Your logged-in account must have sufficient permissions to create user-assigned managed identities in Azure.
Tools
- Azure CLI version ≥ 2.42.0
- Helm 3
Managed Cluster
- A Kubernetes cluster with version ≥ v1.22
Components Installation
OIDC
The OpenID Connect metadata URL of the issuer of the identity provider that Azure AD will utilize in the token exchange protocol for validating tokens before issuing a token as the user-assigned managed identity.
Run the command if you don't have any cluster:
az aks create \
--resource-group myResourceGroup \
--name myAKSCluster \
--node-count 1 \
--enable-oidc-issuer \
--generate-ssh-keysOtherwise, update the existing cluster with the command:
az aks update --resource-group myResourceGroup --name myAKSCluster --enable-oidc-issuerThen get your cluster's OIDC issuer URL, which will be used later:
az aks show --resource-group <resource_group> --name <cluster_name> --query "oidcIssuerProfile.issuerUrl" -otsvMutating Admission Webhook
It has the following features:
- Projects a signed service account token to a well-known path.
- Injects authentication-related environment variables to your pods based on the annotated service account.
Install by Azure CLI
az aks update --resource-group myResourceGroup --name myAKSCluster --enable-workload-identityInstall by Helm
You can refer to this document.
The AKS cluster will create a workload identity controller once the above command is run.
Export Environment Variables
Now let's check what we have:
- OIDC issuer URL
- Resources in AKS for Workload Identity Webhook
Before going to the next steps, we should export the following environment variables:
export RESOURCE_GROUP="azwi-quickstart-$(openssl rand -hex 2)"
export USER_ASSIGNED_IDENTITY_NAME="<your user-assigned managed identity name>"
export SERVICE_ACCOUNT_NAMESPACE="default"
export SERVICE_ACCOUNT_NAME="workload-identity-sa"
export SERVICE_ACCOUNT_ISSUER="<your service account issuer URL>"Create a User-Assigned Managed Identity and Grant Permissions to Access Your Resources
Create a managed identity which will bind with the service account:
az identity create --name "${USER_ASSIGNED_IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}"And assign a role for it. For example, assign Azure Kubernetes Service Cluster Admin Role for this identity if you want to let a pod list cluster credentials.
Create a Kubernetes Service Account
Create a service account for binding the managed identity:
export USER_ASSIGNED_IDENTITY_CLIENT_ID="$(az identity show --name "${USER_ASSIGNED_IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --query 'clientId' -otsv)"
export USER_ASSIGNED_IDENTITY_OBJECT_ID="$(az identity show --name "${USER_ASSIGNED_IDENTITY_NAME}" --resource-group "${RESOURCE_GROUP}" --query 'principalId' -otsv)"cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
annotations:
azure.workload.identity/client-id: ${USER_ASSIGNED_IDENTITY_CLIENT_ID}
name: ${SERVICE_ACCOUNT_NAME}
namespace: ${SERVICE_ACCOUNT_NAMESPACE}
EOFIf your managed identity and service account are in different tenants, you should annotate the service account to let the workload identity manager be aware of it.
Establish Federated Identity Credential Between the Identity and the Service Account Issuer & Subject
Binding the service account and managed identity:
az identity federated-credential create \
--name "kubernetes-federated-credential" \
--identity-name "${USER_ASSIGNED_IDENTITY_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--issuer "${SERVICE_ACCOUNT_ISSUER}" \
--subject "system:serviceaccount:${SERVICE_ACCOUNT_NAMESPACE}:${SERVICE_ACCOUNT_NAME}"Deploy Workload
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: quick-start
namespace: ${SERVICE_ACCOUNT_NAMESPACE}
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: ${SERVICE_ACCOUNT_NAME}
containers:
- image: mcr.microsoft.com/azure-cli
name: oidc
command: ["tail", "-f", "/dev/null"] # Run tail -f /dev/null to keep the container running
resources:
requests:
memory: "256Mi" # Request 256 MiB of memory
cpu: "100m" # Request 100 millicpu (0.1 CPU core)
limits:
memory: "512Mi" # Limit to 512 MiB of memory
cpu: "500m" # Limit to 500 millicpu (0.5 CPU core)
nodeSelector:
kubernetes.io/os: linux
EOFYou will see AZURE_AUTHORITY_HOST, AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_FEDERATED_TOKEN_FILE have been injected into quick-start. Refer to the doc to know the meaning of each attribute.
kubectl describe pod quick-startVerification
You can attach to the Pod quick-start and execute the following command:
az login --service-principal --tenant $AZURE_TENANT_ID --federated-token "$(cat ${AZURE_FEDERATED_TOKEN_FILE})" -u $AZURE_CLIENT_IDWe can also use an init container to get credentials in the beginning and use them later:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: quick-start
namespace: ${SERVICE_ACCOUNT_NAMESPACE}
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: ${SERVICE_ACCOUNT_NAME}
initContainers:
- name: azure-login
image: mcr.microsoft.com/azure-cli
command:
- /bin/sh
- -c
# 1. It should add escape symbol because environment variables should not be interpolated by the current shell
# 2. It should ensure ACL for kube config or it will throw permission error
- |
az login --service-principal --tenant \$AZURE_TENANT_ID --federated-token "\$(cat \$AZURE_FEDERATED_TOKEN_FILE)" -u \$AZURE_CLIENT_ID && \
az aks get-credentials --resource-group myResourceGroup --name myAKSCluster --admin --file /custom/.kube/config && \
addgroup -S myUser && \
adduser -S myUser -G myUser && \
chown myUser /custom/.kube/config
volumeMounts:
- name: aks-credentials
mountPath: /custom
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "256Mi"
cpu: "200m"
containers:
- image: my-workload:latest
name: Workload
command: ["tail", "-f", "/dev/null"] # Keep the container running
volumeMounts:
- name: aks-credentials
mountPath: /custom
env:
- name: KUBECONFIG
value: /custom/.kube/config
resources:
requests:
memory: "512Mi"
cpu: "500m"
limits:
memory: "512Mi"
cpu: "500m"
volumes:
- name: aks-credentials
emptyDir: {}
nodeSelector:
kubernetes.io/os: linux
EOF