What goes wrong
Someone flips the vault's Permission model from Vault access policy to Azure role-based access control. Within minutes:
- App Service shows Key Vault references as unresolved.
- Pipelines fail with
Forbidden ... Caller is not authorized to perform action on resource ... Action: 'Microsoft.KeyVault/vaults/secrets/getSecret/action'. - Disk encryption sets, Application Gateway or SQL TDE with customer-managed keys can't reach their key.
Access policies are ignored once RBAC is on. Anyone who only had an access policy has lost access.
Step 1: inventory the access policies
az keyvault show -n kv-prod-01 --query "properties.accessPolicies[].{objectId:objectId, secrets:permissions.secrets, keys:permissions.keys, certs:permissions.certificates}" -o json > kv-prod-01-policies.json
Resolve each object ID to a name so you know what it is:
for id in $(jq -r '.[].objectId' kv-prod-01-policies.json); do
az ad sp show --id "$id" --query "{name:displayName,type:servicePrincipalType}" -o tsv 2>/dev/null \
|| az ad user show --id "$id" --query userPrincipalName -o tsv 2>/dev/null \
|| az ad group show --group "$id" --query displayName -o tsv 2>/dev/null \
|| echo "$id: not found (stale policy)"
done
Stale object IDs (deleted identities) can be dropped.
Step 2: map permissions to roles
| Access policy permissions | Built-in role |
|---|---|
| Secrets: Get, List | Key Vault Secrets User |
| Secrets: all | Key Vault Secrets Officer |
| Keys: Get, WrapKey, UnwrapKey | Key Vault Crypto Service Encryption User |
| Keys: Sign, Verify, Encrypt, Decrypt | Key Vault Crypto User |
| Keys: all | Key Vault Crypto Officer |
| Certificates: all | Key Vault Certificates Officer |
| Get on secrets for a certificate's private key | Key Vault Secrets User (certificates are backed by secrets) |
| Everything (admins) | Key Vault Administrator |
| List only (portal browsing) | Key Vault Reader (metadata only, no values) |
Grant at the vault scope, not the resource group, unless you really mean every vault in it. For high-value secrets you can grant on a single secret (.../vaults/kv-prod-01/secrets/db-password).
Step 3: create the role assignments before switching
Role assignments can be created while the vault is still on access policies. They have no effect yet, so this is safe.
KV_ID=$(az keyvault show -n kv-prod-01 --query id -o tsv)
az role assignment create --assignee-object-id <app-mi-object-id> \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Secrets User" --scope "$KV_ID"
az role assignment create --assignee-object-id <des-identity-object-id> \
--assignee-principal-type ServicePrincipal \
--role "Key Vault Crypto Service Encryption User" --scope "$KV_ID"
Check the result:
az role assignment list --scope "$KV_ID" --query "[].{who:principalName,role:roleDefinitionName}" -o table
Tip
RBAC changes can take several minutes to take effect. Create the assignments the day before the switch, not seconds before.
Step 4: switch the permission model
az keyvault update -n kv-prod-01 --enable-rbac-authorization true
In recent API versions, changing the permission model needs a role that can write role assignments (Owner or User Access Administrator), not just Key Vault Contributor. Use PIM to get it for the change.
Step 5: test every consumer
# As the app identity (or from the app's Kudu/console)
az keyvault secret show --vault-name kv-prod-01 -n db-password --query id -o tsv
- App Service: Configuration blade, Key Vault references should show a green tick. Restart the app to force a refresh.
- Disk encryption set / CMK services: check the resource's health and the vault's diagnostic logs.
Watch for denials in the vault logs:
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT" and TimeGenerated > ago(1h)
| where httpStatusCode_d == 403
| summarize count() by identity_claim_oid_g, OperationName, bin(TimeGenerated, 5m)
The object ID tells you exactly which identity is missing a role.
Rollback
az keyvault update -n kv-prod-01 --enable-rbac-authorization false
The access policies stay stored on the vault while RBAC is on, so switching back restores them. Confirm with az keyvault show --query properties.accessPolicies before you rely on it.
Common 403 causes after the switch
| Symptom | Cause | Fix |
|---|---|---|
| App gets 403 on getSecret | Only had an access policy | Secrets User on the vault |
| Portal shows "You are unauthorized to view these contents" | Key Vault Reader or Contributor only | Contributor is control plane only; add a data-plane role |
| CMK disk or storage fails to unwrap | Crypto role missing for the service identity | Crypto Service Encryption User |
ARM template reference() to a secret fails | Deploying identity lacks deploy action or the vault lacks enabledForTemplateDeployment | Check both on the vault |
| Still 403 after granting | Propagation delay, or the vault firewall | Wait and retry; check networkAcls |
Prevent it next time
- Create new vaults with RBAC from day one (
--enable-rbac-authorization true) and enforce it with Azure Policy. - Grant roles to groups or managed identities, never to individual users.
- Related: App Service Key Vault reference not resolved.