Skip to main content
Back to the Azure Footguns Database
GovernanceSecurityLowCommonAZF-0063

Key Vault legacy access policies: a parallel permission system your RBAC reviews never see

Vaults created without enableRbacAuthorization manage permissions through a per-vault access-policy list that RBAC tooling, access reviews, and PIM never touch.

Footgun ID
AZF-0063 (Azure Footgun No. 63)
Azure service
Azure Key Vault
Resource type
Microsoft.KeyVault/vaults
Updated
July 9, 2026

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.

Impact (governance)
No direct cost — an unaudited parallel permission model on the resource that holds your secrets
Basis: Azure behavior (authored assessment). Figures are estimates, not measurements.

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

Footgun: the default model — a per-vault policy list nobody audits
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'] }
      }
    ]
  }
}
Fix: assign RBAC data-plane roles first, then flip the vault to RBAC
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.

Start Your 28-Day Free Trial

Every feature unlocked. Deploys to your Azure tenant. No data leaves your tenant.

Available now on the Azure Marketplace.

Not ready to install anything? Browse the Azure Footguns Database: 55+ documented ways Azure quietly costs money or creates risk.

Request a demo

StratoLens catches the cost waste, access risk, and config drift across your whole Azure estate, from inside your own tenant, so your data never leaves it.