Azure Resource Locks & Protection
Resource locks are one of the most powerful guardrails in Azure. They prevent deletion or modification of resources regardless of who (or what AI agent) attempts the operation — even users with Owner permissions cannot bypass a lock without first removing it.
Understanding Lock Types
| Lock Type | Effect | AI Agent Impact | Use Case |
|---|---|---|---|
| CanNotDelete | Allows read and modify, blocks delete | Agent can update resources but cannot delete them | Production resources that need updates but must not be removed |
| ReadOnly | Allows read only, blocks modify and delete | Agent can only view resources, no changes at all | Critical infrastructure that should never be modified |
Applying Locks via Azure CLI
# Lock an individual resource (e.g., a production SQL database) az lock create \ --name "protect-prod-db" \ --lock-type CanNotDelete \ --resource-group rg-production \ --resource-name sql-prod-main \ --resource-type Microsoft.Sql/servers # Lock an entire resource group az lock create \ --name "protect-prod-rg" \ --lock-type CanNotDelete \ --resource-group rg-production # Lock at subscription level (protects ALL resource groups) az lock create \ --name "protect-subscription" \ --lock-type CanNotDelete # Create a ReadOnly lock on critical networking resources az lock create \ --name "readonly-vnet" \ --lock-type ReadOnly \ --resource-group rg-networking \ --resource-name vnet-hub-prod \ --resource-type Microsoft.Network/virtualNetworks
# Create a CanNotDelete lock on a resource group New-AzResourceLock ` -LockName "protect-prod-rg" ` -LockLevel CanNotDelete ` -ResourceGroupName "rg-production" ` -LockNotes "Protects production resources from AI agent deletion" # Create a ReadOnly lock on a specific resource New-AzResourceLock ` -LockName "readonly-keyvault" ` -LockLevel ReadOnly ` -ResourceGroupName "rg-security" ` -ResourceName "kv-prod-secrets" ` -ResourceType "Microsoft.KeyVault/vaults" # List all locks in a subscription Get-AzResourceLock # List locks on a specific resource group Get-AzResourceLock -ResourceGroupName "rg-production"
Lock Inheritance and Scope
Locks in Azure follow an inheritance model. A lock applied at a parent scope automatically applies to all child resources:
-
Subscription-Level Lock
Applies to all resource groups and all resources within the subscription. This is the broadest scope and provides blanket protection. Best for production subscriptions where deletion should always require explicit approval.
-
Resource Group Lock
Applies to the resource group itself and all resources within it. Resources added to the group after the lock is created also inherit the lock. This is the most common scope for AI agent protection.
-
Resource-Level Lock
Applies only to the specific resource. Use this for targeted protection of individual critical resources like databases, Key Vaults, or DNS zones within resource groups that contain a mix of critical and non-critical resources.
Terraform Examples for Resource Locks
# Lock a resource group
resource "azurerm_management_lock" "rg_lock" {
name = "protect-prod-rg"
scope = azurerm_resource_group.production.id
lock_level = "CanNotDelete"
notes = "Protects production RG from AI agent deletion"
}
# Lock an individual SQL database
resource "azurerm_management_lock" "sql_lock" {
name = "protect-prod-sql"
scope = azurerm_mssql_database.production.id
lock_level = "CanNotDelete"
notes = "Production database - cannot be deleted"
}
# Lock a Key Vault with ReadOnly
resource "azurerm_management_lock" "keyvault_lock" {
name = "readonly-keyvault"
scope = azurerm_key_vault.production.id
lock_level = "ReadOnly"
notes = "Production Key Vault - no modifications allowed"
}
# Use prevent_destroy as an additional safety layer
resource "azurerm_resource_group" "production" {
name = "rg-production"
location = "eastus"
lifecycle {
prevent_destroy = true
}
}
resource "azurerm_mssql_database" "production" {
name = "sqldb-prod-main"
server_id = azurerm_mssql_server.production.id
sku_name = "S1"
lifecycle {
prevent_destroy = true
}
}
When Locks Block Legitimate Operations
Locks are intentionally strict, which means they will occasionally block operations you actually want to perform. Here is how to manage the lock lifecycle safely:
#!/bin/bash # safe-lock-lifecycle.sh # Removes a lock, performs the operation, and re-applies the lock LOCK_NAME="protect-prod-rg" RESOURCE_GROUP="rg-production" # Step 1: Document the operation (audit trail) echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ") - Removing lock '$LOCK_NAME' for authorized operation" | tee -a /var/log/lock-operations.log # Step 2: Remove the lock az lock delete --name "$LOCK_NAME" --resource-group "$RESOURCE_GROUP" # Step 3: Perform the authorized operation az vm delete --resource-group "$RESOURCE_GROUP" --name "vm-deprecated-001" --yes # Step 4: Re-apply the lock immediately az lock create \ --name "$LOCK_NAME" \ --lock-type CanNotDelete \ --resource-group "$RESOURCE_GROUP" # Step 5: Verify the lock is back in place az lock list --resource-group "$RESOURCE_GROUP" --output table echo "$(date -u +"%Y-%m-%dT%H:%M:%SZ") - Lock '$LOCK_NAME' re-applied successfully" | tee -a /var/log/lock-operations.log
Microsoft.Authorization/locks/delete permission. This means even if the agent knows about the lock, it cannot remove it. Only human operators with explicit lock management permissions should be able to remove locks.Automating Lock Application for New Resources
Use Azure Event Grid and Azure Functions to automatically apply locks whenever new resources are created:
import azure.functions as func
import json
from azure.identity import DefaultAzureCredential
from azure.mgmt.resource.locks import ManagementLockClient
def main(event: func.EventGridEvent):
"""Automatically apply CanNotDelete lock to new resources in production RGs."""
data = event.get_json()
resource_id = data.get("resourceUri", "")
operation = data.get("operationName", "")
# Only act on successful resource creation in production resource groups
if "rg-production" not in resource_id:
return
if "/write" not in operation:
return
credential = DefaultAzureCredential()
subscription_id = resource_id.split("/")[2]
lock_client = ManagementLockClient(credential, subscription_id)
# Apply CanNotDelete lock to the new resource
lock_client.management_locks.create_or_update_by_scope(
scope=resource_id,
lock_name="auto-protect",
parameters={
"level": "CanNotDelete",
"notes": "Auto-applied by guardrail function"
}
)
print(f"Applied CanNotDelete lock to: {resource_id}")
Microsoft.Resources.ResourceWriteSuccess event type via Event Grid. Filter the subscription to only trigger for resource groups matching your production naming convention (e.g., rg-production*).
Lilly Tech Systems