Production VMs carrying no CanNotDelete resource lock
What does ZopNight detect here?
A CanNotDelete lock is the last guardrail between a production Azure VM and one wrong az vm delete by anyone holding delete permissions. RC-266 flags running, production-classified VMs whose lock state is an explicit false, taken from the Microsoft.Authorization/locks lookup or a deletion_protection=false tag, and abstains when no signal exists.
Signal and threshold
| Field | Value |
|---|---|
| Rule IDs | RC-266 |
| Category | compliance |
| Severity | medium |
| Metric | none — pure configuration read |
| Source | vm_deletion_protection.go |
Where it applies
The blast radius of a mistyped delete
Deleting an Azure VM is fast, silent, and final for the compute resource itself. Disks may
survive depending on their delete options, but the machine, its NIC associations, and its
runtime state are gone, and if backups were never configured the recovery path is a rebuild. A
CanNotDelete lock converts that scenario into a failed API call: the delete is refused until
someone with lock-management rights consciously removes the lock first, which is exactly the
deliberate two-step you want in front of production.
Only production VMs are held to this bar
Dev and test machines are disposable by design, so the rule first requires production classification: a production naming pattern or an explicit env=prod tag, whichever matched is named in the finding. The tag is treated as the authoritative signal and the name match as a guess. That ordering matters, since name-only classification had previously pulled ordinary VMs into scope. Deallocated machines are also skipped; only running production workloads generate findings.
From fail-open to fail-closed
The instructive part of this rule is its history. The original gate fired when the lock signal
was absent or false, which meant every unlabelled production VM was flagged, an evidence-free
default. The current logic reads the discoverer’s deletion_protection metadata (derived from
the Microsoft.Authorization/locks lookup set) first and fires only on an explicit "false";
if that key is absent it falls back to requiring a literal deletion_protection=false
customer tag. No signal at all now means no finding.
Audit lock coverage yourself
az lock list --resource-group <rg> \ --query "[?level=='CanNotDelete'].{name:name, scope:id}" -o tableCompare against the resource group’s production VMs; anything unlisted is unprotected.
Cheap insurance
Locks cost nothing, apply in seconds (az lock create --lock-type CanNotDelete ...), and are
reversible by anyone with Microsoft.Authorization/locks/* rights, typically Owner or User
Access Administrator, which is the point: routine contributors can’t delete around them.
Consider ReadOnly locks for the most critical machines, noting they also block writes like
resizes. Reader reproduces the detection; ZopNight places no locks itself.