09: Azure Key Vault Integration
09: Azure Key Vault Integration
Objective
- Understand why Azure Key Vault is preferred over Kubernetes Secrets for production workloads
- Learn how the Azure Key Vault Secrets Provider (CSI driver) works with AKS
- Create a SecretProviderClass to reference Key Vault secrets
- Mount Key Vault secrets as files in a Pod
- Verify secrets are accessible inside the container
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
Key Concepts
| Concept | Description |
|---|---|
| Secrets Store CSI Driver | A DaemonSet running on every node that implements the CSI interface for secret stores |
| Azure Key Vault Provider | A plugin for the CSI driver that knows how to fetch secrets from Azure Key Vault |
| SecretProviderClass | A Custom Resource (CRD) that defines which Key Vault to connect to and which secrets to fetch |
| Workload Identity | The authentication mechanism that allows Pods to authenticate to Azure Key Vault using a Kubernetes ServiceAccount linked to an Azure Managed Identity |
Authentication Flow
In production, the recommended authentication method is Workload Identity:
- A Kubernetes ServiceAccount is annotated with an Azure Managed Identity client ID.
- The Pod uses this ServiceAccount.
- The CSI driver uses the identity to authenticate to Key Vault.
- Key Vault checks its access policy and returns the secrets.
Instructor Setup (Reference Only)
Note: The following setup steps are performed by the instructor before the exercise. Participants do not need to run these commands. They are included here so you understand the full picture.
The instructor has:
- Created an Azure Key Vault with secrets (
db-password,api-key) - Enabled the AKS Key Vault Secrets Provider add-on (
az aks enable-addons --addons azure-keyvault-secrets-provider) - Created a Managed Identity and configured it with Key Vault access policies
- Created a Kubernetes ServiceAccount in each student namespace, linked to the Managed Identity via Workload Identity federation
- Stored the Key Vault name, identity client ID, and tenant ID for participants to use in their SecretProviderClass manifests
These are cluster-level and Azure-level operations that require elevated permissions.
Practical Tasks
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)'
Expected output:
NAME READY STATUS RESTARTS AGE
aks-secrets-store-csi-driver-xxxxx 3/3 Running 0 ...
aks-secrets-store-csi-driver-yyyyy 3/3 Running 0 ...
aks-secrets-store-provider-azure-zzzzz 1/1 Running 0 ...
aks-secrets-store-provider-azure-wwwww 1/1 Running 0 ...
You should see one CSI driver pod and one provider pod per node.
Verify the SecretProviderClass CRD is available:
kubectl api-resources | grep SecretProviderClass
Expected output:
secretproviderclasses ... secrets-store.csi.x-k8s.io/v1 true 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 a SecretProviderClass
A SecretProviderClass defines which Key Vault to connect to and which secrets to retrieve.
Note: Replace
<KEY_VAULT_NAME>with the Key Vault name provided by the instructor. Replace<IDENTITY_CLIENT_ID>and<TENANT_ID>with the values provided by the instructor.
Create the file secret-provider-class.yaml:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv-secrets
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "false"
clientID: "<IDENTITY_CLIENT_ID>"
keyvaultName: "<KEY_VAULT_NAME>"
tenantId: "<TENANT_ID>"
objects: |
array:
- |
objectName: db-password
objectType: secret
- |
objectName: api-key
objectType: secret
kubectl apply -f secret-provider-class.yaml
# Verify it was created
kubectl get secretproviderclass
kubectl describe secretproviderclass azure-kv-secrets
The objects array lists the Key Vault secret names to fetch. Each objectName must match a secret that exists in the Key Vault.
Task 3: Create a Pod That Mounts Key Vault Secrets
Create the file pod-keyvault.yaml:
apiVersion: v1
kind: Pod
metadata:
name: pod-keyvault
spec:
serviceAccountName: <SERVICE_ACCOUNT_NAME>
containers:
- name: app
image: busybox
command: ["sh", "-c", "echo 'Secrets mounted:' && ls /mnt/secrets/ && echo '---' && cat /mnt/secrets/db-password && echo && 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
Note: Replace
<SERVICE_ACCOUNT_NAME>with the ServiceAccount name provided by the instructor. This ServiceAccount is linked to the Azure Managed Identity that has access to Key Vault.
kubectl apply -f pod-keyvault.yaml
# Wait for the Pod to start
kubectl get pod pod-keyvault -w
If the Pod is stuck in ContainerCreating, check events:
kubectl describe pod pod-keyvault
Task 4: Verify Secrets Are Available
Once the Pod is running, verify the secrets are mounted:
# List the mounted secret files
kubectl exec pod-keyvault -- ls /mnt/secrets/
# Read the secret values
kubectl exec pod-keyvault -- cat /mnt/secrets/db-password
kubectl exec pod-keyvault -- cat /mnt/secrets/api-key
# Check the logs
kubectl logs pod-keyvault
Expected output:
Secrets mounted:
api-key
db-password
---
<value-of-db-password-from-keyvault>
<value-of-api-key-from-keyvault>
Each Key Vault secret becomes a file in the mounted directory. The filename matches the objectName from the SecretProviderClass.
Optional: Sync as Kubernetes Secret
The CSI driver can also sync Key Vault secrets as Kubernetes Secrets, making them available as environment variables. This is configured in the SecretProviderClass with secretObjects:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv-secrets-sync
spec:
provider: azure
secretObjects:
- secretName: db-secrets-k8s
type: Opaque
data:
- objectName: db-password
key: DB_PASSWORD
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "false"
clientID: "<IDENTITY_CLIENT_ID>"
keyvaultName: "<KEY_VAULT_NAME>"
tenantId: "<TENANT_ID>"
objects: |
array:
- |
objectName: db-password
objectType: secret
Note: The synced Kubernetes Secret is only created when a Pod mounts the SecretProviderClass volume. It does not exist before a Pod references it.
Task 5: 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 `useVMManagedIdentity: "false"` (or omit it) with `clientID` set 3. **`objectType: secrets`** should be **`objectType: secret`** — the value is singular (`secret`, `key`, or `cert`), not pluralClean up:
kubectl delete pod pod-keyvault
kubectl delete secretproviderclass azure-kv-secrets --ignore-not-found
Common Problems
| Problem | Cause | Solution |
|---|---|---|
| CSI driver pods not running | AKS add-on not enabled | Inform the instructor — needs to enable with az aks enable-addons --addons azure-keyvault-secrets-provider |
Pod stuck in ContainerCreating |
CSI driver cannot authenticate to Key Vault | Check identity permissions; run kubectl describe pod for CSI errors |
SecretProviderClass not found |
CRD not installed | Verify with kubectl api-resources \| grep SecretProviderClass |
keyvault error: secret not found |
Secret name mismatch between SecretProviderClass and Key Vault | Check objectName matches the actual Key Vault secret name |
403 Forbidden from Key Vault |
Managed Identity does not have access policy | Instructor needs to grant get permission on secrets for the identity |
| Synced K8s Secret not appearing | No Pod has mounted the SecretProviderClass yet | The Kubernetes Secret is created only when a Pod mounts the volume |
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 — Just like with regular Secret volumes. - 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 secret rotation — The CSI driver supports polling for updated secrets. Configure
rotationPollIntervalin the driver settings. - Test access before deploying — Verify the identity can read secrets using
az keyvault secret showbefore creating Pods.
Summary
In this exercise you learned how to:
- Understand why Azure Key Vault is preferred over native Kubernetes Secrets for production
- Verify the Secrets Store CSI Driver is installed on the AKS cluster
- 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 permissions and secret name mismatches
This concludes the Configuration module. Proceed to Exercise 10 to learn about resource requests and limits.