Azure Key Vault RBAC Migration Leaves Legacy Access Policies Silently Active
Toggling Azure Key Vault to RBAC authorisation does not remove existing access policies. Legacy identities can retain secret access until stale policy entries are explicitly deleted and verified.
Operational summary
At a glance
- Symptom
- After changing an Azure Key Vault's permission model from vault access policies to Azure RBAC, a security reviewer expects legacy grants to stop working.
- Likely cause
- Azure Key Vault historically supports two mutually exclusive authorisation models: vault access policies and Azure RBAC, selected by the enableRbacAuthorization boolean on the vault…
- Impact
- Secret, key and certificate access intended to be gated behind RBAC role assignments remains available to identities holding only the old access policy grant, defeating the least-privilege boundary…
- Verification signal
- Re-run the vault property query and confirm the access policy array is empty and that the previously successful unauthorised read now fails with an authorisation error.az keyvault show…
- Safe correction
- Remove every stale access policy entry explicitly rather than relying on the RBAC toggle to suppress them, then assign equivalent least-privilege RBAC roles to the identities that still…
- Rollback or recovery
- If a dependent application breaks because it relied on an undocumented legacy grant, restore only that specific access policy entry rather than reverting the vault to the access-policy…
Symptom
After changing an Azure Key Vault’s permission model from vault access policies to Azure RBAC, a security reviewer expects legacy grants to stop working. Testing shows otherwise: a service principal never assigned an RBAC role can still retrieve secrets, keys or certificates. Diagnostic logs record successful secret-read operations from identities the team believed were fully deprovisioned during the migration.
False Assumption
The team assumed that setting the vault’s enableRbacAuthorization property to true immediately and completely supersedes the old access policy list, and that policies no longer editable in the Azure Portal grant no access. In practice, this property change governs which authorisation model Azure evaluates going forward; it does not itself delete or invalidate previously configured access policy entries, and some clients or cached sessions can continue honouring policy-based grants until the vault fully re-evaluates under RBAC.
Root Cause
Azure Key Vault historically supports two mutually exclusive authorisation models: vault access policies and Azure RBAC, selected by the enableRbacAuthorization boolean on the vault resource. Microsoft’s cloud security benchmark documents identity and access control as a distinct governance domain requiring explicit configuration and verification, not an artefact of flipping one property (Microsoft Learn, Microsoft cloud security benchmark overview). The practical failure arises because teams change the property without a documented step to enumerate and remove existing access policy entries, leaving stale grants in the vault’s access policy collection even when the portal no longer surfaces them for editing. Without an explicit inventory of prior permissions granted under the old model, the security team has no baseline to confirm legacy access has actually been revoked.
Impact
Secret, key and certificate access intended to be gated behind RBAC role assignments remains available to identities holding only the old access policy grant, defeating the least-privilege boundary the migration was meant to establish. Where the vault holds credentials or signing keys for production workloads, this creates an undocumented access path that will not appear in an RBAC role-assignment audit, because reviewers checking role assignments will see no matching entry and may falsely conclude access is fully controlled.
Diagnosis
Confirm the vault’s current authorisation model and inspect for residual access policy entries before drawing conclusions about access control coverage.
az keyvault show --name <vault-name> --resource-group <rg-name> --query "properties.{rbacEnabled:enableRbacAuthorization, accessPolicies:accessPolicies}" -o json
If rbacEnabled is true but the accessPolicies array is non-empty, legacy grants remain present in the resource definition. Cross-check against actual data-plane behaviour by attempting a read with a test identity that holds no RBAC role on the vault but does appear in the returned access policy list, using a non-production secret created solely for this test.
az keyvault secret show --vault-name <vault-name> --name test-diagnostic-secret --auth-mode login
A successful read from an identity with no RBAC role assignment confirms the legacy access path is still active. Cross-check the same property pair via an ARM template export, since portal display can lag behind the underlying resource state.
Correction
Remove every stale access policy entry explicitly rather than relying on the RBAC toggle to suppress them, then assign equivalent least-privilege RBAC roles to the identities that still require access. This is a state-changing, permission-narrowing action and must be performed on one identity at a time with the evidence from Diagnosis in hand.
- Confirm, from the Diagnosis step output, the exact object ID of the identity whose policy entry is being removed and that it holds no legitimate RBAC role assignment already.
- Remove the stale policy entry for that object ID only, then re-query the vault to confirm the array no longer contains it before moving to the next entry.
- Assign a scoped RBAC role, such as Key Vault Secrets User, only to identities with a confirmed operational need, and record the object ID, prior permission set and business justification for each change.
Document each removed policy entry and each new role assignment independently of the portal view, since the portal state has already been shown to lag behind the resource definition.
Validation
Re-run the vault property query and confirm the access policy array is empty and that the previously successful unauthorised read now fails with an authorisation error.
az keyvault show --name <vault-name> --resource-group <rg-name> --query "properties.accessPolicies" -o json
Expect an empty array. Repeat the diagnostic read from the previously permitted identity and expect a Forbidden response, while confirming that legitimate identities with new RBAC role assignments can still read successfully. Retain diagnostic log entries from before and after the change as the audit evidence for this record.
Rollback
If a dependent application breaks because it relied on an undocumented legacy grant, restore only that specific access policy entry rather than reverting the vault to the access-policy authorisation model wholesale, since a full model reversion re-exposes every other identity already cleaned up. Apply the minimum permission set that restores the required function, confirm the dependent application recovers, and open a tracked follow-up to migrate that identity to an RBAC role assignment on a defined timeline. Stop condition: if more than one identity requires rollback, pause further policy removals and re-run the full dependency inventory before continuing, since this indicates the pre-change audit was incomplete. Because restoring an access policy is itself a state-changing, permission-widening action, treat it as a deliberate, logged decision requiring the same object-ID-level confirmation used during Correction, not a reflexive full revert.
Prevention
Treat the RBAC authorisation switch and the access policy cleanup as two separate, sequenced change steps, each with its own validation, rather than a single action. Before switching enableRbacAuthorization, export and retain the full access policy list as a permanent record. After switching, schedule an explicit removal pass for every stale entry and verify with a negative-access test, not a portal glance, that legacy grants no longer function. Include the access policy array in routine vault configuration audits going forward, since the property can silently repopulate if automation or infrastructure-as-code templates still reference the old model.
Apply the safer control
Before you change production
Confirm the affected scope, export the current configuration, and test the replacement control in a non-production environment first.
Fix commands and configuration
enableRbacAuthorizationtrueaz keyvault show --name <vault-name> --resource-group <rg-name> --query "properties.{rbacEnabled:enableRbacAuthorization, accessPolicies:accessPolicies}" -o jsonVerify, roll back or escalate
Verify
Re-run the vault property query and confirm the access policy array is empty and that the previously successful unauthorised read now fails with an authorisation error.az keyvault show --name <vault-name> --resource-group <rg-name> --query "properties.accessPolicies" -o jsonExpect an empty array.
Rollback
If a dependent application breaks because it relied on an undocumented legacy grant, restore only that specific access policy entry rather than reverting the vault to the access-policy authorisation model wholesale, since a full model reversion re-exposes every other identity…
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.