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'

Trackbacks and Pingbacks:

  1. Lock Down Your Azure Resources - Cloud Solution Architect - Site Home - MSDN Blogs - June 18, 2015

    […] For another example of using resource locks, please see Alexandre Brisebois’ post at https://alexandrebrisebois.wordpress.com/2015/05/26/protect-mission-critial-azure-virtual-machines-f…. In his post, Alexandre demonstrates how to create a resource lock against Azure Virtual […]

    Like

  2. Creating a Visual Studio Enterprise 2015 RC Virtual Machine with ARM « Alexandre Brisebois ☁ - June 18, 2015

    […] mentioned in a previous post, it can be critical to protect important Virtual Machines from deletion. Personally, I […]

    Like

  3. Locking a Virtual Machine Resource – Azure Resource Manager Template « Alexandre Brisebois ☁ - June 18, 2015

    […] the way that I deleted the resource lock. I did this because I ran into some issues with the code from my earlier post. Using the Lock Resource ID worked like a charm for this […]

    Like

  4. Lock Down Your Azure Resources | Michael S. Collier's Blog - June 21, 2015

    […] template. For another example of using resource locks, please see Alexandre Brisebois’ post at https://alexandrebrisebois.wordpress.com/2015/05/26/protect-mission-critial-azure-virtual-machines-f…. In his post, Alexandre demonstrates how to create a resource lock against Azure Virtual […]

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.