What it is
An Azure Key Vault still using the legacy access-policy authorization model instead of Azure RBAC. Every vault carries its own private list of who can read, write, and delete keys, secrets, and certificates — a list that lives outside role assignments, outside access reviews, outside PIM, and outside every tool your organization uses to answer "who has access to what." Microsoft's own guidance is to migrate to RBAC; the default still isn't.
Why it happens
Unless a vault is created with enableRbacAuthorization: true, it uses access policies. That was the only model for years, so older vaults, copied ARM templates, and most tutorial code all produce access-policy vaults, and the setting is easy to miss in the portal's create flow.
The result is two permission systems on one resource. Azure RBAC governs the management plane (who can change the vault), while the access-policy list governs the data plane (who can read the secrets). Your RBAC reports, Entra access reviews, and PIM eligibility flows only see the first one. A contractor removed from every role assignment can still hold a get/list secrets policy on a vault nobody thought to check, because access policies don't inherit, don't expire, and don't show up where anyone is looking.
Access policies also can't be scoped below the whole vault, and each vault caps out at 1,024 policy entries — so teams end up granting broader data-plane access than they intend, one vault at a time.
What it costs / blast radius
There is no meter running here — the direct cost is zero. The harm is governance: your secrets store, the single most sensitive resource type in most subscriptions, is governed by a permission list your audit tooling doesn't read. Access reviews come back clean while stale principals keep data-plane access to production secrets. When a security review or compliance audit finally asks "who can read this vault," the honest answer requires opening every vault individually. (Authored assessment of Azure behavior, not a measured statistic.)
See it
resource kv 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'kv-prod'
location: location
properties: {
sku: { family: 'A', name: 'standard' }
tenantId: tenant().tenantId
// enableRbacAuthorization omitted — legacy access policies apply
accessPolicies: [
{
tenantId: tenant().tenantId
objectId: appObjectId // invisible to RBAC reviews and PIM
permissions: { secrets: ['get', 'list', 'set', 'delete'] }
}
]
}
}resource kv 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'kv-prod'
location: location
properties: {
sku: { family: 'A', name: 'standard' }
tenantId: tenant().tenantId
enableRbacAuthorization: true // access policies now ignored
accessPolicies: []
}
}
// Grant data-plane access via a role assignment BEFORE flipping the
// switch, or every caller loses access the moment it takes effect.
resource secretsUser 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(kv.id, appObjectId, 'kv-secrets-user')
scope: kv
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets User
principalId: appObjectId
principalType: 'ServicePrincipal'
}
}How StratoLens helps
StratoLens flags every vault still on the legacy access-policy model, automatically and continuously, across every subscription in your tenant — so migrating to RBAC becomes a worklist instead of a vault-by-vault archaeology project, and no new legacy vault slips in unnoticed.