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
.tffile 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:
or, for the starter resource group, in resource-lock.tf:
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. |