Exercise 5: Secret Management with Azure Key Vault
The Azure Key Vault Provider for Secrets Store CSI Driver allows you to:
- Mount secrets from Azure Key Vault as volumes in your pods
- Automatically rotate secrets
- Keep sensitive data out of your manifests and container images
Task 1 - Set Up Azure Key Vault
The steps below assume the environment variables you setup in Lab 1 are still available. If not, please follow the steps to recreate them.
Create a Key Vault:
# Create Key Vault $KEYVAULT_NAME = "keyvault-$($INITIALS)" az keyvault create --name $KEYVAULT_NAME ` --resource-group $RESOURCE_GROUP ` --location $LOCATION Write-Host "Created Key Vault: $KEYVAULT_NAME"
# Create Key Vault KEYVAULT_NAME="keyvault-$INITIALS" az keyvault create --name $KEYVAULT_NAME \ --resource-group $RESOURCE_GROUP \ --location $LOCATION echo "Created Key Vault: $KEYVAULT_NAME"
Assign yourself the “Key Vault Secrets Officer” role to ensure you have permission to manage secrets:
# Get your user principal $USER_ID = (az ad signed-in-user show --query id -o tsv) # Get Key Vault resource ID $KEYVAULT_ID = (az keyvault show --name $KEYVAULT_NAME --resource-group $RESOURCE_GROUP --query id -o tsv) # Assign Key Vault Secrets Officer role az role assignment create ` --assignee $USER_ID ` --role "Key Vault Secrets Officer" ` --scope $KEYVAULT_ID Write-Host "Assigned Key Vault Secrets Officer role to your account"
# Get your user principal USER_ID=$(az ad signed-in-user show --query id -o tsv) # Get Key Vault resource ID KEYVAULT_ID=$(az keyvault show --name $KEYVAULT_NAME --resource-group $RESOURCE_GROUP --query id -o tsv) # Assign Key Vault Secrets Officer role az role assignment create \ --assignee $USER_ID \ --role "Key Vault Secrets Officer" \ --scope $KEYVAULT_ID echo "Assigned Key Vault Secrets Officer role to your account"
Add a secret to the Key Vault:
az keyvault secret set --vault-name $KEYVAULT_NAME ` --name "app-secret" ` --value "This is a secret value from Key Vault"
az keyvault secret set --vault-name $KEYVAULT_NAME \ --name "app-secret" \ --value "This is a secret value from Key Vault"
Task 2 - Configure AKS Access to Key Vault
Check if the Key Vault Secrets Provider add-on is enabled and get its identity:
# Get AKS name $AKS_NAME = (az aks list --resource-group $RESOURCE_GROUP --query '[0].name' -o tsv) # Check the Key Vault add-on status and get identity details $ADDON_INFO = (az aks show --resource-group $RESOURCE_GROUP --name $AKS_NAME --query 'addonProfiles.azureKeyvaultSecretsProvider' -o json | ConvertFrom-Json) # Check if add-on is enabled if ($ADDON_INFO.enabled -ne $true) { Write-Host "Enabling Azure Key Vault Secrets Provider add-on..." az aks enable-addons --addons azure-keyvault-secrets-provider --name $AKS_NAME --resource-group $RESOURCE_GROUP # Refresh add-on info after enabling $ADDON_INFO = (az aks show --resource-group $RESOURCE_GROUP --name $AKS_NAME --query 'addonProfiles.azureKeyvaultSecretsProvider' -o json | ConvertFrom-Json) } # Get the managed identity client ID $IDENTITY_CLIENT_ID = $ADDON_INFO.identity.clientId # Get the managed identity principal ID (object ID) $IDENTITY_PRINCIPAL_ID = $ADDON_INFO.identity.objectId Write-Host "Key Vault Add-on is enabled: $($ADDON_INFO.enabled)" Write-Host "Identity Client ID: $IDENTITY_CLIENT_ID" Write-Host "Identity Principal ID: $IDENTITY_PRINCIPAL_ID"
# Get AKS name AKS_NAME=$(az aks list --resource-group "$RESOURCE_GROUP" --query '[0].name' -o tsv) # Check if the Key Vault add-on is enabled ENABLED=$(az aks show --resource-group "$RESOURCE_GROUP" --name "$AKS_NAME" \ --query 'addonProfiles.azureKeyvaultSecretsProvider.enabled' -o tsv) if [[ "$ENABLED" != "true" ]]; then echo "Enabling Azure Key Vault Secrets Provider add-on..." az aks enable-addons --addons azure-keyvault-secrets-provider --name "$AKS_NAME" --resource-group "$RESOURCE_GROUP" # Refresh enabled state after enabling ENABLED=$(az aks show --resource-group "$RESOURCE_GROUP" --name "$AKS_NAME" \ --query 'addonProfiles.azureKeyvaultSecretsProvider.enabled' -o tsv) fi # Get the managed identity client ID and principal (object) ID IDENTITY_CLIENT_ID=$(az aks show --resource-group "$RESOURCE_GROUP" --name "$AKS_NAME" \ --query 'addonProfiles.azureKeyvaultSecretsProvider.identity.clientId' -o tsv) IDENTITY_PRINCIPAL_ID=$(az aks show --resource-group "$RESOURCE_GROUP" --name "$AKS_NAME" \ --query 'addonProfiles.azureKeyvaultSecretsProvider.identity.objectId' -o tsv) echo "Key Vault Add-on is enabled: $ENABLED" echo "Identity Client ID: $IDENTITY_CLIENT_ID" echo "Identity Principal ID: $IDENTITY_PRINCIPAL_ID"
Tip
You should see a message saying
Key Vault Add-on is enabled: true
followed by a GUID for each of the Identity Client ID and Identity Principal ID.Assign the appropriate role to the managed identity to access Key Vault:
$TENANT_ID = (az account show --query tenantId -o tsv) # Use RBAC for Key Vault access (more modern approach) az role assignment create ` --assignee $IDENTITY_PRINCIPAL_ID ` --role "Key Vault Secrets User" ` --scope $KEYVAULT_ID Write-Host "Tenant ID: $TENANT_ID"
TENANT_ID=$(az account show --query tenantId -o tsv) # Use RBAC for Key Vault access (more modern approach) az role assignment create \ --assignee $IDENTITY_PRINCIPAL_ID \ --role "Key Vault Secrets User" \ --scope $KEYVAULT_ID echo "Tenant ID: $TENANT_ID"
Task 3 - Install the CSI Driver
Since we already have the Azure Key Vault Secrets Provider add-on enabled, we don’t need to install the CSI driver manually. The add-on provides all the necessary components.
Verify the CSI driver installation:
kubectl get pods -n kube-system -l 'app in (secrets-store-csi-driver, secrets-store-provider-azure)'
kubectl get pods -n kube-system -l 'app in (secrets-store-csi-driver, secrets-store-provider-azure)'
You should see pods for both the CSI driver and the Azure provider running.
Task 4 - Create SecretProviderClass and Pod
Create a SecretProviderClass with your Key Vault information:
$secretProviderClass = @" apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: azure-kvname spec: provider: azure parameters: usePodIdentity: "false" useVMManagedIdentity: "true" userAssignedIdentityID: "$IDENTITY_CLIENT_ID" keyvaultName: "$KEYVAULT_NAME" objects: | array: - | objectName: app-secret objectType: secret objectVersion: "" tenantId: "$TENANT_ID" "@ $secretProviderClass | kubectl apply -f -
cat <<EOF | kubectl apply -f - apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: azure-kvname spec: provider: azure parameters: usePodIdentity: "false" useVMManagedIdentity: "true" userAssignedIdentityID: "$IDENTITY_CLIENT_ID" keyvaultName: "$KEYVAULT_NAME" objects: | array: - | objectName: app-secret objectType: secret objectVersion: "" tenantId: "$TENANT_ID" EOF
Create a pod that uses the SecretProviderClass:
$secretPod = @" apiVersion: v1 kind: Pod metadata: name: nginx-secrets-store spec: containers: - name: nginx image: k8sonazureworkshoppublic.azurecr.io/nginx volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "azure-kvname" "@ $secretPod | kubectl apply -f -
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: nginx-secrets-store spec: containers: - name: nginx image: k8sonazureworkshoppublic.azurecr.io/nginx volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "azure-kvname" EOF
Wait for the pod to be ready:
kubectl wait --for=condition=Ready pod/nginx-secrets-store --timeout=60s
kubectl wait --for=condition=Ready pod/nginx-secrets-store --timeout=60s
Task 5 - Verify Secret Access
Check if the secret is mounted:
kubectl exec -it nginx-secrets-store -- ls -la /mnt/secrets-store/
kubectl exec -it nginx-secrets-store -- ls -la /mnt/secrets-store/
You should see the
app-secret
file.View the contents of the secret:
kubectl exec -it nginx-secrets-store -- cat /mnt/secrets-store/app-secret
kubectl exec -it nginx-secrets-store -- cat /mnt/secrets-store/app-secret
This should display the secret value you stored in Key Vault.
Task 6 - Create Kubernetes Secret from Key Vault
Rather than just mounting the secret, you can also create a Kubernetes Secret from the Key Vault secret. This allows you to enable native integration with Kubernetes features that require Secret objects, such as environment variables, init containers, and Helm charts.
Update the SecretProviderClass to create a Kubernetes Secret:
$secretProviderClassSync = @" apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: azure-kvname-sync spec: provider: azure secretObjects: - secretName: keyvault-secret type: Opaque data: - objectName: app-secret key: app-secret parameters: usePodIdentity: "false" useVMManagedIdentity: "true" userAssignedIdentityID: "$IDENTITY_CLIENT_ID" keyvaultName: "$KEYVAULT_NAME" objects: | array: - | objectName: app-secret objectType: secret objectVersion: "" tenantId: "$TENANT_ID" "@ $secretProviderClassSync | kubectl apply -f -
cat <<EOF | kubectl apply -f - apiVersion: secrets-store.csi.x-k8s.io/v1 kind: SecretProviderClass metadata: name: azure-kvname-sync spec: provider: azure secretObjects: - secretName: keyvault-secret type: Opaque data: - objectName: app-secret key: app-secret parameters: usePodIdentity: "false" useVMManagedIdentity: "true" userAssignedIdentityID: "$IDENTITY_CLIENT_ID" keyvaultName: "$KEYVAULT_NAME" objects: | array: - | objectName: app-secret objectType: secret objectVersion: "" tenantId: "$TENANT_ID" EOF
The secret won’t actually exist until a pod consumes it. Create a pod that uses this SecretProviderClass:
$secretPodSync = @" apiVersion: v1 kind: Pod metadata: name: nginx-secrets-sync spec: containers: - name: nginx image: k8sonazureworkshoppublic.azurecr.io/nginx volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true env: - name: SECRET_FROM_KEYVAULT valueFrom: secretKeyRef: name: keyvault-secret key: app-secret volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "azure-kvname-sync" "@ $secretPodSync | kubectl apply -f -
cat <<EOF | kubectl apply -f - apiVersion: v1 kind: Pod metadata: name: nginx-secrets-sync spec: containers: - name: nginx image: k8sonazureworkshoppublic.azurecr.io/nginx volumeMounts: - name: secrets-store-inline mountPath: "/mnt/secrets-store" readOnly: true env: - name: SECRET_FROM_KEYVAULT valueFrom: secretKeyRef: name: keyvault-secret key: app-secret volumes: - name: secrets-store-inline csi: driver: secrets-store.csi.k8s.io readOnly: true volumeAttributes: secretProviderClass: "azure-kvname-sync" EOF
Wait for the pod to be ready:
kubectl wait --for=condition=Ready pod/nginx-secrets-sync --timeout=60s
kubectl wait --for=condition=Ready pod/nginx-secrets-sync --timeout=60s
Verify the Kubernetes Secret was created:
kubectl get secret keyvault-secret
kubectl get secret keyvault-secret
Check that the environment variable is set in the pod:
kubectl exec -it nginx-secrets-sync -- sh -c 'echo $SECRET_FROM_KEYVAULT'
kubectl exec -it nginx-secrets-sync -- sh -c 'echo $SECRET_FROM_KEYVAULT'
You should see the secret value from Key Vault.
Task 7 - Clean Up
Delete the pod and SecretProviderClass:
kubectl delete pod nginx-secrets-store kubectl delete pod nginx-secrets-sync kubectl delete secretproviderclass azure-kvname kubectl delete secretproviderclass azure-kvname-sync
kubectl delete pod nginx-secrets-store kubectl delete pod nginx-secrets-sync kubectl delete secretproviderclass azure-kvname kubectl delete secretproviderclass azure-kvname-sync
Delete the key vault and its contents:
az keyvault delete --name $KEYVAULT_NAME --resource-group $RESOURCE_GROUP
az keyvault delete --name $KEYVAULT_NAME --resource-group $RESOURCE_GROUP