Integrate Azure Key Vault into AKS using the Secret Provider (AKS Part 2)

The Secrets Store CSI Driver for Azure Key Vault enables the integration of Azure Key Vault as a secret store within an Azure Kubernetes Service (AKS) cluster through a CSI volume. This marks the second installment of my Azure Kubernetes Service series. In this part, we will expand on the AKS cluster we established in the initial segment. If you haven't checked out the first part, I encourage you to do so here.

hero-banner

Enable Azure Key Vault Secret Provider add-on for Secrets Store CSI Driver support

We'll upgrade our AKS cluster with Azure Key Vault Provider for Secrets Store CSI Driver capability using the az aks enable-addons command with the azure-keyvault-secrets-provider add-on. The add-on creates a user-assigned managed identity we can use to authenticate to our Azure key vault.

az aks enable-addons --addons azure-keyvault-secrets-provider --name myAKSCluster --resource-group myResourceGroup

Example:

az aks enable-addons --addons azure-keyvault-secrets-provider --name aks-helloworld --resource-group rg-helloworld

Verify the Azure Key Vault Provider for Secrets Store CSI Driver installation

Verify the installation is finished using the kubectl get pods command to list all pods that have the secrets-store-csi-driver and secrets-store-provider-azure labels in the kube-system namespace, and ensure that your output looks similar to the following output:

kubectl get pods -n kube-system -l 'app in (secrets-store-csi-driver,secrets-store-provider-azure)'

Output:

NAME                                     READY   STATUS    RESTARTS   AGE
aks-secrets-store-csi-driver-qh95x       3/3     Running   0          5m52s
aks-secrets-store-provider-azure-qlbg2   1/1     Running   0          5m52s

Enable autorotation on our AKS cluster

Update an existing cluster to enable autorotation of secrets using the az aks addon update command and the enable-secret-rotation parameter.

az aks addon update -g myResourceGroup -n myAKSCluster -a azure-keyvault-secrets-provider --enable-secret-rotation

Example:

az aks addon update -g rg-helloworld -n aks-helloworld -a azure-keyvault-secrets-provider --enable-secret-rotation

Create or use an existing Azure key vault

Create an Azure key vault using the az keyvault create command. The name of the key vault must be globally unique.

az keyvault create -n <keyvault-name> -g myResourceGroup -l westus
az keyvault create -n akv-helloworld -g rg-helloworld -l westus

Example:

Our Azure key vault can store keys, secrets, and certificates. In this example, use the az keyvault secret set command to set a plain-text secret called Appsettings--PageTitle

az keyvault secret set --vault-name <keyvault-name> -n <secret-name> --value <secret-name>

Example:

az keyvault secret set --vault-name akv-helloworld -n AppSettings--PageTitle --value "Welcome to our AKS cluster"

Provide an identity to access the Azure Key Vault Provider for Secrets Store CSI Driver

To access your key vault, we can use the user-assigned managed identity that we created when we create our AKS cluster. The following commands will get our identityClientId and set policy to access secrets in our key vault with get and list permissions.

$identityClientId=$(az aks show -g <resource-group> -n <cluster-name> --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId -o tsv)
#set policy to access secrets in our key vault with get and list permissions
az keyvault set-policy -n <keyvault-name> --secret-permissions get, list --spn <identity-client-id>

Example:

$identityClientId=$(az aks show -g rg-helloworld -n aks-helloworld --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId -o tsv)
#set policy to access secrets in our key vault
az keyvault set-policy -n akv-helloworld --secret-permissions get, list --spn $identityClientId

Create Secret provider class

We’ll create a Kubernetes secret to mirror our mounted secrets content. Our secrets will sync after we start a pod to mount them. When we delete the pods that consume the secrets, our Kubernetes secret will also be deleted.

To sync mounted content with a Kubernetes secret, use the secretObjects field when creating a SecretProviderClass to define the desired state of the Kubernetes secret, as shown in the following example.

We will need to gets our identityClientId and tenantId

