🔑 Azure KeyVault setup for AKS
Azure Key Vault is a cloud service provided by Microsoft Azure to securely store and manage sensitive information such as:
-
Secrets (passwords, connection strings, API keys)
-
Keys (encryption keys)
-
Certificates
Instead of storing sensitive values directly in application configuration files or Kubernetes manifests, Azure Key Vault allows applications to retrieve secrets securely at runtime using Azure-managed identities and role-based access control (RBAC).
🟦 Creating Azure Key Vault for AKS Integration
Azure Key Vault supports two authorization models.
Choose one based on your requirement:
-
Option A: Azure RBAC (Recommended)
-
Option B: Vault Access Policy (Legacy / Alternative)
Step 1: Create Azure Key Vault (Common for both)
- Sign in to https://portal.azure.com
- Search for Key Vault → Click Create

-
Fill in the Basics tab:
• Subscription
• Resource Group
• Key Vault name (example: aks-ClusterKeyVault)
• Region (same as AKS)
• Pricing tier: Standard
-
Click Next
Step 2: Choose Access Configuration (DIFFERENT STEP)
🔹 Option A: Azure RBAC (Recommended)
On the Access configuration tab:
Select Azure role-based access control (Azure RBAC)
🔹 Option B: Vault Access Policy (Legacy)
On the Access configuration tab:
Select Vault access policy

Step 3: Networking (Common for both)
• Select Public endpoint (or configure Private Endpoint if required)
• Allow access from trusted Azure services
• Click Next
Step 4: Review and Create (Common)
• Review configuration
• Click Create
Step 5: Add Secrets to Key Vault (Common)
• Open the created Key Vault
• Go to Secrets
• Click + Generate/Import
• Add required secrets

Step 6: Install Key Vault CSI Driver Add-on in AKS
Enable the Azure Key Vault Secrets Provider add-on on the AKS cluster:
az aks enable-addons ^
--addons azure-keyvault-secrets-provider ^
--name <AKS-NAME> ^
--resource-group <Resourcegroup>
This command may take a few minutes to complete. Verify installation:
kubectl get pods -n kube-system
You should see pods similar to:
secrets-store-csi-driver-xxxx
secrets-store-provider-azure-xxxx

Step 7:Identify Managed Identities used by AKS
List managed identities created for the AKS cluster:
az identity list --query "[].{name:name,rg:resourceGroup}" -o table
You should find an identity with the name:
azurekeyvaultsecretsprovider-aks-cluster-name
Example: azurekeyvaultsecretsprovider-cluster-leasetool

Step 8: Get Managed Identity Object ID (Principal ID)
This Object ID is required for RBAC role assignment.
az identity show ^
--name <secret-provider-IdentityName> ^
--resource-group <secret-provider-ResourceGroup> ^
--query principalId -o tsv

Step 9: Assigning Role
🔹 Option A (RBAC Policy): Assign RBAC role to the Managed Identity If the Key Vault is configured in Azure RBAC mode, Access Policies are ignored.
Assign the minimum required role:
Role: Key Vault Secrets User (read-only, recommended for applications)
az role assignment create ^
--assignee <MANAGED_IDENTITY_OBJECT_ID> ^
--role "Key Vault Secrets User" ^
--scope /subscriptions/<SUB_ID>/resourceGroups/<KV_RG>/providers/Microsoft.KeyVault/vaults/<KEYVAULT_NAME>
For the
az account show --query id -o tsv


Important: Azure RBAC propagation takes 10–15 minutes. Pods must be restarted after the role becomes effective.
🔹 Option B (Vault Policy): Grant Key Vault Access Using Vault Access Policy
Run the following command to grant read-only access to Key Vault secrets:
az keyvault set-policy ^
--name <KEYVAULT_NAME> ^
--object-id <MANAGED_IDENTITY_OBJECT_ID> ^
--secret-permissions get list
To verify that the policy was added:
az keyvault show ^
--name <KEYVAULT_NAME> ^
--query properties.accessPolicies
Step 10: Get Client ID and Tenant ID
Client ID (used by CSI driver)
az identity show ^
--name <secretprovider-IdentityName> ^
--resource-group <secretprovider-ResourceGroup> ^
--query clientId -o tsv

Tenant ID
az account show --query tenantId -o tsv

