Scope Inheritance at Scale
RBAC assignments flow Management Group → Subscription → Resource Group → Resource. An assignment at any level grants that permission to everything below — with no way to "revoke" it at a child scope (only deny assignments can block).
🔑 Inheritance Trade-offs
- Broad scope (MG level): Low maintenance, but over-grants if you're not careful. One Reader at root = Reader on every resource in the tenant.
- Narrow scope (RG/Resource): Precise least-privilege, but assignment sprawl creates management burden — hard to audit thousands of role assignments.
- Sweet spot: Assign at Subscription level for environment-wide roles; use RG for team-specific isolation; reserve resource-level only for break-glass or service principals with single-resource access.
Custom Role Design Patterns
Built-in roles are coarse. When Contributor is too broad and a built-in doesn't fit, design a custom role.
🔑 When to Create a Custom Role
- You need a subset of Contributor (e.g., can manage VMs but not delete VNets)
- You need cross-service permissions not in any single built-in role
- Compliance requires explicit action-level documentation of what a persona can do
{
"Name": "App Team Deployer",
"Description": "Deploy and manage App Services and Functions, read networking",
"Actions": [
"Microsoft.Web/*",
"Microsoft.Network/virtualNetworks/read",
"Microsoft.Network/virtualNetworks/subnets/join/action",
"Microsoft.Resources/deployments/*"
],
"NotActions": [
"Microsoft.Web/sites/config/list/action" // block reading connection strings
],
"DataActions": [],
"AssignableScopes": [
"/subscriptions/{sub-id}/resourceGroups/rg-app-prod",
"/subscriptions/{sub-id}/resourceGroups/rg-app-dev"
]
}
🔑 Scoping Custom Roles
- AssignableScopes controls where the role can be assigned — not what resources the permissions act on (that's the assignment scope).
- Set AssignableScopes to the Management Group if multiple subscriptions need it; narrow to subscription/RG if it's team-specific.
- You can update AssignableScopes later without recreating the role.
Deny Assignments
Deny assignments block specific actions even if another role assignment grants them. They override allow-based RBAC.
🔑 How Deny Assignments Work
- Evaluated after role assignments — deny always wins.
- Cannot be created directly by customers (today). They're applied by Azure Blueprints and Managed Applications to protect resources from modification.
- Use case: Blueprint locks down a networking RG so even Subscription Owner can't delete the hub VNet.
📝 Exam Tip
If the exam asks "how to prevent an Owner from modifying a resource," the answer is deny assignment via Blueprint — not removing their role (you can't remove inherited Owner). Resource locks prevent deletion/modification but Owners can remove locks; deny assignments cannot be overridden.
RBAC vs Azure Policy
Both enforce governance, but they solve different problems. Getting this distinction wrong leads to architectures that fight themselves.
| Dimension | RBAC | Azure Policy |
|---|---|---|
| Controls | Who can do what (identity → action) | What can exist / how it must be configured (resource state) |
| Enforcement point | ARM authorization layer (before action executes) | ARM resource provider (during/after deployment) |
| Default posture | Deny-all (no access until role assigned) | Allow-all (everything is allowed until policy restricts) |
| Scope | MG / Sub / RG / Resource | MG / Sub / RG (not individual resource) |
| Targets identity? | Yes — specific user, group, SP, MI | No — applies to all requests regardless of who |
| Can remediate? | No | Yes — DeployIfNotExists, Modify effects |
| Example | "Devs can create VMs in rg-dev" | "All VMs must use managed disks, everywhere" |
🔑 Decision Rule
RBAC when the question is "should this person be allowed?" → identity-centric.
Policy when the question is "should this configuration be allowed?" → resource-centric.
Use both together: RBAC gates who can deploy; Policy gates what they can deploy.
PIM & Just-in-Time Access
Privileged Identity Management (PIM) converts standing access into eligible assignments that must be activated — with approval, MFA, time limits, and audit trails.
🔑 Architect Design Decisions for PIM
- Eligible vs Active: Make high-privilege roles (Owner, User Access Admin, Key Vault Admin) eligible-only. Keep Reader/operational roles active.
- Activation duration: Default 8 hours. For break-glass, allow up to 24h. For routine ops, 1-4h reduces blast radius.
- Approval workflow: Require approval for production Owner; self-approve for dev Contributor.
- Access reviews: Schedule quarterly reviews for all eligible assignments. PIM auto-removes stale access.
ABAC for Storage
Attribute-Based Access Control adds conditions to role assignments. Instead of creating dozens of custom roles, add conditions like "only blobs tagged project=alpha."
🔑 When to Use ABAC
- Multi-tenant storage accounts where teams share a container but own different blobs
- Restricting access based on blob index tags, container name, or encryption scope
- Avoiding one-storage-account-per-team sprawl
// Role: Storage Blob Data Contributor
// Condition: only act on blobs where tag "department" = assignee's department
(
@Resource[Microsoft.Storage/storageAccounts/blobServices/containers/blobs/tags:department]
StringEquals
@Principal[Microsoft.Directory/CustomSecurityAttributes/department]
)
This means a user in department "Engineering" can only read/write blobs tagged department=Engineering — even though they have Data Contributor on the whole account.
🏢 Real-World: Designing Access for a 500-Person Org
Scenario: Contoso (500 people) has 4 personas — Developers (200), Ops/SRE (30), Contractors (50 rotating), Auditors (20) — across 3 environments (Dev, Staging, Prod) with 6 subscriptions and a hub-spoke network.
Structure
- Management Group:
mg-contoso→ child MGs:mg-nonprod,mg-prod - Subscriptions:
sub-dev,sub-stagingunder mg-nonprod;sub-prod-app,sub-prod-dataunder mg-prod;sub-connectivity,sub-managementat mg-contoso level
Role Assignments
| Persona | Scope | Role | Method |
|---|---|---|---|
| Developers | mg-nonprod | Contributor | Active (Entra group) |
| Developers | sub-prod-app | Reader | Active |
| Developers | sub-prod-app | Custom "Deployer" (deploy only, no infra delete) | Eligible via PIM, 2h, approval required |
| Ops/SRE | mg-contoso | Reader | Active |
| Ops/SRE | mg-prod | Contributor | Eligible via PIM, 4h, self-approve |
| Ops/SRE | sub-connectivity | Network Contributor | Active |
| Contractors | specific RGs in sub-dev | Custom "Scoped Dev" (no secrets, no networking) | Active, time-bound (contract end date) |
| Auditors | mg-contoso | Reader | Active |
| Auditors | mg-contoso | Security Reader | Active |
Guardrails (Policy, not RBAC)
- Policy at mg-contoso: "Allowed locations = West Europe, North Europe"
- Policy at mg-prod: "VMs must use managed disks," "Storage must have private endpoint"
- Policy at mg-contoso: "Inherit tag: CostCenter from RG" (Modify effect)
Deny Assignment
- Blueprint on sub-connectivity locks hub VNet, Firewall, and ExpressRoute circuit — even Ops Owner can't delete them without removing the Blueprint assignment (which requires elevated access + approval).
ABAC
- Shared data lake in sub-prod-data: contractors get Storage Blob Data Reader with condition
tags:project = their-assigned-project.
Key Takeaways
- Assign at the right scope — broad enough for manageability, narrow enough for least-privilege.
- Custom roles fill the gap between built-in roles; scope AssignableScopes to where they're needed.
- Deny assignments (via Blueprints) are the only way to override inherited allow permissions.
- RBAC = who can act. Policy = what's allowed to exist. Use both.
- PIM eliminates standing privileged access — eligible + time-bound + auditable.
- ABAC conditions on storage roles replace per-team storage account sprawl.