Episode 10 — Enforcing Standards — Azure Policy and Resource Locks

In Episode 9 you organized the estate: a management-group hierarchy above your subscriptions, resource groups with a clear lifecycle, and a tagging taxonomy. Now you make that structure defend itself — with rules that resources must obey, and locks that stop anyone from breaking what matters.

Learning objectives

  • Distinguish Azure Policy from Azure RBAC and from resource locks, and choose the right tool for a given governance goal.
  • Assign a built-in policy definition to a scope in the portal and interpret its compliance results.
  • Explain policy definitions, initiatives, effects, evaluation triggers, and remediation with managed identities.
  • Configure CanNotDelete and ReadOnly locks and predict their side effects, including the classic control-plane gotchas.
  • Design policy assignments across the scope hierarchy, using exclusions rather than child overrides.

Why it matters

RBAC (Episodes 7–8) answers who can act — but a fully authorized Contributor can still deploy a VM in the wrong region, skip your tags, or delete the production database by accident. Governance needs two more layers: Azure Policy to constrain what state resources may have, and resource locks to freeze critical assets against even authorized changes. The AZ-104 exam tests exactly these seams: what a deny policy blocks that RBAC allows, what a ReadOnly lock unexpectedly breaks, and why a child assignment can never override a parent deny.

Azure Policy — business rules as code

Azure Policy enforces organizational standards and assesses compliance at scale. It compares the properties of resources against business rules written as JSON policy definitions. Typical enforcement goals: deploy only to allowed regions, apply taxonomic tags consistently, require diagnostic logs to a Log Analytics workspace. With Azure Arc, the same policy-based governance extends to other clouds and on-premises datacenters. A compliance dashboard gives an aggregated view with drill-down to per-resource, per-policy detail.

The three building blocks

  • A policy definition holds the conditions to evaluate and the effect applied when they match. Parameters (e.g. a location parameter set to EastUS at assignment time) make one definition reusable. Built-in examples: Allowed Locations (Deny), Allowed Storage Account SKUs (Deny), Add a tag to resources (Modify).
  • An initiative (called policySet in the CLI/PowerShell SDKs) groups several definitions toward one overarching goal. When an initiative assignment is evaluated, all policies inside it are evaluated.
  • An assignment binds a definition or initiative to a scope — management group, subscription, resource group, or individual resource. All child resources inherit the assignment, and you can carve out subscopes with exclusions.
Policy definition(JSON: conditions + effect)Initiative / policySet(collection of definitions)Assignment(MG / sub / RG / resource,with exclusions)Trigger: resourcecreated or updatedTrigger: new orupdated assignmentTrigger: 24-hourcompliance cycleCompliance state:Compliant / Non-compliantRemediation task(deployIfNotExists /modify,runs as managed identity)

Two scope rules deserve special attention. First, although definitions can live at — and assignments can be made at — management-group level, only resources at subscription or resource-group level are actually evaluated. Second, Azure Policy is an explicit-deny system: a more permissive child assignment can never override a parent-level deny. If a sandbox subscription needs a way out, you exclude that subscope from the denying assignment (or use an exemption) — you don't assign a "counter-policy" below it.

Effects — what happens on a match

Effect familyExamplesWhat it does
Deny the changedenyThe create/update request fails
Log the changeaudit, auditIfNotExistsResource is flagged non-compliant, nothing blocked
Alter before the changeappend, modifyAdds/edits properties (e.g. tags) during the request
Alter after the changeAdjusts the resource after deployment
Deploy related resourcesdeployIfNotExistsDeploys a compliant companion resource if missing
Block actionsdenyActionPrevents specific actions on resources

When resources are evaluated

Evaluation is triggered when: a resource is created or updated in a scope with an assignment; a scope gets a new assignment; an existing assignment is updated; and on the standard compliance cycle once every 24 hours. Assignments always use the latest state of their assigned definition or initiative.

Remediation — fixing what already exists

