Switching an Azure Key Vault to RBAC Leaves Legacy Access Policies Dormant, Not Deleted
Enabling Azure RBAC on a Key Vault does not clear its legacy access policy list. If the setting is later reverted, dormant permissions reactivate silently, bypassing every RBAC-based access removal.
Operational summary
At a glance
- Symptom
- A platform team removes a departing contractor's service principal from every Azure RBAC role assignment on an Azure Key Vault, confirms the removal in…
- Likely cause
- Azure Key Vault enforces exactly one of two mutually exclusive authorisation models at any given time, selected by the vault property enableRbacAuthorization: the legacy…
- Impact
- Reverting a vault to the access policy model silently restores every permission ever granted through that legacy list, bypassing any access that was removed through Azure RBAC and…
- Verification signal
- After removing the legacy policies, verify both that the list is empty and that intended access continues to work only through Azure RBAC.Re-run the diagnostic query and confirm…
- Safe correction
- Remove every entry from the vault's legacy access policy list once Azure RBAC is the intended model, rather than leaving it populated but unenforced.az keyvault delete-policy --name --object-id…
- Rollback or recovery
- If removing a legacy access policy blocks a workload that had not yet been migrated to an equivalent RBAC role, restore that specific policy entry rather than re-enabling…
Symptom
A platform team removes a departing contractor’s service principal from every Azure RBAC role assignment on an Azure Key Vault, confirms the removal in the Azure portal, and closes the access review — yet weeks later the same principal can still read secrets from that vault.
The vault had been migrated from the legacy vault access policy model to Azure RBAC authorisation earlier in the year. A later, unrelated infrastructure-as-code deployment redeployed the vault’s Bicep template for an unrelated networking change. That template did not declare the enableRbacAuthorization property, and the deployment reset it to its prior value of false. The vault silently reverted to enforcing its legacy access policy list — a list that had never been cleared and still contained the contractor’s original permissions.
False Assumption
The team assumed that enabling Azure RBAC on a Key Vault replaces or clears any existing vault access policies, and that revoking access purely through RBAC role assignments is sufficient to guarantee a principal has no remaining path to the vault’s data.
Neither assumption holds. Key Vault access policies and Azure RBAC role assignments are stored and evaluated independently. Switching the authorisation model changes which list is enforced; it does not touch the other list’s contents.
Root Cause
Azure Key Vault enforces exactly one of two mutually exclusive authorisation models at any given time, selected by the vault property enableRbacAuthorization: the legacy vault access policy list, or Azure RBAC role assignments scoped to the vault or a parent scope.
Toggling that property changes enforcement, not storage. The inactive model’s permission list is retained on the vault object indefinitely unless an operator explicitly clears it. If enableRbacAuthorization is later set back to false — through a redeployed template that omits the property, a support action, or a vault restore — the dormant access policy list becomes authoritative again immediately, with no warning and no distinction between entries added before or after the migration to RBAC.
Impact
Reverting a vault to the access policy model silently restores every permission ever granted through that legacy list, bypassing any access that was removed through Azure RBAC and undoing the audit trail the team believed was authoritative.
Because access policies grant permissions per operation — get, list, decrypt, sign, unwrapKey and similar — rather than through auditable RBAC role assignments, a security review that inspects only IAM role assignments will report a clean vault while a stale access policy entry retains standing access to secrets, keys or certificates. The exposure is invisible to RBAC-scoped access reviews and depends entirely on what permissions were configured in the original policy, which can include decrypt or sign rights on encryption keys and get/list rights on connection-string secrets.
Diagnosis
Confirm the vault’s current authorisation model and inspect whether a legacy access policy list is still present, even while Azure RBAC is the model currently in effect.
az keyvault show --name <vault-name> --resource-group <resource-group> --query 'properties.{rbacEnabled:enableRbacAuthorization,accessPolicies:accessPolicies}' -o json
A non-empty accessPolicies array while rbacEnabled reads true confirms dormant legacy grants are present. Cross-check current intended access with the RBAC role assignments actually scoped to the vault:
az role assignment list --scope <vault-resource-id> -o table
Compare the two outputs against the access-review record. Any principal present in accessPolicies but absent from the intended RBAC role list, or any principal the review believed had been removed, is a dormant exposure regardless of which model is currently enforced.
Correction
Remove every entry from the vault’s legacy access policy list once Azure RBAC is the intended model, rather than leaving it populated but unenforced.
az keyvault delete-policy --name <vault-name> --object-id <object-id>
Repeat for each stale principal identified during diagnosis, then re-run the diagnostic query to confirm accessPolicies is empty. Do this deliberately, one principal at a time, rather than as a bulk operation, so that any workload still depending on the legacy grant surfaces immediately and can be given an equivalent RBAC role assignment before the policy is removed.
Validation
After removing the legacy policies, verify both that the list is empty and that intended access continues to work only through Azure RBAC.
- Re-run the diagnostic query and confirm
accessPoliciesreturns an empty array whileenableRbacAuthorizationistrue. - Attempt a data-plane read, such as listing a secret, as a principal holding only an Azure RBAC role on the vault, and confirm the read succeeds.
- Attempt the same read as the previously removed access-policy principal, without any RBAC role, and confirm the request is refused.
Rollback
If removing a legacy access policy blocks a workload that had not yet been migrated to an equivalent RBAC role, restore that specific policy entry rather than re-enabling the access policy model for the whole vault.
az keyvault set-policy --name <vault-name> --object-id <object-id> --secret-permissions get list
Restore only the permissions the principal held before removal, immediately open a tracked task to grant the equivalent Azure RBAC role, and remove the reinstated policy again once that role is confirmed working. Stop and escalate to the vault owner if restoring the policy does not resolve the workload failure within one validation cycle, or if the required permissions are unclear — do not grant broader permissions than the principal held originally in an attempt to fix the symptom. Never set enableRbacAuthorization back to false as a rollback step: doing so reactivates every remaining entry in the legacy list, not only the one causing the incident.
Prevention
Treat the Key Vault authorisation model as a governed, monitored setting rather than a one-time migration step.
Declare enableRbacAuthorization explicitly as true in every infrastructure-as-code template that manages the vault, so that redeployments cannot silently reset it by omission. Add a periodic check — as part of access reviews, not only RBAC audits — that queries every RBAC-managed vault’s accessPolicies array and flags any non-empty result. This aligns with the identity, data protection and governance controls described in Microsoft’s cloud security benchmark guidance, which expects access grants to remain visible and reviewable rather than dormant. Until that check is in place, do not assume an access review is complete after inspecting RBAC role assignments alone.
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
enableRbacAuthorizationfalsegetVerify, roll back or escalate
Verify
After removing the legacy policies, verify both that the list is empty and that intended access continues to work only through Azure RBAC.Re-run the diagnostic query and confirm accessPolicies returns an empty array while enableRbacAuthorization is true.Attempt a data-plane read…
Rollback
If removing a legacy access policy blocks a workload that had not yet been migrated to an equivalent RBAC role, restore that specific policy entry rather than re-enabling the access policy model for the whole vault.az keyvault set-policy --name <vault-name>…
Escalate
Escalate when the blast radius is uncertain, the control cannot be tested safely, or remediation requires an outage or security exception.