Stage 5 · Platform
Storage & Secrets
Key Vault in Kubernetes
Secrets Store CSI Driver, workload identity, and zero-downtime cert rotation.
Integration Patterns
There are three primary patterns for integrating Key Vault with AKS: the Secrets Store CSI Driver (recommended), environment variable injection via external-secrets, and Sealed Secrets for GitOps workflows.
| Pattern | Mechanism | GitOps Friendly | Secret Rotation |
|---|---|---|---|
| CSI Driver | Mounted volume | Yes | Automatic via polling |
| External Secrets | K8s secret sync | Yes | Scheduled polling |
| Sealed Secrets | Encrypted in Git | Yes | Manual re-seal |
Secrets Store CSI Driver
The Secrets Store CSI Driver is an optional Kubernetes storage driver that mounts secrets, keys, and certificates from Key Vault as volumes. It is the Microsoft-recommended approach for Key Vault integration in AKS.
# Add the Helm repo
helm repo add csi-secrets-store https://raw.githubusercontent.com/Azure/secrets-store-csi-driver-provider-azure/master/charts
helm repo update
# Install the driver
helm install csi-secrets-store \
--namespace kube-system \
--set syncSecret.enabled=true \
csi-secrets-store/secrets-store-csi-driver
# Install the Azure provider
helm install csi-secrets-store-provider-azure \
--namespace kube-system \
--repo https://raw.githubusercontent.com/Azure/secrets-store-csi-driver-provider-azure/master/chartsThe syncSecret.enabled=true flag creates Kubernetes Secret objects from mounted secrets, making them available as environment variables in pods.
Workload Identity + Key Vault
Workload Identity connects a Kubernetes service account to a user-assigned managed identity in Azure. The CSI Driver uses this identity to authenticate to Key Vault without any client secrets.
# 1. Enable workload identity on the cluster
az aks update \
--resource-group rg-aks-prod \
--name aks-payments-prod \
--enable-oidc-issuer \
--enable-workload-identity
# 2. Create a user-assigned identity
az identity create --name mi-keyvault-payments --resource-group rg-aks
# 3. Get the OIDC issuer URL
ISSUER=$(az aks show \
--resource-group rg-aks-prod \
--name aks-payments-prod \
--query "oidcIssuerProfile.issuerUrl" --output tsv)
# 4. Create a federated credential
az identity federated-credential create \
--name fic-keyvault \
--identity mi-keyvault-payments \
--issuer "$ISSUER" \
--subject "system:serviceaccount:default:sa-keyvault"
# 5. Grant Key Vault access
az role assignment create \
--assignee-object-id "$(az identity show --name mi-keyvault-payments --query principalId --output tsv)" \
--role "Key Vault Secrets User" \
--scope "/subscriptions/{sub-id}/resourceGroups/rg-payments/providers/Microsoft.KeyVault/vaults/kv-payments-prod"The federated credential links the Kubernetes service account to the Azure identity. No client secrets are stored anywhere in the cluster.
SecretProviderClass
A SecretProviderClass defines which secrets to fetch from Key Vault and where to mount them in the pod. It specifies the provider (azure), the Key Vault name, and the objects to retrieve.
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv-payments
namespace: default
spec:
provider: azure
parameters:
usePodIdentity: "true"
keyvaultName: "kv-payments-prod"
tenantId: "00000000-0000-0000-0000-000000000000"
objects: |
array:
- |
objectName: db-password
objectType: secret
objectEncoding: base64
- |
objectName: tls-cert
objectType: cert
secretObjects:
- secretName: kv-secrets
type: Opaque
data:
- objectName: db-password
key: db-passwordThe secretObjects section creates a Kubernetes Secret named kv-secrets with the fetched values. This allows pods to use secrets as environment variables.
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
spec:
template:
spec:
serviceAccountName: sa-keyvault
containers:
- name: payments
image: craksprodeastus.azurecr.io/payments:v1
volumeMounts:
- name: kv-secrets
mountPath: /mnt/secrets-store
readOnly: true
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: kv-secrets
key: db-password
volumes:
- name: kv-secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: azure-kv-paymentsThe CSI volume mounts secrets as files. The secretObjects section also creates a K8s Secret, allowing secrets to be used as environment variables.
Certificate Rotation
The CSI Driver polls Key Vault periodically and updates mounted secrets when they change. This enables automatic certificate rotation without restarting pods.
# Add rotation annotation to SecretProviderClass
cat <<EOF | kubectl apply -f -
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-kv-certs
annotations:
rotation: "true"
rotationPollInterval: "1h"
spec:
provider: azure
parameters:
usePodIdentity: "true"
keyvaultName: "kv-payments-prod"
objects: |
array:
- |
objectName: tls-cert
objectType: cert
EOFWhen rotation is enabled, the CSI Driver checks Key Vault every rotationPollInterval and updates the mounted files and synced K8s Secrets when a newer version is found.
Use cert-manager with the Key Vault issuer to automatically request and renew certificates from a CA. The CSI Driver then mounts the renewed certificate without pod restart.
Alternative: Sealed Secrets
Sealed Secrets is a Kubernetes controller that encrypts secrets so they can be safely stored in Git. The controller decrypts them in the cluster using a private key that never leaves the cluster.
# Install Sealed Secrets controller
helm install sealed-secrets \
--namespace kube-system \
--repo https://bitnami-labs.github.io/sealed-secrets
# Encrypt a secret
kubectl create secret generic db-password \
--from-literal=password=SuperSecret123! \
--dry-run=client -o yaml | kubeseal \
--controller-namespace kube-system \
--format yaml > sealed-db-password.yamlSealed Secrets are encrypted to the cluster's public key. They can only be decrypted by the controller in that specific cluster, making them safe for GitOps workflows.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.