# get identityClientId
az aks show -g <resource-group> -n <cluster-name> --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId -o tsv
# get tenantId
az account tenant list

Example:

# get identityClientId
az aks show -g rg-helloworld -n aks-helloworld --query addonProfiles.azureKeyvaultSecretsProvider.identity.clientId -o tsv

# get tenantId
az account tenant list

Create a helloworld-secret-provider.yaml file by using the following YAML. You will need to set identityClientId and tenantId

apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
  name: akv-provider-helloworld
spec:
  provider: azure
  secretObjects:
    - secretName: kv-helloworld
      type: Opaque
      data:
        - objectName: AppSettings--PageTitle
          key: AppSettings__PageTitle
  parameters:
    usePodIdentity: "false"
    useVMManagedIdentity: "true" # Set to true for using managed identity
    userAssignedIdentityID: <identityClientId> # Set the clientID of the user-assigned managed identity to use
    keyvaultName: akv-helloworld # Set to the name of your key vault
    objects: |
      array:
        - |
          objectName: AppSettings--PageTitle
          objectType: secret
          objectVersion: ""
    tenantId: <tenantId> # The tenant ID (Directory ID) of the key vault

Apply the SecretProviderClass to our cluster:

kubectl apply -f helloworld-secret-provider.yaml

Verify SecreteProviderClass successfully added to our cluster:

kubectl get secretproviderclass

NAME                      AGE
akv-provider-helloworld   17s

Update our hello-world deployment to use our secret as an environment variable

We going to update our helloworld-secret-provider.yaml file to set our Appsettings--PageTitle as an environment variable.

kind: Service
apiVersion: v1
metadata:
  name: hello-world-svc
spec:
  selector:
    app: hello-world
  type: ClusterIP
  ports:
    - port: 80

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
spec:
  replicas: 3
  selector:
    matchLabels:
      app: hello-world
  template:
    metadata:
      labels:
        app: hello-world
    spec:
      containers:
        - name: hello-world
          image: registry.tpham.org/hello-world
          readinessProbe:
            httpGet:
              path: /health
              port: 80
          resources:
            limits:
              cpu: "300m"
          ports:
            - containerPort: 80
          env:
            - name: "AppSettings__PageTitle"
              valueFrom:
                secretKeyRef:
                  name: kv-helloworld
                  key: AppSettings__PageTitle
          volumeMounts:
            - name: akv-secret-store
              readOnly: true
              mountPath: /app/akv-secret-store
      volumes:
        - name: akv-secret-store
          csi:
            driver: secrets-store.csi.k8s.io
            readOnly: true
            volumeAttributes:
              secretProviderClass: "akv-provider-helloworld"
      imagePullSecrets:
        - name: tpham-registry

---
apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: hpa-hello-world
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: hello-world
  minReplicas: 1
  maxReplicas: 10
  targetCPUUtilizationPercentage: 50

Apply the new changes:

kubectl apply -f hello-world-deployment.yaml

After all of our pods updated, we should be able to refresh our application page and see the title update to Welcome to our AKS cluster

Deploy Reloader tool (optional)

When our Appsettings--PageTitle gets update from AKV, we need to restart the pods (rolling updates) to get the latest secret as an environment variable. We’ll deploy Reloader **tool to **watch for changes on the synced Kubernetes Secret and perform rolling upgrades on pods.

Deploy Reloader to our AKS:

kubectl apply -k https://github.com/stakater/Reloader/deployments/kubernetes

Update helloworld-secret-provider.yaml with the following annotations:


---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-world
  annotations:
    secret.reloader.stakater.com/reload: "kv-helloworld"
spec:

Apply the new changes:

kubectl apply -f hello-world-deployment.yaml

To specify a custom rotation interval, use the following command:

az aks addon update -g myResourceGroup -n myAKSCluster2 -a azure-keyvault-secrets-provider --enable-secret-rotation --rotation-poll-interval 2m

Example:

az aks addon update -g rg-helloworld -n aks-helloworld -a azure-keyvault-secrets-provider --enable-secret-rotation --rotation-poll-interval 30s