Auditing finds non-compliant resources; remediation fixes them. The deployIfNotExists and modify effects support remediation tasks that run under a managed identity. In the portal, the managed-identity checkbox on the Remediation tab must be checked when the assigned policy contains one of these effects — and granting that identity its permissions requires the User Access Administrator role.

Policy and RBAC — two different questions

Azure RBAC manages user actions at different scopes; Azure Policy evaluates resource state, regardless of who made the change. Even if a user's role allows an action, a deny policy still blocks the create or update when the result would be non-compliant. The combination of the two provides full scope control. For transparency, all Policy objects are readable by all roles over their scope. The working permissions:

RoleAzure Policy rights
OwnerFull rights
Resource Policy ContributorMost Azure Policy operations (the "policy author" role)
ContributorRead everything; may trigger remediation but can't create/update definitions or assignments
ReaderRead all Policy operations
User Access AdministratorNeeded to grant the managed identity its permissions for deployIfNotExists/modify

Start with audit (or auditIfNotExists) rather than an enforcement effect, to measure impact before enforcing. Create definitions high in the hierarchy (management group or subscription) and assignments at the next child level. Use initiatives even for a single policy, so you can add policies later without multiplying assignments. Manage policy resources as code with reviewed changes. Selected limits: 500 policy definitions per scope, 200 initiative definitions per scope (2,500 per tenant), 200 assignments per scope, 400 exclusions (notScopes) per assignment, 1,000 policies per initiative.

Hands-on recap — assigning "Audit VMs that do not use managed disks"

The AZ-104 reference quickstart assigns the built-in definition Audit VMs that do not use managed disks to a resource group:

  1. Portal → search PolicyAssignmentsAssign Policy.
  2. Basics tab: pick the Scope (subscription + resource group via the ellipsis), leave Exclusions empty, select the definition from Available Definitions, accept Version 1.*.* (ingests major, minor, and patch updates — referencing a version is optional; assignments default to the latest), keep the default Assignment name, leave Policy enforcement Enabled.
  3. Remediation tab: no managed identity needed here (audit only) — mandatory only for deployIfNotExists/modify.
  4. Non-compliance messages tab: add a message such as "Virtual machines should use managed disks" — shown when a resource is denied or evaluated non-compliant.
  5. Review + createCreate. Then open Policy → Compliance: the state takes a few minutes to become active; select the assignment to see per-resource Resource Compliance.

How to read the compliance table: for Audit / Modify / AuditIfNotExists, a policy evaluation of True means Non-compliant (the condition detecting the problem matched). For DeployIfNotExists / AuditIfNotExists, non-compliance requires the IF statement True and the existence condition False.

Resource locks — the emergency brake

A resource lock protects a subscription, resource group, or resource from accidental deletion or modification. Unlike RBAC, a lock overrides all user permissions — it restricts all users and roles, including Owners.

Portal nameCommand-line nameAuthorized users can……but cannot
DeleteCanNotDeleteRead and modifyDelete
Read-onlyReadOnlyReadDelete or update (everyone effectively restricted to the Reader role)

Inheritance: a lock at a parent scope is inherited by all resources within it, including resources added later; the most restrictive lock in the chain wins. Extension resources inherit locks from the resource they're attached to. A Delete lock on a single resource blocks deleting its whole resource group (ARM refuses partial deletion). Two boundaries: you can't add a lock to a management group, and canceling a subscription is not blocked by locks.

Control plane only — and the gotcha list

