Introduction

You are busy developing. It is late at night. You decide to delete one of your test virtual machines and recreate it to validate your deployment procedure. You log into the Azure Portal, select a virtual machine and click DELETE.

Then you proceed to create a new virtual machine. You receive an error that a virtual machine with the same name exists. Next, your cell phone lights up with alerts. You say to yourself, what the shazam did I do?

Now it is 5 AM. You have recovered the deleted virtual machine from snapshots. You pat yourself on the back for being prepared for emergencies. You leave your home office and try to get a few hour’s sleep.

The next day, you set a goal to never allow that to happen again.

This is a true story. I was working late and made a mistake that luckily only cost me a few hours of time to recover from.

Introducing Azure Resource Locks

Azure supports locking resources at the subscription, resource group, and resource level. Implementing locks in the Azure Portal is easy. However, when I tried to lock a virtual machine using the CLI, I found the documentation lacking. It took a few tries and failures to get the parameters correct.

There are two types of locks:

  • CanNotDelete. This lock prevents deleting a resource. The resource can still be read and modified by authorized users.
  • ReadOnly. This lock prevents modifying a resource including preventing stop, start, and deletion.

Whereas RBAC grants permissions to users, locks affect all users regardless of their roles. Locks override any permissions the user might have.

Locks can be modified or removed with the permission Microsoft.Authorization/locks/*. The built-in roles Owner and User Access Administrator have this permission.

Note: I discovered that an additional lock can be applied to a resource that has the ReadOnly lock applied.

Locks are inherited. This means that if you apply a lock at the Subscription level all resources in that subscription are locked.

This article focuses on using the Azure CLI and Terraform to apply CanNotDelete locks to a virtual machine. There are other resource types related to virtual machines that you might also want to lock, such as a public IP address. Terraform supports locks with the azurerm_management_lock resource.

[Update 2021-09-30]: I added a Python program to list the locks with an Azure subscription using the Azure SDK for Python.

Azure CLI

Figuring out the CLI was the hardest part. In comparison, writing Terraform HCL was easy. My challenge was partially due to the Azure Lock documentation and partially because I am very comfortable with Azure. I made assumptions about parameters that are wrong in the context of Azure locks.

For example, the parameter –resource-name exists. The documentation states [link]:

Name or ID of the resource being locked. If an ID is given, other resource arguments should not be given.

I incorrectly assumed that the ID was the Resource ID of the virtual machine. That was a wrong assumption. By using the virtual machine resource ID, I locked the entire subscription. I still do not know why.

The second problem that I had was figuring out the Resource Type. I need to locate a document that lists these types. I switched to writing Terraform HCL to lock the virtual machine and then I used the Azure CLI command, az lock list, to see the parameters. From that, I figured out the Resource Type is Microsoft.Compute/virtualMachines.

To use the Azure CLI, collect the following information:

  • Azure Resource Group Name
  • Azure Virtual Machine Name

The final command that is the easiest to configure (Windows command syntax):

@set RESOURCE_GROUP=REPLACE_ME 
@set VM_NAME=REPLACE_ME 

az lock create ^ 
--resource-group %RESOURCE_GROUP% ^ 
--name "Prevent-VM-Delete" ^ 
--lock-type CanNotDelete ^ 
--resource-name %VM_NAME% ^ 
--resource-type Microsoft.Compute/virtualMachines

Resource Type

[Update 2021-09-23]

Resource Type is actually two items. The namespace (provider) and the type.

This command will list the providers:

az provider list --query "[].{Provider:namespace, Status:registrationState}" --out table

This command will list the types for a provider. In this example, for Microsoft.Compute.

az provider show --namespace Microsoft.Compute --query "sort(resourceTypes[].resourceType)" -o tsv

Similar command using jq and sort.

az provider show --namespace Microsoft.Compute | jq -r ".resourceTypes[] | .resourceType" | sort

Truncated Output:

...
virtualMachines
virtualMachines/extensions
virtualMachines/metricDefinitions
virtualMachineScaleSets
virtualMachineScaleSets/extensions
virtualMachineScaleSets/networkInterfaces
virtualMachineScaleSets/publicIPAddresses
virtualMachineScaleSets/virtualMachines
virtualMachineScaleSets/virtualMachines/extensions
virtualMachineScaleSets/virtualMachines/networkInterfaces

Terraform

If I would have started this project with Terraform, I would have figured out the options easier than using the Azure CLI.

My Terraform files are on GitHub: GitHub Gist

To use Terraform, collect the following information:

  • Azure Resource Group Name
  • Azure Virtual Machine Name

Create a working directory on your system.

Create a file named terraform.tfvars with the following content modified with the Resource Group and Virtual Machine names:

Create another file named main.tf with the main Terraform HCL

The last file contains definitions for the variables. Name this file variables.tf.

Complete the following steps to initialize, validate, and plan as preliminary steps to validate the HCL and variables:

  • terraform init
  • terraform validate
  • terraform plan

After reviewing the output from the above commands for mistakes, warnings, and errors, you are ready to lock the Azure Virtual Machine:

  • terraform apply

Run this command to remove the virtual machine lock. Do not worry, this will not delete the virtual machine. Only the lock will be deleted.

  • terraform destroy

Python program

The following example shows how to use the Azure SDK for Python to lock a virtual machine. For details on setting up the SDK environment, see my article: Azure – Setting up a Development Environment for Python

Create a requirements.txt file

The file requirements.txt declares the Python packages that are required for a program to operate correctly.

Using your favorite editor, create a file named requirments.txt with the following content:

azure-mgmt-resource>=18.0.0
azure-identity>=1.5.0

Install dependencies

  • pip install -r requirements.txt

Create the program file

Create a file named list_locks.py

##############################################################################
# Date Created: 2021-09-17
# Last Update:  2021-09-17
# https://www.jhanley.com - Google Cloud
# Copyright (c) 2021, John J. Hanley
# Author: John J. Hanley
# License: MIT
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
##############################################################################

'''
This program displays the Azure Manage Locks at the subscription level.
'''

# Indents are tabs
# Add this to your .pylintrc
# [FORMAT]
# indent-string="\t"

# Requirements:
# pip install -r requirements.txt

import os
from azure.mgmt.resource import ManagementLockClient
from azure.identity import DefaultAzureCredential

def get_authorization_token():
    '''
    Return an authorization token that is setup by the Azure CLI.
    This requires the credentials created by the command "az login" are valid.
    '''

    credential = DefaultAzureCredential(
        exclude_cli_credential=False,
        exclude_environment_credential=True,
        exclude_managed_identity_credential=True,
        exclude_visual_studio_code_credential=True,
        exclude_shared_token_cache_credential=True
    )

    return credential

def get_resource_name(resource_id):
    '''
    Parse the Resource ID and return the Resource Name
    '''

    parts = resource_id.split('/')

    # print('len: ', len(parts))
    if len(parts) < 9:
        return ''

    return parts[8]

def get_resource_type(resource_id):
    '''
    Parse the Resource ID and return the Resource Type
    '''

    parts = resource_id.split('/')

    # print('len: ', len(parts))
    if len(parts) < 9:
        return ''

    return parts[7]

def display_locks(locks):
    '''
    Display the Azure Management Locks formatted to the screen.
    '''

    # Display a header
    print("{0:<20} {1:<22} {2:<15} {3}".format(
        "Resource Name",
        "Resource Type",
        "Lock Level",
        "Lock Name"))

    print("{0} {1} {2} {3}".format("-"*20, "-"*22, "-"*15, "-"*15))

    for item in locks:
        print(
            "{0:<20} {1:<22} {2:<15} {3}"
            .format(
                get_resource_name(item.id),
                get_resource_type(item.id),
                item.level,
                item.name,
            )
        )

if __name__ == '__main__':
    # Main function.
    # 1) Get the Subscription ID
    # 2) Get authorization credentials
    # 3) Fetch the Azure Management Locks
    # 4) Display lock details

    # Retrieve subscription ID from an environment variable.
    # az account show
    # set AZURE_SUBSCRIPTION_ID=REPACLE_ME
    azure_subscription_id = os.environ["AZURE_SUBSCRIPTION_ID"]

    # Get a authorization credentials
    azure_credential = get_authorization_token()

    # Fetch the Azure Management Locks
    # at the subscription level
    client = ManagementLockClient(azure_credential, azure_subscription_id)

    azure_locks = list(client.management_locks.list_at_subscription_level())

    # Display lock details
    display_locks(azure_locks)

Setup environment variable

This step requires the Azure CLI to display the account Subscription ID. This is also available in the Azure Portal.

Run the following command:

  • az account show

The account information is displayed in JSON. Copy the value for “id” without the quotes.

Run the following command to set the environment variable AZURE_SUBSCRIPTION_ID. Replace the text REPLACE_ME with the id from the previous command output.

  • set AZURE_SUBSCRIPTION_ID=REPLACE_ME

Run the program

If there are any Management Locks within the subscription, a formatted table will be displayed.

Run the Python program:

  • python list_locks.py

Example output:

Resource Name        Resource Type          Lock Level      Lock Name
-------------------- ---------------------- --------------- ---------------
jhanley-dev          virtualMachines        CanNotDelete    Prevent-Delete
jhanley-dev-ip       publicIPAddresses      CanNotDelete    Prevent-Delete
home-only            networkSecurityGroups  CanNotDelete    Prevent-Delete

Summary

I hope this article inspires you to take action and protect important Azure resources. Locks are one type of method to protect resources. Strong security, backups, snapshots, etc. are also important tools.

Using the Azure Portal makes most tasks very simple. However, I find that I do not learn the low-level details only using the Azure Portal. Combining knowledge of the details of many services and tools helps broaden and deepen my command of Azure.

More Information

Photography Credits

I write free articles about technology. Recently, I learned about Pexels.com which provides free images. The image in this article is courtesy of Pixabay at Pexels.