09: Azure Key Vault Integration
09: Azure Key Vault Integration
Objective
- Understand why Azure Key Vault is preferred over Kubernetes Secrets for production workloads
- Create an Azure Key Vault and add secrets
- Set up Workload Identity (Managed Identity + federation) to allow Pods to authenticate to Key Vault
- Create a SecretProviderClass and mount Key Vault secrets in a Pod
Theory
Why Azure Key Vault?
Kubernetes Secrets have several limitations in production:
| Problem | Details |
|---|---|
| Base64 is not encryption | Anyone with RBAC access can decode Secret values trivially |
| Stored in etcd | Even with encryption at rest, secrets live inside the cluster |
| No audit trail | Kubernetes audit logs exist but are not as rich as a dedicated secret management platform |
| No rotation | Kubernetes Secrets do not support automatic secret rotation natively |
| No central management | Secrets are per-cluster; multi-cluster environments need duplication |
Azure Key Vault solves these problems by acting as an external, centralized secret store:
- Secrets are stored outside the cluster in a FIPS 140-2 compliant vault
- Fine-grained access policies and Azure RBAC control who can read or manage secrets
- Full audit logging via Azure Monitor and Microsoft Entra ID
- Built-in secret rotation and versioning
- Supports secrets, keys, and certificates
How It Works: Azure Key Vault Secrets Provider
The Azure Key Vault Provider for Secrets Store CSI Driver is an AKS add-on that makes Key Vault secrets available to Pods as mounted volumes.
flowchart LR
KV["Azure Key Vault<br/>Stores secrets, keys, certs"]
CSI["Secrets Store<br/>CSI Driver<br/>(runs on each node)"]
SPC["SecretProviderClass<br/>(CRD in your namespace)"]
POD["Pod<br/>mounts the volume"]
FS["Container Filesystem<br/>/mnt/secrets/"]
KV -->|"authenticated read"| CSI
SPC -->|"defines which secrets"| CSI
CSI -->|"provides volume"| POD
POD -->|"volumeMount"| FS
Authentication Flow: Workload Identity
Workload Identity allows a Pod to authenticate to Azure services without stored credentials:
- You create an Azure Managed Identity
- You grant that identity access to Key Vault
- You create a Federated Identity Credential that links a Kubernetes ServiceAccount to the Managed Identity
- You create a Kubernetes ServiceAccount annotated with the Managed Identity client ID
- The Pod uses this ServiceAccount — the CSI driver authenticates to Key Vault automatically
flowchart LR
SA["K8s ServiceAccount<br/>(annotated with client ID)"]
FIC["Federated Identity<br/>Credential"]
MI["Managed Identity"]
KV["Key Vault"]
Pod["Pod"]
Pod -->|uses| SA
SA -->|OIDC token| FIC
FIC -->|validates| MI
MI -->|authenticates| KV
Practical Tasks
Replace
XXwith your student number throughout (e.g.,01,02, …).
Task 1: Verify the CSI Driver Is Installed
The Secrets Store CSI Driver runs as a DaemonSet in kube-system. Check that it is running:
# Check CSI driver and Azure Key Vault provider pods
kubectl get pods -n kube-system -l 'app in (secrets-store-csi-driver,secrets-store-provider-azure)'
You should see one CSI driver pod and one provider pod per node.
Verify the SecretProviderClass CRD is available:
kubectl api-resources | grep SecretProviderClass
If the CSI driver pods are not running or the CRD does not exist, inform the instructor — the AKS add-on may not be enabled.
Task 2: Create an Azure Key Vault
Create your own Key Vault. Key Vault names must be globally unique, so include your student number:
# Set variables (replace XX with your number)
export RESOURCE_GROUP="k8s-aks-training-rg"
export LOCATION="swedencentral"
export KV_NAME="kv-student-XX"
# Create the Key Vault
az keyvault create \
--name $KV_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--enable-rbac-authorization true
Note: We use
--enable-rbac-authorization trueso that access is controlled via Azure RBAC roles (not the older access policies).
Now add some secrets:
# You need the Key Vault Secrets Officer role to set secrets
# First, get your own user object ID
MY_OBJECT_ID=$(az ad signed-in-user show --query id -o tsv)
# Assign yourself Key Vault Secrets Officer on the vault
az role assignment create \
--role "Key Vault Secrets Officer" \
--assignee-object-id $MY_OBJECT_ID \
--assignee-principal-type User \
--scope $(az keyvault show --name $KV_NAME --query id -o tsv)
# Wait a moment for the role to propagate, then add secrets
sleep 30
az keyvault secret set --vault-name $KV_NAME --name "db-password" --value "SuperSecret123!"
az keyvault secret set --vault-name $KV_NAME --name "api-key" --value "ak-training-key-2026"
Verify the secrets exist:
az keyvault secret list --vault-name $KV_NAME -o table
Task 3: Create a Managed Identity
Create an Azure Managed Identity that your Pod will use to authenticate to Key Vault:
export IDENTITY_NAME="id-student-XX"
az identity create \
--name $IDENTITY_NAME \
--resource-group $RESOURCE_GROUP \
--location $LOCATION
# Save the client ID and principal ID — you will need them
export IDENTITY_CLIENT_ID=$(az identity show --name $IDENTITY_NAME --resource-group $RESOURCE_GROUP --query clientId -o tsv)
export IDENTITY_PRINCIPAL_ID=$(az identity show --name $IDENTITY_NAME --resource-group $RESOURCE_GROUP --query principalId -o tsv)
echo "Client ID: $IDENTITY_CLIENT_ID"
Now grant this identity read access to your Key Vault secrets:
az role assignment create \
--role "Key Vault Secrets User" \
--assignee-object-id $IDENTITY_PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--scope $(az keyvault show --name $KV_NAME --query id -o tsv)
Task 4: Set Up Workload Identity Federation
Link the Managed Identity to a Kubernetes ServiceAccount using federated credentials.
First, get the AKS cluster’s OIDC issuer URL:
export CLUSTER_NAME="k8s-aks-training"
export OIDC_ISSUER=$(az aks show \
--name $CLUSTER_NAME \
--resource-group $RESOURCE_GROUP \
--query "oidcIssuerProfile.issuerUrl" -o tsv)
echo "OIDC Issuer: $OIDC_ISSUER"
Create the federated credential. This tells Entra ID: “trust tokens from this Kubernetes ServiceAccount”:
export SERVICE_ACCOUNT_NAME="sa-keyvault-XX"
export NAMESPACE="student-XX"
az identity federated-credential create \
--name "fed-student-XX" \
--identity-name $IDENTITY_NAME \
--resource-group $RESOURCE_GROUP \
--issuer $OIDC_ISSUER \
--subject "system:serviceaccount:${NAMESPACE}:${SERVICE_ACCOUNT_NAME}" \
--audiences "api://AzureADTokenExchange"
What this does: When a Pod in namespace
student-XXuses ServiceAccountsa-keyvault-XX, Kubernetes issues an OIDC token. Azure Entra ID trusts this token (because of the federated credential) and grants the Pod the Managed Identity’s permissions.
Task 5: Create the Kubernetes ServiceAccount
Create a ServiceAccount in your namespace, annotated with the Managed Identity client ID:
export TENANT_ID=$(az account show --query tenantId -o tsv)
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ServiceAccount
metadata:
name: $SERVICE_ACCOUNT_NAME
namespace: $NAMESPACE
annotations:
azure.workload.identity/client-id: "$IDENTITY_CLIENT_ID"
labels:
azure.workload.identity/use: "true"
EOF
Verify:
kubectl get sa $SERVICE_ACCOUNT_NAME -n $NAMESPACE -o yaml
You should see the azure.workload.identity/client-id annotation with your Managed Identity’s client ID.
Task 6: Create a SecretProviderClass
The SecretProviderClass tells the CSI driver which Key Vault to connect to and which secrets to fetch.
Create the file secret-provider-class.yaml:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv-secrets
namespace: student-XX
spec:
provider: azure
parameters:
usePodIdentity: "false"
clientID: "<IDENTITY_CLIENT_ID>" # paste your $IDENTITY_CLIENT_ID
keyvaultName: "kv-student-XX"
tenantId: "<TENANT_ID>" # paste your $TENANT_ID
objects: |
array:
- |
objectName: db-password
objectType: secret
- |
objectName: api-key
objectType: secret
Tip: Run
echo $IDENTITY_CLIENT_IDandecho $TENANT_IDto get the values to paste.
kubectl apply -f secret-provider-class.yaml
kubectl get secretproviderclass -n student-XX
Task 7: Create a Pod That Mounts Key Vault Secrets
Create the file pod-keyvault.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-keyvault
namespace: student-XX
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: sa-keyvault-XX
containers:
- name: app
image: busybox
command:
- sh
- -c
- |
echo "Secrets mounted:"
ls /mnt/secrets/
echo "---"
echo "db-password: $(cat /mnt/secrets/db-password)"
echo "api-key: $(cat /mnt/secrets/api-key)"
sleep 3600
volumeMounts:
- name: secrets-store
mountPath: /mnt/secrets
readOnly: true
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: azure-kv-secrets
restartPolicy: Never
Important: The label
azure.workload.identity/use: "true"on the Pod is required — the Workload Identity webhook uses it to inject the token volume.
kubectl apply -f pod-keyvault.yaml
# Watch the Pod start
kubectl get pod pod-keyvault -n student-XX -w
If the Pod is stuck in ContainerCreating, check events:
kubectl describe pod pod-keyvault -n student-XX
Task 8: Verify Secrets Are Available
Once the Pod is running:
# Check the logs — secrets should be printed
kubectl logs pod-keyvault -n student-XX
# Or exec in and read them directly
kubectl exec pod-keyvault -n student-XX -- ls /mnt/secrets/
kubectl exec pod-keyvault -n student-XX -- cat /mnt/secrets/db-password
kubectl exec pod-keyvault -n student-XX -- cat /mnt/secrets/api-key
Expected output:
Secrets mounted:
api-key
db-password
---
db-password: SuperSecret123!
api-key: ak-training-key-2026
Each Key Vault secret becomes a file in the mounted directory. The filename matches the objectName from the SecretProviderClass.
Task 9: Challenge — Troubleshoot a Broken SecretProviderClass
Challenge: The following SecretProviderClass has three deliberate errors. Can you find them all without applying the manifest?
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: broken-spc
spec:
provider: azurekeyvault
parameters:
usePodIdentity: "true"
clientID: "<IDENTITY_CLIENT_ID>"
keyvaultName: "<KEY_VAULT_NAME>"
tenantId: "<TENANT_ID>"
objects: |
array:
- |
objectName: my-secret
objectType: secrets
Answers
1. **`provider: azurekeyvault`** should be **`provider: azure`** — the provider name for Azure Key Vault is `azure`, not `azurekeyvault` 2. **`usePodIdentity: "true"`** should be **`usePodIdentity: "false"`** — Pod Identity v1 is deprecated; Workload Identity uses `clientID` directly 3. **`objectType: secrets`** should be **`objectType: secret`** — the value is singular (`secret`, `key`, or `cert`), not pluralClean Up
# Delete Kubernetes resources
kubectl delete pod pod-keyvault -n student-XX
kubectl delete secretproviderclass azure-kv-secrets -n student-XX --ignore-not-found
kubectl delete sa sa-keyvault-XX -n student-XX
# Delete Azure resources
az keyvault delete --name $KV_NAME --resource-group $RESOURCE_GROUP
az keyvault purge --name $KV_NAME --location $LOCATION 2>/dev/null
az identity delete --name $IDENTITY_NAME --resource-group $RESOURCE_GROUP
Common Problems
| Problem | Cause | Solution |
|---|---|---|
| CSI driver pods not running | AKS add-on not enabled | Inform the instructor — az aks enable-addons --addons azure-keyvault-secrets-provider |
Pod stuck in ContainerCreating |
CSI driver cannot authenticate to Key Vault | Check kubectl describe pod for CSI errors; verify federated credential and role assignment |
AADSTS70021: No matching federated identity record found |
Federation subject mismatch | Verify namespace and ServiceAccount name match exactly in the federated credential |
keyvault error: secret not found |
Secret name mismatch | Check objectName matches the actual Key Vault secret name |
403 Forbidden from Key Vault |
Managed Identity missing Key Vault Secrets User role | Re-run the az role assignment create command from Task 3 |
SecretProviderClass not found |
CRD not installed | Verify with kubectl api-resources \| grep SecretProviderClass |
| Role assignment not taking effect | Propagation delay | Wait 30-60 seconds after creating a role assignment |
Best Practices
- Use Key Vault for production secrets — Keep secrets outside the cluster in a centrally managed, audited store.
- Use Workload Identity — This is the recommended authentication method for AKS workloads accessing Azure resources. Avoid pod-managed identity (deprecated).
- Set
readOnly: trueon CSI volume mounts — Prevent accidental writes. - Use separate Key Vaults per environment — Do not share a Key Vault between dev and production.
- Enable Key Vault audit logging — Monitor who accesses secrets and when.
- Use RBAC authorization on Key Vault — Prefer
--enable-rbac-authorizationover legacy access policies. - One identity per workload — Each application should have its own Managed Identity with minimal permissions.
Summary
In this exercise you learned how to:
- Create an Azure Key Vault and add secrets
- Create a Managed Identity and grant it Key Vault access via Azure RBAC
- Set up Workload Identity federation (OIDC issuer, federated credential, annotated ServiceAccount)
- Create a SecretProviderClass that references specific Key Vault secrets
- Mount Key Vault secrets as files in a Pod using the CSI volume driver
- Troubleshoot common issues with identity, federation, and permissions
This concludes the Configuration module. Proceed to Exercise 10 to learn about resource requests and limits.