Intermediate

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
Important: A ReadOnly lock can have unexpected side effects. For example, a ReadOnly lock on a storage account prevents listing keys (which is a POST operation, not GET). A ReadOnly lock on a resource group prevents creating new resources in that group. Plan carefully when using ReadOnly locks.

Applying Locks via Azure CLI

Creating CanNotDelete locks at different scopes
# 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
PowerShell equivalents for lock management
# 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:

  1. 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.

  2. 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.

  3. 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

main.tf - Terraform resource lock examples
# 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:

Safe lock removal and re-application workflow
#!/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
Key Point: The AI agent's custom role should not include the 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:

Azure Function to auto-apply locks (Python)
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}")
💡
Automation Tip: Subscribe the Azure Function to the 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*).