Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
In this article, you learn how to deploy and configure an Azure Kubernetes Service (AKS) cluster with Microsoft Entra Workload ID. The steps in this article include:
- Create a new or update an existing AKS cluster using the Azure CLI or Terraform with OpenID Connect (OIDC) issuer and Microsoft Entra Workload ID enabled.
- Create a workload identity and Kubernetes service account.
- Configure the managed identity for token federation.
- Deploy the workload and verify authentication with the workload identity.
- Optionally grant a pod in the cluster access to secrets in an Azure key vault.
Prerequisites
- If you don't have an Azure account, create a free account before you begin.
- This article requires version 2.47.0 or later of the Azure CLI. If using Azure Cloud Shell, the latest version is already installed. Run
az --versionto find the version. If you need to install or upgrade, see Install Azure CLI. - Make sure that the identity that you're using to create your cluster has the appropriate minimum permissions. For more information, see Access and identity options for Azure Kubernetes Service (AKS).
- If you have multiple Azure subscriptions, select the appropriate subscription ID in which the resources should be billed using the
az account setcommand.
- Terraform installed locally. For installation instructions, see Install Terraform.
Note
You can use Service Connector to help you configure some steps automatically. For more information, see Tutorial: Connect to Azure storage account in Azure Kubernetes Service (AKS) with Service Connector using Microsoft Entra Workload ID.
Create the Terraform configuration file
Terraform configuration files define the infrastructure that Terraform creates and manages.
Create a file named
main.tfand add the following code to define the Terraform version and specify the Azure provider:terraform { required_version = ">= 1.5.0" required_providers { azurerm = { source = "hashicorp/azurerm" version = "~> 4.0" } kubernetes = { source = "hashicorp/kubernetes" version = "~> 2.30" } random = { source = "hashicorp/random" version = "~> 3.6" } } } provider "azurerm" { features {} subscription_id = var.subscription_id } data "azurerm_client_config" "current" {}Add the following code to
main.tfto define reusable variables and generate unique names for all resources:resource "random_string" "suffix" { length = 6 upper = false special = false numeric = true } locals { suffix = random_string.suffix.result resource_group_name = "rg-aks-wi-${local.suffix}" cluster_name = "akswi${local.suffix}" managed_identity_name = "uami-wi-${local.suffix}" federated_credential_name = "fic-wi-${local.suffix}" key_vault_name = lower(substr("kvwi${local.suffix}", 0, 24)) secret_name = "secret-${local.suffix}" service_account_name = "workload-sa-${local.suffix}" service_account_namespace = "default" workload_identity_subject = "system:serviceaccount:${local.service_account_namespace}:${local.service_account_name}" }
Create a resource group
Create a resource group using the az group create command.
export RANDOM_ID="$(openssl rand -hex 3)"
export RESOURCE_GROUP="myResourceGroup$RANDOM_ID"
export LOCATION="<your-preferred-region>"
az group create --name "${RESOURCE_GROUP}" --location "${LOCATION}"
Add the following code to main.tf to create an Azure resource group. Update the location value to match your preferred Azure region.
resource "azurerm_resource_group" "this" {
name = local.resource_group_name
location = "eastus"
}
Enable OIDC issuer and Microsoft Entra Workload ID on an AKS cluster
You can enable OIDC issuer and Microsoft Entra Workload ID on a new or existing AKS cluster.
Create an AKS cluster using the az aks create command with the --enable-oidc-issuer parameter to enable OIDC issuer and the --enable-workload-identity parameter to enable Microsoft Entra Workload ID. The following example creates a cluster with a single node:
export CLUSTER_NAME="myAKSCluster$RANDOM_ID"
az aks create \
--resource-group "${RESOURCE_GROUP}" \
--name "${CLUSTER_NAME}" \
--enable-oidc-issuer \
--enable-workload-identity \
--generate-ssh-keys
After a few minutes, the command completes and returns JSON-formatted information about the cluster.
Add the following code to main.tf to create an AKS cluster with OIDC issuer and Microsoft Entra Workload ID enabled:
resource "azurerm_kubernetes_cluster" "this" {
name = local.cluster_name
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
dns_prefix = local.cluster_name
oidc_issuer_enabled = true
workload_identity_enabled = true
role_based_access_control_enabled = true
default_node_pool {
name = "system"
node_count = 1
vm_size = "Standard_B4ms"
}
identity {
type = "SystemAssigned"
}
}
Retrieve the OIDC issuer URL
Get the OIDC issuer URL using the az aks show command and save it to an environmental variable.
export AKS_OIDC_ISSUER="$(az aks show --name "${CLUSTER_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--query "oidcIssuerProfile.issuerUrl" \
--output tsv)"
The environment variable should contain the issuer URL, similar to the following example:
https://eastus.oic.prod-aks.azure.com/00000000-0000-0000-0000-000000000000/11111111-1111-1111-1111-111111111111/
By default, the issuer is set to use the base URL https://{region}.oic.prod-aks.azure.com/{tenant_id}/{uuid}, where the value for {region} matches the location to which the AKS cluster is deployed. The value {uuid} represents the OIDC key, which is a randomly generated and immutable GUID for each cluster.
Add the following code to main.tf to retrieve the OIDC issuer URL:
output "oidc_issuer_url" {
value = azurerm_kubernetes_cluster.this.oidc_issuer_url
}
Create a managed identity
Get your subscription ID and save it to an environment variable using the
az account showcommand.export SUBSCRIPTION="$(az account show --query id --output tsv)"Create a user-assigned managed identity using the
az identity createcommand.export USER_ASSIGNED_IDENTITY_NAME="myIdentity$RANDOM_ID" az identity create \ --name "${USER_ASSIGNED_IDENTITY_NAME}" \ --resource-group "${RESOURCE_GROUP}" \ --location "${LOCATION}" \ --subscription "${SUBSCRIPTION}"The following output example shows successful creation of a managed identity:
{ "clientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourcegroups/myResourceGroupxxxxxx/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myIdentityxxxxxx", "location": "eastus", "name": "myIdentityxxxxxx", "principalId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "resourceGroup": "myResourceGroupxxxxxx", "systemData": null, "tags": {}, "tenantId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", "type": "Microsoft.ManagedIdentity/userAssignedIdentities" }Get the client ID of the managed identity and save it to an environment variable using the
az identity showcommand.export USER_ASSIGNED_CLIENT_ID="$(az identity show \ --resource-group "${RESOURCE_GROUP}" \ --name "${USER_ASSIGNED_IDENTITY_NAME}" \ --query 'clientId' \ --output tsv)"
Add the following code to main.tf to create a managed identity:
resource "azurerm_user_assigned_identity" "this" {
name = local.managed_identity_name
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
}
Create a Kubernetes service account
Connect to your AKS cluster using the
az aks get-credentialscommand.az aks get-credentials --name "${CLUSTER_NAME}" --resource-group "${RESOURCE_GROUP}"Create a Kubernetes service account and annotate it with the client ID of the managed identity by applying the following manifest using the
kubectl applycommand.export SERVICE_ACCOUNT_NAME="workload-identity-sa$RANDOM_ID" export SERVICE_ACCOUNT_NAMESPACE="default" cat <<EOF | kubectl apply -f - apiVersion: v1 kind: ServiceAccount metadata: annotations: azure.workload.identity/client-id: "${USER_ASSIGNED_CLIENT_ID}" name: "${SERVICE_ACCOUNT_NAME}" namespace: "${SERVICE_ACCOUNT_NAMESPACE}" EOFThe following output shows successful creation of the workload identity:
serviceaccount/workload-identity-sa created
Add the following code to
main.tfto configure Kubernetes access to allow creation of Kubernetes resources:data "azurerm_kubernetes_cluster" "this" { name = azurerm_kubernetes_cluster.this.name resource_group_name = azurerm_resource_group.this.name } provider "kubernetes" { host = data.azurerm_kubernetes_cluster.this.kube_config[0].host client_certificate = base64decode(data.azurerm_kubernetes_cluster.this.kube_config[0].client_certificate) client_key = base64decode(data.azurerm_kubernetes_cluster.this.kube_config[0].client_key) cluster_ca_certificate = base64decode(data.azurerm_kubernetes_cluster.this.kube_config[0].cluster_ca_certificate) }Add the following code to
main.tfto create a Kubernetes service account and annotate it with the client ID of the managed identity:resource "kubernetes_service_account" "this" { metadata { name = local.service_account_name namespace = local.service_account_namespace annotations = { "azure.workload.identity/client-id" = azurerm_user_assigned_identity.this.client_id } } }
Create the federated identity credential
Create a federated identity credential between the managed identity, the service account issuer, and the subject using the az identity federated-credential create command.
export FEDERATED_IDENTITY_CREDENTIAL_NAME="myFedIdentity$RANDOM_ID"
az identity federated-credential create \
--name ${FEDERATED_IDENTITY_CREDENTIAL_NAME} \
--identity-name "${USER_ASSIGNED_IDENTITY_NAME}" \
--resource-group "${RESOURCE_GROUP}" \
--issuer "${AKS_OIDC_ISSUER}" \
--subject system:serviceaccount:"${SERVICE_ACCOUNT_NAMESPACE}":"${SERVICE_ACCOUNT_NAME}" \
--audience api://AzureADTokenExchange
Note
It takes a few seconds for the federated identity credential to propagate after it's added. If a token request is made immediately after adding the federated identity credential, the request might fail until the cache is refreshed. To avoid this issue, you can add a slight delay after adding the federated identity credential.
Add the following code to main.tf to create a federated identity credential between the managed identity, the service account issuer, and the subject:
resource "azurerm_federated_identity_credential" "this" {
name = local.federated_credential_name
resource_group_name = azurerm_resource_group.this.name
parent_id = azurerm_user_assigned_identity.this.id
issuer = azurerm_kubernetes_cluster.this.oidc_issuer_url
subject = local.workload_identity_subject
audience = ["api://AzureADTokenExchange"]
}
For more information about federated identity credentials in Microsoft Entra, see Overview of federated identity credentials in Microsoft Entra ID.
Create a key vault with Azure RBAC authorization
The following example shows how to use the Azure role-based access control (Azure RBAC) permission model to grant the pod access to the key vault. For more information about the Azure RBAC permission model for Azure Key Vault, see Grant permission to applications to access an Azure key vault using Azure RBAC.
Create a key vault with purge protection and Azure RBAC authorization enabled using the
az keyvault createcommand. You can also use an existing key vault if it's configured for both purge protection and Azure RBAC authorization.export KEYVAULT_NAME="keyvault-workload-id$RANDOM_ID" # Ensure the key vault name is between 3-24 characters az keyvault create \ --name "${KEYVAULT_NAME}" \ --resource-group "${RESOURCE_GROUP}" \ --location "${LOCATION}" \ --enable-purge-protection \ --enable-rbac-authorizationGet the key vault resource ID and save it to an environment variable using the
az keyvault showcommand.export KEYVAULT_RESOURCE_ID=$(az keyvault show --resource-group "${RESOURCE_GROUP}" \ --name "${KEYVAULT_NAME}" \ --query id \ --output tsv)
Add the following code to main.tf to create a key vault with Azure RBAC authorization:
resource "azurerm_key_vault" "this" {
name = local.key_vault_name
location = azurerm_resource_group.this.location
resource_group_name = azurerm_resource_group.this.name
tenant_id = data.azurerm_client_config.current.tenant_id
sku_name = "standard"
rbac_authorization_enabled = true
}
Assign RBAC permissions for key vault management
Get the caller object ID and save it to an environment variable using the
az ad signed-in-user showcommand.export CALLER_OBJECT_ID=$(az ad signed-in-user show --query id -o tsv)Assign yourself the Azure RBAC Key Vault Secrets Officer role so that you can create a secret in the new key vault using the
az role assignment createcommand.az role assignment create --assignee "${CALLER_OBJECT_ID}" \ --role "Key Vault Secrets Officer" \ --scope "${KEYVAULT_RESOURCE_ID}"
Add the following code to main.tf to assign yourself the Azure RBAC Key Vault Secrets Officer role so that you can create a secret in the new key vault and assign the Key Vault Secrets User role to the user-assigned managed identity:
resource "azurerm_role_assignment" "user" {
scope = azurerm_key_vault.this.id
role_definition_name = "Key Vault Secrets Officer"
principal_id = data.azurerm_client_config.current.object_id
}
resource "azurerm_role_assignment" "identity" {
scope = azurerm_key_vault.this.id
role_definition_name = "Key Vault Secrets User"
principal_id = azurerm_user_assigned_identity.this.principal_id
}
Create and configure secret access
Create a secret in the key vault using the
az keyvault secret setcommand.export KEYVAULT_SECRET_NAME="my-secret$RANDOM_ID" az keyvault secret set \ --vault-name "${KEYVAULT_NAME}" \ --name "${KEYVAULT_SECRET_NAME}" \ --value "Hello\!"Get the principal ID of the user-assigned managed identity and save it to an environment variable using the
az identity showcommand.export IDENTITY_PRINCIPAL_ID=$(az identity show \ --name "${USER_ASSIGNED_IDENTITY_NAME}" \ --resource-group "${RESOURCE_GROUP}" \ --query principalId \ --output tsv)Assign the Key Vault Secrets User role to the user-assigned managed identity using the
az role assignment createcommand. This step gives the managed identity permission to read secrets from the key vault.az role assignment create \ --assignee-object-id "${IDENTITY_PRINCIPAL_ID}" \ --role "Key Vault Secrets User" \ --scope "${KEYVAULT_RESOURCE_ID}" \ --assignee-principal-type ServicePrincipalCreate an environment variable for the key vault URL using the
az keyvault showcommand.export KEYVAULT_URL="$(az keyvault show \ --resource-group "${RESOURCE_GROUP}" \ --name ${KEYVAULT_NAME} \ --query properties.vaultUri \ --output tsv)"
Add the following code to main.tf to create a secret in the key vault:
resource "azurerm_key_vault_secret" "this" {
name = local.secret_name
value = "Hello from Key Vault"
key_vault_id = azurerm_key_vault.this.id
}
Deploy a verification pod and test access
Deploy a pod to verify that the workload identity can access the secret in the key vault. The following example uses the
ghcr.io/azure/azure-workload-identity/msal-goimage, which contains a sample application that retrieves a secret from Azure Key Vault using Microsoft Entra Workload ID:kubectl apply -f - <<EOF apiVersion: v1 kind: Pod metadata: name: sample-workload-identity-key-vault namespace: ${SERVICE_ACCOUNT_NAMESPACE} labels: azure.workload.identity/use: "true" spec: serviceAccountName: ${SERVICE_ACCOUNT_NAME} containers: - image: ghcr.io/azure/azure-workload-identity/msal-go name: oidc env: - name: KEYVAULT_URL value: ${KEYVAULT_URL} - name: SECRET_NAME value: ${KEYVAULT_SECRET_NAME} nodeSelector: kubernetes.io/os: linux EOFWait for the pod to be in the
Readystate using thekubectl waitcommand.kubectl wait --namespace ${SERVICE_ACCOUNT_NAMESPACE} --for=condition=Ready pod/sample-workload-identity-key-vault --timeout=120sCheck that the
SECRET_NAMEenvironment variable is set in the pod using thekubectl describecommand.kubectl describe pod sample-workload-identity-key-vault | grep "SECRET_NAME:"If successful, the output should be similar to the following example:
SECRET_NAME: ${KEYVAULT_SECRET_NAME}Verify that pods can get a token and access the resource using the
kubectl logscommand.kubectl logs sample-workload-identity-key-vaultIf successful, the output should be similar to the following example:
I0114 10:35:09.795900 1 main.go:63] "successfully got secret" secret="Hello\\!"Important
Azure RBAC role assignments can take up to 10 minutes to propagate. If the pod is unable to access the secret, you might need to wait for the role assignment to propagate. For more information, see Troubleshoot Azure RBAC.
Disable Microsoft Entra Workload ID on an AKS cluster
Disable Microsoft Entra Workload ID on the AKS cluster where it's been enabled and configured, update the AKS cluster using the az aks update command with the --disable-workload-identity parameter.
az aks update \
--resource-group "${RESOURCE_GROUP}" \
--name "${CLUSTER_NAME}" \
--disable-workload-identity
Deploy a verification pod
Add the following code to main.tf to deploy a verification pod that uses the workload identity to access the secret in the key vault:
resource "kubernetes_pod" "test" {
metadata {
name = "workload-identity-test"
namespace = local.service_account_namespace
labels = {
"azure.workload.identity/use" = "true"
}
}
spec {
service_account_name = kubernetes_service_account.this.metadata[0].name
container {
name = "test"
image = "ghcr.io/azure/azure-workload-identity/msal-go"
env {
name = "KEYVAULT_URL"
value = azurerm_key_vault.this.vault_uri
}
env {
name = "SECRET_NAME"
value = azurerm_key_vault_secret.this.name
}
}
}
}
Initialize Terraform
Initialize Terraform in the directory containing your main.tf file using the terraform init command. This command downloads the Azure provider required to manage Azure resources with Terraform.
terraform init
Create a Terraform execution plan
Create a Terraform execution plan using the terraform plan command. This command shows you the resources that Terraform will create or modify in your Azure subscription.
terraform plan
Apply the Terraform configuration
After reviewing and confirming the execution plan, apply the Terraform configuration using the terraform apply command. This command creates or modifies the resources defined in your main.tf file in your Azure subscription.
terraform apply
Verify the deployment
Connect to your AKS cluster using the
az aks get-credentialscommand.az aks get-credentials --name <cluster-name> --resource-group <resource-group>Check the status of the verification pod using the
kubectl get podscommand.Once the pod reaches a
Readystate, verify it can access the key vault secret by checking the pod logs using thekubectl logscommand.kubectl logs workload-identity-test
Related content
In this article, you deployed a Kubernetes cluster and configured it to use Microsoft Entra Workload ID in preparation for application workloads to authenticate with that credential. Now you're ready to deploy your application and configure it to use the workload identity with the latest version of the Azure Identity client library. If you can't rewrite your application to use the latest client library version, you can set up your application pod to authenticate using managed identity with workload identity as a short-term migration solution.
The Service Connector integration helps simplify the connection configuration for AKS workloads and Azure backing services. It securely handles authentication and network configurations and follows best practices for connecting to Azure services. For more information, see Connect to Azure OpenAI in Foundry Models in AKS using Microsoft Entra Workload Identity and the Service Connector introduction.