Lock it Down!
Accidents happen. Resource Locks help prevent them.
The Resource Lock level is applied at the resource group or resource scope. These can be set by the administrators and current values include CanNotDelete and ReadOnly.
Using a modified version of the ARM Template from a post on creating a CentOS Virtual Machine, let’s provision a VM and protect it from accidental deletion.
As a best practice, we should consider implementing Resource Locks for mission critical resources.
The Azure Resource Manager (ARM) Template
To make things easy to follow, both data disks and the Custom Script for Linux Virtual Machine extension were removed from the ARM Template.
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "newStorageAccountName": { "type": "string", "metadata": { "description": "Unique DNS Name for the Storage Account where the Virtual Machine's disks will be placed." } }, "adminUsername": { "type": "string", "metadata": { "description": "User name for the Virtual Machine." } }, "adminPassword": { "type": "securestring", "metadata": { "description": "Password for the Virtual Machine." } }, "dnsNameForPublicIP": { "type": "string", "metadata": { "description": "Unique DNS Name for the Public IP used to access the Virtual Machine." } }, "OSVersion": { "type": "string", "defaultValue": "7.1", "allowedValues": [ "7.1" ] } }, "variables": { "addressPrefix": "10.0.0.0/16", "dataDiskSize": "100", "imageOffer": "CentOS", "imagePublisher": "OpenLogic", "location": "West US", "nicName": "myVMNic", "OSDiskName": "osdisk", "publicIPAddressType": "Dynamic", "scriptUrl": "https://briseboispackages.blob.core.windows.net/linux/centos-vm-disk-utils-0.1.sh", "storageAccountType": "Standard_LRS", "subnetName": "Subnet", "subnetPrefix": "10.0.0.0/24", "subnetRef": "[concat(variables('vnetID'),'/subnets/',variables('subnetName'))]", "virtualNetworkName": "MyVNET", "vmName": "msbriseboislinux", "vmSize": "Standard_A3", "vmStorageAccountContainerName": "vhds", "vnetID": "[resourceId('Microsoft.Network/virtualNetworks',variables('virtualNetworkName'))]" }, "resources": [ { "type": "Microsoft.Storage/storageAccounts", "name": "[parameters('newStorageAccountName')]", "apiVersion": "2015-05-01-preview", "location": "[variables('location')]", "tags": { "displayName": "StorageAccount" }, "properties": { "accountType": "[variables('storageAccountType')]" } }, { "apiVersion": "2015-05-01-preview", "type": "Microsoft.Network/publicIPAddresses", "name": "[parameters('dnsNameForPublicIP')]", "location": "[variables('location')]", "tags": { "displayName": "PublicIPAddress" }, "properties": { "publicIPAllocationMethod": "[variables('publicIPAddressType')]", "dnsSettings": { "domainNameLabel": "[parameters('dnsNameForPublicIP')]" } } }, { "apiVersion": "2015-05-01-preview", "type": "Microsoft.Network/virtualNetworks", "name": "[variables('virtualNetworkName')]", "location": "[variables('location')]", "tags": { "displayName": "VirtualNetwork" }, "properties": { "addressSpace": { "addressPrefixes": [ "[variables('addressPrefix')]" ] }, "subnets": [ { "name": "[variables('subnetName')]", "properties": { "addressPrefix": "[variables('subnetPrefix')]" } } ] } }, { "apiVersion": "2015-05-01-preview", "type": "Microsoft.Network/networkInterfaces", "name": "[variables('nicName')]", "location": "[variables('location')]", "tags": { "displayName": "NetworkInterface" }, "dependsOn": [ "[concat('Microsoft.Network/publicIPAddresses/', parameters('dnsNameForPublicIP'))]", "[concat('Microsoft.Network/virtualNetworks/', variables('virtualNetworkName'))]" ], "properties": { "ipConfigurations": [ { "name": "ipconfig1", "properties": { "privateIPAllocationMethod": "Dynamic", "publicIPAddress": { "id": "[resourceId('Microsoft.Network/publicIPAddresses',parameters('dnsNameForPublicIP'))]" }, "subnet": { "id": "[variables('subnetRef')]" } } } ] } }, { "apiVersion": "2015-05-01-preview", "type": "Microsoft.Compute/virtualMachines", "name": "[variables('vmName')]", "location": "[variables('location')]", "tags": { "displayName": "VirtualMachine" }, "dependsOn": [ "[concat('Microsoft.Storage/storageAccounts/', parameters('newStorageAccountName'))]", "[concat('Microsoft.Network/networkInterfaces/', variables('nicName'))]" ], "properties": { "hardwareProfile": { "vmSize": "[variables('vmSize')]" }, "networkProfile": { "networkInterfaces": [ { "id": "[resourceId('Microsoft.Network/networkInterfaces',variables('nicName'))]" } ] }, "osProfile": { "computername": "[variables('vmName')]", "adminUsername": "[parameters('adminUsername')]", "adminPassword": "[parameters('adminPassword')]" }, "storageProfile": { "imageReference": { "publisher": "[variables('imagePublisher')]", "offer": "[variables('imageOffer')]", "sku": "[parameters('OSVersion')]", "version": "latest" }, "osDisk": { "name": "osdisk", "vhd": { "uri": "[concat('http://',parameters('newStorageAccountName'),'.blob.core.windows.net/',variables('vmStorageAccountContainerName'),'/',variables('OSDiskName'),'.vhd')]" }, "caching": "ReadWrite", "createOption": "FromImage" } } } } ] }
Template Parameters
{ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "newStorageAccountName": { "value": "mslinuxbrisebois" }, "adminUsername": { "value": "brisebois" }, "dnsNameForPublicIP": { "value": "briseboisdns" } } }
Deploying the Template to Azure
To deploy the ARM Template, navigate to the folder that contains both the template and parameter files. Then call the New-AzureResourceGroup cmdlet.
Switch-AzureMode AzureResourceManager New-AzureResourceGroup -Name 'mslinuxbrisebois' ` -Location 'westus' ` -TemplateFile 'DeploymentTemplate.json' ` -TemplateParameterFile 'DeploymentTemplate.param.dev.json' ` -Verbose
Creating a Resource Lock
Resource Locks can be applied on Resource Groups and on individual Resources like VMs and Storage Accounts. In this example, we are locking a Virtual Machine so that it cannot be deleted by accident.
New-AzureResourceLock -LockLevel CanNotDelete ` -LockNotes 'Critial Virtual Machine, do not delete.' ` -LockName 'msbriseboislinuxlock' ` -ResourceName 'msbriseboislinux' ` -ResourceType 'Microsoft.Compute/virtualMachines' ` -ResourceGroupName 'mslinuxbrisebois' ` -Verbose
Deleting the Resource Group
To show what happens when we try to delete a Resource Group that contains a Resource Lock, we can execute the Remove-AzureResourceGroup cmdlet.
Remove-AzureResourceGroup -Name 'mslinuxbrisebois'
The cmdlet should fail with an error similar to the following.
Remove-AzureResourceGroup : ScopeLocked: The scope '/subscriptions/64668628-d5e6-4c5b-9be4-9e577d5b8fd0/resourcegroups/mslinuxbrisebois' cannot perform delete operation because following scope(s) are locked: '/subscriptions/64668628-d5e6-4c5b-9be4-9e577d5b8fd0/resourceGroups/mslinuxbrisebois/providers/Mi crosoft.Compute/virtualMachines/msbriseboislinux'. Please remove the lock and try again. At line:1 char:1 + Remove-AzureResourceGroup -Name 'mslinuxbrisebois' + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + CategoryInfo : CloseError: (:) [Remove-AzureResourceGroup], CloudException + FullyQualifiedErrorId : Microsoft.Azure.Commands.Resources.RemoveAzureResourceGroupCommand
Removing the Resource Lock & Deleting the Resource Group
Deleting a Resource Group that contains Resources Lock requires us to start by removing all Locks. Then we can use the Remove-AzureResourceGroup cmdlet to delete the Resource Group.
# remove the lock before the resource group Get-AzureResourceLock -ResourceGroupName 'mslinuxbrisebois' ` -ResourceName 'msbriseboislinux' ` -ResourceType 'Microsoft.Compute/virtualMachines' ` | Remove-AzureResourceLock Remove-AzureResourceGroup -Name 'mslinuxbrisebois'