Skip to content

Manage Resource Locks

The engineer's guide to delete locks in a vended subscription. Use this runbook to:

  • Lock a new resource group.
  • Delete or replace something that sits behind a lock.
  • Work out why a change failed with ScopeLocked.
  • Clear a "Resource Group Delete Lock Required" finding.

What a lock does and doesn't do

A CanNotDelete lock stops anyone deleting the resource group or anything in it, Owners and pipelines included, until the lock itself is removed. Changes and new resources are still allowed. It's a guard against mistakes (a bad terraform destroy, a slip in the portal), not an access control. Removing a lock is deliberate and visible, which is the point.

Where locks come from

Lock On Set by Switch
rg-platform-<name>-delete-lock rg-platform-<name> Vending (solution-manage-azure-subscription) platform_resource_group_delete_lock_enabled in the subscription's <name>.tf
lock-cannotdelete The starter resource group in a new workload project The scaffold's resource-lock.tf, committed once by vending local.enable_resource_lock in the workload repo
lock-cannotdelete Any resource group made with solution-manage-resource-group That solution enable_delete_lock on the module call
Delete locks on rg-terraform-state* State resource groups azure-tenant-bootstrap and subscription-vending-platform Tier 0. Don't remove them.

All of these are CanNotDelete. Don't use ReadOnly: it blocks more than it looks like. For example, it blocks listing storage account keys and some App Service operations, because those are POST calls that Azure treats as writes.

Who can remove a lock

Removing a lock needs Microsoft.Authorization/locks/delete. Contributor and RBAC Administrator don't include it.

Identity Can manage locks?
The subscription's pipeline, sp-sub-tf-<name>-rw Yes. It holds the custom role Grinntec - Lock Contributor - <name> (Microsoft.Authorization/locks/* only).
grp-<name>-contributor, grp-<name>-role-based-access-administrator No.
The vending pipeline Yes, on rg-platform-<name>

So a team removes a lock through its own pipeline, in code. Nobody removes one by hand in the portal.

The projects

Project Lane Holds
The workload's own repo, grinntec-cloud/deployments/workloads/<name> B Locks on the team's resource groups. Its CI applies on merge as sp-sub-tf-<name>-rw.
subscription-vending-workload / -platform B / A The lock on rg-platform-<name>.
azure-governance-epac B The audit policy Grinntec - General - Resource Group Delete Lock Required (gen-gov-ResourceGroupLockRequired).

Lock a new resource group

📁 workload repo: 📃 the .tf file with the resource group

Create resource groups with solution-manage-resource-group. The lock is on by default:

module "rg_payments_data" {
  source = "git::https://gitlab.com/grinntec-cloud/terraform-modules/terraform-solutions/solution-manage-resource-group.git?ref=v2.0.1"

  name        = "payments-data"
  location    = "westeurope"
  environment = "prod"
  workload    = "payments"

  enable_delete_lock = true   # default
  lock_notes         = "Holds the payments database. Contact the payments team before removing."
}

Write lock_notes for the person who meets the lock in a year: what's in the group and who to ask.

If you made a resource group some other way, lock it with the lock module:

module "rg_payments_data_lock" {
  source = "git::https://gitlab.com/grinntec-cloud/terraform-modules/terraform-azure-modules/terraform-azurerm-resource-lock.git?ref=v1.0.1"

  scope_id = azurerm_resource_group.payments_data.id
}

The plan should show one azurerm_management_lock add. Leave a group unlocked only if Terraform is expected to delete and recreate it routinely (for example, an ephemeral sandbox), and say so in a comment.


Delete or replace something behind a lock

Use two MRs. Never remove the lock and make the destructive change in the same apply. The second MR's plan is the one reviewers need to see on its own: it's the one that deletes things.

MR 1: remove the lock

In the workload repo, switch the lock off:

  enable_delete_lock = false   # solution-manage-resource-group

or, for the starter resource group, in resource-lock.tf:

locals {
  enable_resource_lock = false
}

The plan should show one azurerm_management_lock destroy and nothing else. Merge. CI applies.

MR 2: make the change

Make the delete or replace. Check the plan destroys only what you mean it to. Merge. CI applies.

MR 3: put the lock back

Unless the resource group itself is gone, set the switch back to true. The plan should show one azurerm_management_lock add. Merge.

Do MR 3 the same day. An unlocked resource group shows up as non-compliant on gen-gov-ResourceGroupLockRequired until it's locked again.

Replacements count as deletes

A lock also blocks Terraform replacing a resource in the group: the replace starts with a delete. If a plan shows -/+ on something in a locked group, it needs the same three MRs.

For rg-platform-<name>

The same three steps, but in the subscription vending repo: platform_resource_group_delete_lock_enabled = false, then the change, then true again. For a platform subscription each step is a Lane A apply. rg-platform-<name> is platform-owned: agree it with the platform team first.


Check it worked

# Locks on a resource group
az lock list --resource-group rg-payments-data-prod \
  --query "[].{name:name, level:level, notes:notes}" -o table

# Every lock in the subscription
az lock list --subscription <sub-id> \
  --query "[].{name:name, level:level, scope:id}" -o table

In the portal: the resource group → Settings → Locks.


When it fails

Symptom Cause Fix
ScopeLocked: The scope '...' cannot perform delete operation because following scope(s) are locked The plan deletes or replaces something in a locked group Two MRs: remove the lock first.
AuthorizationFailed on Microsoft.Authorization/locks/write or /delete The identity has Contributor but not the Lock Contributor role. Either a person is trying it by hand, or the role assignment is missing. Do it through the workload pipeline. If the pipeline fails too, re-run subscription vending to restore the role assignment.
RoleDefinitionWithSameNameExists creating Grinntec - Lock Contributor - <name> A role with the same name is left over from a deleted or migrated subscription. Custom role names are unique per tenant. Delete the orphaned role definition (needs Owner or User Access Administrator at its scope), then re-run vending.
gen-gov-ResourceGroupLockRequired flags a resource group It has no CanNotDelete lock Add one as in Lock a new resource group. If the group is meant to be unlocked, request an exemption with an expiry.
A lock removed in the portal comes back It's in Terraform, and the next apply put it back Intended. Remove it in code.
Plan wants to add a lock that's already there Someone created the lock by hand, so it isn't in state terraform import it into the lock module's address, or remove the hand-made lock and let Terraform create it.