Locks apply only to control-plane operations — requests to https://management.azure.com. Data-plane operations are not protected: a lock on a storage account does not protect blobs, queues, tables, or files from being modified or deleted, and a ReadOnly lock on a SQL logical server still allows data changes inside the databases. Locks also block some operations that don't look like "writes" because they are POST requests:

  • ReadOnly on a storage account blocks List Keys (a POST) — users must fall back to Microsoft Entra credentials for blob/queue data; it also blocks RBAC assignments scoped to the account and container creation via the control plane.
  • ReadOnly on a resource group containing a VM blocks starting or restarting the VM (POST) and prevents moving resources in or out of the group.
  • CanNotDelete on a resource or RG blocks deleting Azure RBAC role assignments there.
  • CanNotDelete on a resource group stops ARM from auto-pruning deployment history (at 800 deployments, new ones fail) and breaks Azure Backup restore-point cleanup.
  • ReadOnly on a subscription breaks Azure Advisor (it can't store its query results).
  • ReadOnly on an Application Gateway blocks getting backend health (POST).

Creating locks

Creating or deleting locks requires the Microsoft.Authorization/locks/* (or Microsoft.Authorization/*) actions — held by Owner and User Access Administrator. In the portal, the blade is named Locks on resources and resource groups, but "Resource locks" on a subscription. To lock everything in a group, lock the parent — children inherit. One-liners:

New-AzResourceLock -LockName LockGroup -LockLevel CanNotDelete -ResourceGroupName exampleresourcegroup
Get-AzResourceLock
az lock create --name LockGroup --lock-type CanNotDelete --resource-group exampleresourcegroup
az lock list --resource-group exampleresourcegroup

Note: some services (e.g. Azure Databricks) deploy as managed applications with a locked infrastructure resource group — you can't delete that RG or its lock directly; deleting the service removes both.

RBAC vs Policy vs Locks — the synthesis

Azure RBACAzure PolicyResource locks
Question answeredWho can do what actionsWhat state resources may haveShould anything change at all
ModelAdditive (no assignment = no access)Explicit deny (child can't override parent deny)Overrides all user permissions
Acts onIdentities (principal + role + scope)Resource properties, regardless of actorAll users and roles, including Owners
Bypass pathHigher-scope assignmentExclusion / exemption on the assignmentRemove the lock (Owner / UAA)

🧠 Mnemonic: "RBAC asks WHO, Policy asks WHAT, Locks say STOP." Three gates in a row: identity gets you in, compliance shapes what you build, and a lock freezes the result.

Exercises

Exercise 10.1 — Audit unmanaged disks and read the verdict

In a trial subscription with a resource group containing at least one VM without managed disks, assign the built-in policy Audit VMs that do not use managed disks to that resource group, add a non-compliance message, then find the compliance result. What compliance state do you expect for the VM, and why is nothing blocked?

Solution
  1. Portal → search PolicyAssignmentsAssign Policy.
  2. Basics: Scope = your subscription + the resource group; Policy definition = search Audit VMs that do not use managed disksAdd; Version = 1.*.*; keep defaults (enforcement Enabled).
  3. Parameters: none for this definition. Remediation: no managed identity needed — the effect is audit, not deployIfNotExists/modify.
  4. Non-compliance messages: e.g. "Virtual machines should use managed disks".
  5. Review + createCreate.
  6. Policy → Compliance → locate the assignment (allow a few minutes for the state to become active) → the VM shows Non-compliant; click the assignment for per-resource Resource Compliance.

Nothing is blocked because the effect is audit: evaluation True → the resource is logged as Non-compliant, but creates/updates proceed. That is exactly the recommended rollout pattern — audit first, enforce later.

Exercise 10.2 — Allowed Locations at MG level with a sandbox escape hatch

Your company mandates West Europe only. You plan to assign the Allowed Locations (Deny) policy at the top management group. The innovation team's sandbox subscription must be free to deploy anywhere. A colleague proposes assigning a second, permissive "allow all locations" policy directly on the sandbox subscription to override the deny. Will that work? Design the correct setup.

Solution

The colleague's plan fails. Azure Policy is an explicit-deny system: a management-group-level deny cannot be overridden by a more permissive child assignment — both assignments apply, and the deny wins.

Correct design:

  1. Assign Allowed Locations (with the location parameter set to West Europe) at the management group; all child subscriptions and resource groups inherit it.
  2. On that same assignment, add the sandbox subscription as an exclusion (notScope) — or use an exemption. The escape hatch lives on the denying assignment, never below it.

Bonus points for following the guidance: keep the definition high (management group), assign at the appropriate level, and wrap it in an initiative even if it's a single policy today, so future location/tag rules join the same assignment. Remember also that even with an MG-level assignment, only resources at subscription or resource-group level are evaluated.

Exercise 10.3 — Predict the ReadOnly fallout

You place a ReadOnly lock on resource group rg-prod containing a VM and a storage account. Predict: (a) Can an Owner start or restart the VM? (b) Can anyone list the storage account keys in the portal? (c) Can an application holding a valid SAS or Entra data-plane role still read and write blobs? (d) Can you move the storage account to another resource group?

Solution

(a) No. Starting/restarting a VM is a POST to the control plane; a ReadOnly lock blocks it for all users, including Owners — locks override user permissions.

(b) No. List Keys is also a POST operation, so a ReadOnly lock blocks it. Users must authenticate to blob/queue data with Microsoft Entra credentials instead of account keys.

(c) Yes. Locks apply only to control-plane requests (management.azure.com), not to data-plane endpoints (e.g. myaccount.blob.core.windows.net). Blob data can still be read, written, and even deleted.

(d) No. A ReadOnly lock on a resource group prevents moving existing resources in or out of that group. (Curiously, a resource that itself carries a ReadOnly lock can be moved to another group.)

Key takeaways

  • Azure Policy evaluates resource state against JSON policy definitions; definitions group into initiatives (policySets); assignments at MG/subscription/RG/resource inherit downward with optional exclusions.
  • Effects range from audit (log only) through append/modify (alter), deployIfNotExists (deploy companions), deny (block the change), to denyAction. Start with audit before enforcing.
  • Evaluation triggers: create/update, new assignment, updated assignment, and the 24-hour compliance cycle. Even with MG-level assignments, only subscription- and RG-level resources are evaluated.
  • Azure Policy is an explicit-deny system — no child override of a parent deny; use exclusions/exemptions on the denying assignment.
  • Remediation (deployIfNotExists/modify) runs under a managed identity; granting it permissions requires User Access Administrator; authoring requires Resource Policy Contributor (Contributor can only trigger remediation).
  • Locks come in two levels — CanNotDelete (read/modify but no delete) and ReadOnly (everyone ≈ Reader) — and override all user permissions; the most restrictive lock in the chain wins.
  • Locks are control-plane only: blob data stays writable, but POSTs like List Keys and VM start/restart are blocked by ReadOnly; no locks on management groups; canceling a subscription isn't blocked.
  • Layered model: RBAC (who) → Policy (what state) → Locks (stop).

Quick recall

Q: A user has Contributor at subscription scope and deploys a storage account in a region forbidden by an Allowed Locations deny policy. What happens? A: The deployment is blocked by Azure Policy — sufficient RBAC rights don't matter, because Policy evaluates resource state regardless of who acts.

Q: Which effects require a managed identity on the assignment, and who grants its permissions? A: deployIfNotExists and modify (for remediation); a User Access Administrator grants the managed identity its permissions.

Q: When is a resource evaluated for compliance? A: On create or update, when a scope gets a new assignment, when an assignment is updated, and on the 24-hour standard compliance cycle.

Q: Scenario — a ReadOnly lock sits on a subscription. Why did Azure Advisor stop working? A: Advisor needs to store its query results, a control-plane write; the ReadOnly lock blocks it — a classic lock gotcha.

Q: Can you lock a management group? Can a lock stop a subscription from being canceled? A: No and no — locks apply to subscriptions, resource groups, and resources only, and canceling a subscription is not blocked by locks.

Q: What's the difference between the portal names and command-line names of the two lock levels? A: Portal Delete = CLI/PowerShell CanNotDelete; portal Read-only = ReadOnly.

Coming up

Standards are enforced — now watch the money. Episode 11 closes the module with cost control: how charges flow through Cost Management, budgets and their alert thresholds, credit and anomaly alerts, and Azure Advisor's recommendation categories.