Skip to main content
Version: main 🚧

AKS Workload Identity

Supported Configurations
Running the control plane as a container with:

Workload Identity allows Kubernetes workloads to securely access Azure resources without storing credentials in the cluster. It federates a Kubernetes Service Account with a Microsoft Entra identity, which improves security and simplifies identity management.

To configure Workload Identity in a cluster, you must link the Kubernetes Service Account (KSA) used by your workload with a Microsoft Entra Workload ID. This KSA must exist in the control plane cluster where the cluster runs.

Use the sync.toHost feature to expose the KSA to the control plane cluster.

Prerequisites​

Ensure you have the following:

Integrate AKS Workload Identity with vCluster​

  1. To integrate your workloads with Azure Kubernetes Service (AKS) Workload Identity, you must have a running instance of the vCluster Platform.

    If you have not set one up yet, follow the vCluster Platform installation guide.

    After deploying the platform, create a new access key to authenticate with the vCluster Platform API. This key is required to retrieve the synced Kubernetes Service Account (KSA) name and complete the Workload Identity setup.

  2. Set up the necessary environment variables. These variables include information about your Azure resource group, vCluster details, and authentication keys.

    Modify the following with your specific values to generate a copyable command:
    export RESOURCE_GROUP_NAME=vcluster-demo-rg
    export SERVICE_ACCOUNT_NAME=vcluster-demo
    export SERVICE_ACCOUNT_NAMESPACE=default
    export VCLUSTER_NAME=vcluster-demo
    export VCLUSTER_NAMESPACE=team-x
    export AKS_CLUSTER_NAME=my-aks-cluster
    export HOST=YOUR_PLATFORM_HOST
    export ACCESS_KEY=YOUR_ACCESS_KEY
    export REGION=westeurope
    export FEDERATED_IDENTITY_CREDENTIAL_NAME=vcluster-demo
    export USER_ASSIGNED_IDENTITY_NAME=vcluster-demo-workload-identity

    Ensure you specify the correct Azure Resource Group name, vCluster Platform host, and auth key.

  3. Create a vcluster.yaml file that includes the following configuration:

    sync:
    toHost:
    serviceAccounts:
    enabled: true
  4. Run the following command to deploy the cluster:

    vcluster create $VCLUSTER_NAME --namespace $VCLUSTER_NAMESPACE --values vcluster.yaml
  5. Create a Kubernetes ServiceAccount that can be used to interact with the cluster:

    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: ServiceAccount
    metadata:
    name: "${SERVICE_ACCOUNT_NAME}"
    namespace: "${SERVICE_ACCOUNT_NAMESPACE}"
    EOF
  6. vCluster rewrites the ServiceAccount name when it syncs the ServiceAccount to the control plane cluster. The synced name follows the pattern NAME-x-NAMESPACE-x-VCLUSTER_NAME. Cloud IAM trust policies must reference that synced name, not the name used inside the cluster.

    vCluster caps the rewritten name at 63 characters. This cap is vCluster's own, not a Kubernetes limit, since Kubernetes accepts ServiceAccount names up to 253 characters. When the rewritten name is longer, vCluster keeps its first 52 characters and appends - plus the first 10 characters of the hex-encoded SHA-256 digest of the full rewritten name. A trust policy built from the untruncated name doesn't match, and the failure surfaces as a permissions error rather than a naming error.

    Resolve the name through the API

    Don't hard-code synced ServiceAccount names or reimplement the truncation rule in your own automation. Query the vCluster Platform API as shown below so the name stays correct when namespace or cluster names change.

    This applies to ServiceAccounts that sync into the vCluster control plane's namespace, which is the default. If you enable namespace syncing and the ServiceAccount's namespace matches a mapping, vCluster keeps the name unchanged and the API returns a name that doesn't exist. In that case, reference the ServiceAccount by its original name in the mapped namespace on the control plane cluster.

    Define a function to fetch the KSA name using curl and use it to export KSA_NAME environment variable.

    # Define the function to get the KSA name using curl
    get_ksa_name() {
    local vcluster_ksa_name=$1
    local vcluster_ksa_namespace=$2
    local vcluster_name=$3
    local host=$4
    local access_key=$5

    local resource_path="/kubernetes/management/apis/management.loft.sh/v1/translatevclusterresourcenames"
    local host_with_scheme=$([[ $host =~ ^(http|https):// ]] && echo "$host" || echo "https://$host")
    local sanitized_host="${host_with_scheme%/}"
    local full_url="${sanitized_host}${resource_path}"

    local response=$(curl -s -k -X POST "$full_url" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer ${access_key}" \
    -d @- <<EOF
    {
    "spec": {
    "name": "${vcluster_ksa_name}",
    "namespace": "${vcluster_ksa_namespace}",
    "vclusterName": "${vcluster_name}"
    }
    }
    EOF
    )

    local status_name=$(echo "$response" | jq -r '.status.name')
    if [[ -z "$status_name" || "$status_name" == "null" ]]; then
    echo "Error: Unable to fetch KSA name from response: $response"
    exit 1
    fi
    echo "$status_name"
    }

    # Get the KSA name
    export KSA_NAME=$(get_ksa_name "$SERVICE_ACCOUNT_NAME" "$SERVICE_ACCOUNT_NAMESPACE" "$VCLUSTER_NAME" "$HOST" "$ACCESS_KEY")
  7. Create the federated identity credential between the managed identity, the service account issuer, and the subject.

    First, create the managed identity and retrieve the required values:

    export SUBSCRIPTION="$(az account show --query id --output tsv)"
    az identity create \
    --name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --location "${REGION}" \
    --subscription "${SUBSCRIPTION}"


    export AKS_OIDC_ISSUER="$(az aks show --name "${AKS_CLUSTER_NAME}" \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --query "oidcIssuerProfile.issuerUrl" \
    --output tsv)"

    export USER_ASSIGNED_CLIENT_ID="$(az identity show \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --query 'clientId' \
    --output tsv)"

    export TENANT_ID="$(az identity show \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --query 'tenantId' \
    --output tsv)"

    Then create the federated identity credential. The namespace parameter depends on your vCluster deployment type:

    Namespace configuration
    • vCluster (not using the platform): Use the namespace where vCluster is deployed
    • Platform-managed vCluster: The namespace follows the pattern loft-<project-name>-v-<vcluster-name>

    For vCluster deployments not connected with the platform (i.e. deployed with vcluster create or Helm without the platform):

    az identity federated-credential create \
    --name ${FEDERATED_IDENTITY_CREDENTIAL_NAME} \
    --identity-name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --issuer "${AKS_OIDC_ISSUER}" \
    --subject system:serviceaccount:"${VCLUSTER_NAMESPACE}":"${KSA_NAME}" \
    --audience api://AzureADTokenExchange
  8. Assign the necessary RBAC roles to the managed identity so it can access Azure resources. For this example, you'll assign the Reader role at the subscription level:

    # Get the principal ID of the managed identity
    export PRINCIPAL_ID="$(az identity show \
    --resource-group "${RESOURCE_GROUP_NAME}" \
    --name "${USER_ASSIGNED_IDENTITY_NAME}" \
    --query 'principalId' \
    --output tsv)"

    # Assign Reader role to the managed identity at subscription level
    az role assignment create \
    --assignee "${PRINCIPAL_ID}" \
    --role "Reader" \
    --scope "/subscriptions/${SUBSCRIPTION}"

    # Wait for role assignment to propagate
    echo "Waiting for role assignment to propagate..."
    sleep 30

    # Verify the role assignment
    az role assignment list --assignee "${PRINCIPAL_ID}" --output table
    note

    You can assign more specific roles and scopes based on your requirements. For example, if you need the identity to manage specific resource groups or resources, assign appropriate roles such as "Contributor" or custom roles with specific permissions.

  9. Annotate the service account with the user-assigned identity client ID and tenant ID:

    kubectl annotate serviceaccount \
    "${SERVICE_ACCOUNT_NAME}" \
    -n "${SERVICE_ACCOUNT_NAMESPACE}" \
    azure.workload.identity/use="true" \
    azure.workload.identity/client-id="${USER_ASSIGNED_CLIENT_ID}" \
    azure.workload.identity/tenant-id="${TENANT_ID}"
  10. Deploy an application. Replace placeholders with your actual values:

    cat <<EOF | kubectl apply -f -
    apiVersion: v1
    kind: Pod
    metadata:
    name: azure-cli-test
    labels:
    app: azure-cli-test
    azure.workload.identity/use: "true"
    spec:
    serviceAccountName: "${SERVICE_ACCOUNT_NAME}"
    containers:
    - name: azure-cli
    image: mcr.microsoft.com/azure-cli:latest
    command: ["/bin/bash", "-c", "--"]
    args:
    - |
    echo "Testing Azure CLI login";
    az login --service-principal -u \$AZURE_CLIENT_ID -t \$AZURE_TENANT_ID --federated-token \$(cat \$AZURE_FEDERATED_TOKEN_FILE);
    az account show;
    sleep 3600
    EOF
  11. Verify that the setup is complete and the pod is able to access Azure resources using the Workload Identity.

    kubectl logs -l app=azure-cli-test -n default