Step 11: Create SecretProviderClass
Create secretproviderclass.yaml:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: leasetool-kv
namespace: pvtest
spec:
provider: azure
secretObjects:
- secretName: leasetool-kv-secrets
type: Opaque
data:
- objectName: DBCONNECTSTR
key: DB_CONNECTSTR
- objectName: SYSADMINPWD
key: SYSADMIN_PWD
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: <Client ID>
keyvaultName: aks-ClusterKeyVault
tenantId: <Tenant ID>
objects: |
array:
- |
objectName: DBCONNECTSTR
objectType: secret
- |
objectName: SYSADMINPWD
objectType: secret
Step 12: Update Deployment to consume secrets Environment variables from Kubernetes Secret
Under env need to add like this so that the values can be fetched from the secrets:
- name: DB_CONNECTSTR
valueFrom:
secretKeyRef:
name: leasetool-kv-secrets
key: DB_CONNECTSTR
- name: SYSADMIN_PWD
valueFrom:
secretKeyRef:
name: leasetool-kv-secrets
key: SYSADMIN_PWD
volumeMounts:
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
volumes:
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: leasetool-kv
So the complete deployment.yaml file will look like:
apiVersion: apps/v1
kind: Deployment
metadata:
name: leasetool #This is the name of the Deployment, can be replaced with the required ApplicationName (eg: SCMWebApp..)
namespace: pvtest
spec:
replicas: 1 #This speocifies that one pod should be running at all times
selector:
matchLabels:
app: leasetool #This is the name of the ApplicationName, can be replaced with the required ApplicationName (eg: SCMWebApp..)
template:
metadata:
labels:
app: leasetool #This is the name of the ApplicationName, can be replaced with the required ApplicationName (eg: SCMWebApp..)
spec:
imagePullSecrets:
- name: acr-secret # ImagePullSecret: Secret Name: acr-secret, which is used to pull the images from ACR
# 🔐 POD-LEVEL SECURITY
securityContext:
runAsNonRoot: true
runAsUser: 1000
containers:
- name: leasetool #The container is named leasetool, can be replaced with the required ApplicationName (eg: SCMWebApp..)
image: planvisage.azurecr.io/leasetool:latest
imagePullPolicy: Always
ports:
- containerPort: 8080 # Exposes port 8080 inside container.
# 🔐 CONTAINER-LEVEL SECURITY
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 1000
capabilities:
drop:
- ALL
# Setting Environment Variables
env:
- name: TIME_ZONE
value: "Asia/Kolkata"
- name: DB_TYPE
value: ""
- name: BLOB_CONTAINER_NAME
value: ""
- name: LOG_TARGET
value: "FILE"
- name: SECURE_COOKIE
value: "False" # False since we have not set SSL for the testing, this can be set to True when SSL is setup.
# 🔐 FROM AZURE KEY VAULT (Synced Kubernetes Secret)
- name: DB_CONNECTSTR
valueFrom:
secretKeyRef:
name: leasetool-kv-secrets
key: DB_CONNECTSTR
- name: SYSADMIN_PWD
valueFrom:
secretKeyRef:
name: leasetool-kv-secrets
key: SYSADMIN_PWD
volumeMounts:
# Writable temp directory (required for read-only root FS)
- name: tmp
mountPath: /tmp
# Writable directories
- name: applogs
mountPath: /app/wwwroot/log
- name: appconfig
mountPath: /app/wwwroot/xml
- name: appdata
mountPath: /app/wwwroot/data
- name: appdesign
mountPath: /app/wwwroot/design
# 🔐 Mount Secrets Store CSI (Required to trigger secret sync)
- name: secrets-store
mountPath: "/mnt/secrets"
readOnly: true
volumes:
# 🔐 Azure Key Vault CSI Volume
- name: secrets-store
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: leasetool-kv
# Writable volumes
- name: applogs
emptyDir: {}
- name: appconfig
emptyDir: {}
- name: appdata
emptyDir: {}
- name: appdesign
emptyDir: {}
- name: tmp
emptyDir: {}
The environment variables used in the above deployment.yaml (such as database connection strings, blob storage configuration, logging, and security settings) are explained in detail in the document below.
Refer to: Application Environmental Variables
Step 13: Apply configurations and restart deployment
kubectl apply -f secretproviderclass.yaml
kubectl apply -f deployment.yaml
Follow the below steps only if application was already deployed, else go back to Deployment page and proceed with deployment.
kubectl rollout restart deployment <appname> -n <namespace>
kubectl get pods -n <namespace>
Verification
After confirming if the pod is running, we can confirm if the vault secret was created by running below command:
kubectl get secret <secretname-given-inSecretProviderClass> -n <namespace>

kubectl exec -n pvtest -it <podname> -- ls /mnt/secrets

Confirming the Secret Value coming up correctly:
kubectl exec -n pvtest -it <podname> -- cat /mnt/secrets/SYSADMINPWD
