The access review is going fine until the auditor sorts the export by role and stops at row 40: a contractor who rolled off the project in February still holds Contributor on the production subscription. Nobody in the room granted it. Nobody in the room knew it was there.
That is not negligence. It is what Azure RBAC does when nothing pushes back. Onboarding adds assignments, incidents add assignments, migrations add assignments, and almost nothing ever removes one. Each grant made sense on the day it was created. The reasons expire; the access does not.
The Problem: Assignments Outlive Their Reasons
Azure's native RBAC tooling answers one question well: "who can touch this specific resource?" Open any resource's IAM blade and the list is right there. The question audits actually ask is the inverse: "what can this specific person touch?" And that question runs into structural friction:
- The view is resource-centric. To assemble one principal's total footprint you visit every subscription and scope they might have been granted at, one IAM blade at a time.
- Inheritance does the quiet work. A single assignment at management group or subscription scope covers every resource underneath it. It is one row in one place and effective everywhere.
- The "why" is recorded nowhere. An assignment carries no justification, and the Activity Log entry showing who created it expires after 90 days.
- Ghosts accumulate. Delete a user or service principal and its assignments linger, showing up as "Identity not found" rows that nobody is confident enough to remove.
Inventorying Assignments by Hand
The CLI gives you a per-subscription export, including inherited assignments:
az role assignment list --all --include-inherited --output table
Note that this operates in your current subscription context, so a full audit means looping it across every subscription.
Azure Resource Graph does better on coverage. The authorizationresources table holds role assignments across every subscription you can read:
authorizationresources
| where type =~ 'microsoft.authorization/roleassignments'
| extend principalId = tostring(properties.principalId),
principalType = tostring(properties.principalType),
roleDefinitionId = tostring(properties.roleDefinitionId),
scope = tostring(properties.scope)
| project principalId, principalType, roleDefinitionId, scope
A quick shape-of-the-problem summary is one line more:
authorizationresources
| where type =~ 'microsoft.authorization/roleassignments'
| extend principalType = tostring(properties.principalType)
| summarize assignments = count() by principalType
These queries are the right starting point for any RBAC audit, and running them will probably surprise you. But look at what comes back:
- GUIDs, not names.
principalIdandroleDefinitionIdare both identifiers. Resolving them means a second lookup against theroledefinitionstype for role names and calls to Microsoft Entra ID for display names, which Resource Graph cannot make for you. - No footprint per principal. The results are still assignment rows. Grouping them into "here is everything this service principal can reach, including what inheritance grants it" is spreadsheet work.
- No risk lens. Owner on the production subscription and Reader on a dev resource group are visually identical rows. Nothing marks which assignments deserve scrutiny.
- It is an export, not a review. The results go stale the day after you run them. An actual access review needs this repeated across every subscription, on a schedule, with someone reading the output.
Flipping to a Principal-Centric View
StratoLens approaches RBAC from the direction the audit questions come from. Instead of resource-by-resource lists, it gives you a principal-centric view of your entire environment:
- Start from the person, group, or service principal and see every resource they can access, with inherited access included, so one contractor's total footprint is one screen instead of a scope-walking exercise.
- Privilege categorization sorts assignments by risk level (Critical, Management, Read), so the Owner grants stand out from the noise and over-privileged principals surface on their own.
- Instant filtering by principal type, privilege level, or scope type turns "audit every service principal with elevated permissions" from an export project into a filter click.
- Names, not GUIDs. Users, groups, and service principals appear as themselves, so nobody is cross-referencing identifiers by hand.
Because StratoLens collects this data from inside your own Azure tenant, the RBAC picture of your environment stays in your subscription, where it belongs.
Run the Review Before the Auditor Does
RBAC sprawl compounds quietly, so the cheapest time to catch it is on a routine cadence rather than the week before an audit. The Resource Graph queries above are a solid quarterly starting point. If you want the principal-centric view, risk categorization, and filtering ready whenever the question comes up, that is what Role Assignments in StratoLens is built for.