🛸 Hitchhiker's Guide — Phase 04: RBAC & Azure Policy
Read this if: you can click "Add role assignment" in the portal but "why does this service principal get a 403 it shouldn't" still makes you open a ticket. This is the compressed tour the WARMUP derives slowly. Skim it, then read the WARMUP, then build the evaluator.
0. The 30-second mental model
Every Azure write passes two gates in series, both deny-biased:
who? → RBAC (may THIS identity do THIS action at THIS scope?) → AuthorizationFailed
what? → Policy (is the resulting RESOURCE allowed to exist?) → RequestDisallowedByPolicy
RBAC: a role assignment is (principal, role, scope); effective perms =
union(Actions) − union(NotActions); deny assignments win; default deny. Policy: an
if condition tree over the resource's fields + a then.effect; deny dominates. One
sentence to tattoo: authorization is two deny-biased gates over a scope hierarchy, and
the control plane and data plane are different permission sets.
1. The four roles you must know cold
| Role | Actions | The gotcha |
|---|---|---|
Owner | * | no DataActions → can't read a blob; can grant access |
Contributor | * minus Microsoft.Authorization/* | manage everything except grant access |
Reader | */read | sees all, changes nothing |
User Access Administrator | */read + Microsoft.Authorization/* | manages access, little else |
Then the data roles that fix the blob problem: Storage Blob Data Reader /
…Data Contributor / …Data Owner, and for secrets Key Vault Secrets User.
2. The numbers and rules to tattoo on your arm
| Thing | Value |
|---|---|
| RBAC evaluation order | deny first → allow → default deny |
| Effective perms | union(Actions) − union(NotActions) (per plane) |
NotActions | subtracts from this role only (not a deny) |
Wildcard * | matches any chars including / (spans segments) |
| Scope hierarchy | MG → Sub → RG → resource; inherited down |
| Role assignments per subscription | 4,000 (hard limit — a real ceiling at scale) |
| Custom roles per tenant | 5,000 |
| Management-group tree depth | 6 levels (excl. root + subscription) |
| Policy effect precedence | disabled < audit* < append/modify < deployIfNotExists < deny |
| Request-time effects | deny, append, modify |
| Scan-time effects | audit, auditIfNotExists, deployIfNotExists |
3. The az one-liners
# WHO has access here (incl. inherited up the tree)?
az role assignment list --scope <resourceId> --include-inherited -o table
# What can a role actually do?
az role definition list --name "Contributor" --query "[0].permissions" -o json
az role definition list --name "Storage Blob Data Reader" \
--query "[0].permissions[0].dataActions" # ← the DataActions
# Grant least privilege: narrow role at the tightest scope (RG, not subscription)
az role assignment create --assignee <objId> --role "Storage Blob Data Reader" \
--scope <storageAccountId>
# Discover the action strings a provider exposes (the patterns your role matches)
az provider operation show --namespace Microsoft.Storage -o json
# Policy: assign a built-in "deny non-HTTPS storage" at an RG
az policy assignment create --name deny-insecure-storage \
--policy <builtinDefinitionId> --scope <rgId>
# Policy: what's non-compliant right now?
az policy state summarize --resource-group <rg> -o table
# Custom role (last resort): author exact Actions + tight assignableScopes
az role definition create --role-definition ./my-custom-role.json
4. War story shapes you'll relive
- "I'm
Ownerbut I can't read the blob!" → control plane vs data plane.Ownerhas noDataActions. AddStorage Blob Data Reader. The #1 RBAC ticket. - "My service principal is
Contributorbut the deploy saysRequestDisallowedByPolicy." → RBAC passed; Policy denied the resource shape (region/tag/SKU/HTTPS). Read the policy'sifto find the field. Different gate, different error code. - "I removed their assignment and they still have access." → inherited assignment at the
RG / subscription / MG. Effective access is the union up the tree;
--include-inherited. - "An
Ownerdeleted a production resource the stack was protecting!" → they shouldn't have, if there'd been a deny assignment (denySettings). A resource lock isn't enough —Ownercan remove a lock; a deny assignment overrides evenOwner. - "Contributor can't grant my teammate access." → working as designed:
ContributorhasNotActions: Microsoft.Authorization/*/Write. GrantUser Access Administrator(orOwner). - "My
auditpolicy isn't blocking anything." →auditnever blocks; it reports on the scan. If you need to stop the create, the effect isdeny.
5. The two-second debugging fork
403 error code?
├─ AuthorizationFailed → RBAC. Check: assignment exists? right scope?
│ NotActions subtracting it? deny assignment?
│ data-plane action needing a DataAction?
└─ RequestDisallowedByPolicy → Policy. Check: which assignment? which definition's
`if` matched? which field is non-compliant?
6. Vocabulary that signals you've held the pager
- Role assignment — the
(principal, role, scope)triple; the atomic RBAC fact. NotActions— subtraction from a role's own grant, not a deny.- DataActions — the data-plane permission set; separate from
Actions. - Deny assignment — the unconditional override; deny always wins; stack/blueprint-only.
- Scope inheritance — access flows down MG → Sub → RG → resource.
- Effective permissions — the union up the tree, minus
NotActions, minus deny. - Effect — what a policy does on a match;
denydominates. - Alias — the name a policy uses for a resource property (vs a raw JSON path).
- Initiative / policy set — a bundle of policy definitions assigned as one unit.
deployIfNotExists— auto-remediation effect; needs a managed identity.
7. Beginner mistakes that mark you in interviews
- Granting
Owner/Contributorat the subscription when an RG-scoped role would do (blast radius), or inventing a custom role when a built-in already fits. - Thinking
Ownerimplies data access (the blob trap) — or thinkingNotActionsis a deny. - Forgetting deny is evaluated first and always wins — describing RBAC as "just add up the allows."
- Doing scope containment with a raw
startswithand getting therg-vs-rg2bug. - Confusing the two gates: blaming RBAC for a
RequestDisallowedByPolicy(it's Policy). - Using
auditwhere the requirement is "must never be created" (that'sdeny), or reaching for a resource lock where a deny assignment is required. - Not knowing which effects run at request time vs the compliance scan.
8. How this phase pays off later
- Phase 05 (Landing Zones) is this, at scale: RBAC + Policy inherited down a management-group tree, an initiative per compliance domain, the ALZ guardrails.
- Phase 12 (Key Vault / Managed Identity) lives on the control-vs-data-plane split you
learned here (
Key Vault Secrets Useris aDataActionrole). - Phase 08 (CI/CD) is least-privilege deployment identities — narrow role, tight scope, OIDC federation instead of a secret.
- Every later phase that touches identity defaults to deny, and you now know exactly why and how that default is enforced.
Now read the WARMUP slowly, then build the evaluator. After that, every "why did this 403?" becomes a function you run in your head.