Locks In Azure Bicep - On Resource(s), Resource Group, Subscription

Protecting production resources from an accidental deletion or modification can sometimes be a very important thing to do. This is where locks in Azure come in handy.

Lock in Azure is an extension resource which can be applied to different scopes (e.g. resource or resource group) and allows to prevent other users from deleting or modifying resource(s). Azure Bicep allows managing locks through a template similar to other Azure resources.

It’s not always the case that a lock can protect from a person with a malicious intent, but for sure it is useful at preventing human mistakes. For example, if someone has a Contributor role (or temporary elevated) and wants to delete an unused/test CosmosDB instance but by accident deletes a production one.

Contents:

Overview

In this post, we will cover a number of topics related to Azure locks and how to manage them using Azure Bicep.

As a first step, we discuss which permissions one needs to have to add/modify/delete locks. Keep in mind, that these permissions should be assigned to the identity under which the deployment is performed (e.g. user or service principal).

Next, we look at how to deploy a lock for a new resource or an already existing resource.

Also, it is possible to add a lock for an entire resource group or subscription, this is illustrated in the Locking Resource Group or Subscription section. By taking it a bit further, we see a use case of how to Create a Resource Group and Apply a Lock It.

If one wants to create many similar resources in a loop and also add locks for each of those resources, then Multiple Locks For Multiple Resources section will show a way how to achieve it.

NOTES:

Required Permissions

Managing locks requires certain permissions since we want to limit number of users who can delete or modify locks. As mentioned in the documentation, to work with locks we need to have Microsoft.Authorization/locks/* permissions, the star * includes read, write, and delete actions.

By default, Owner and User Access Administrator built-in roles include permissions to manage locks, but you can also create your custom role and include the necessary permissions.

Resource Lock

To add a lock for a particular resource, we just need to specify scope property when creating a lock. In the following example, we are using a storage account as a sample resource.

By using a symbolic name when specifying scope, Bicep will automatically create dependency between resources so that lock is deployed after the storage account’s deployment is finished.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ========== resource-lock.bicep ==========

param storageAccountName string = 'stcontoso'

// Creating some storage account resource
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' = {
  name: storageAccountName
  location: resourceGroup().location
  sku: {
    name: 'Standard_GRS'
  }
  kind: 'StorageV2'
}

// Setting a lock for the storage account
resource lock 'Microsoft.Authorization/locks@2017-04-01' = {
  name: '${storageAccountName}-lock'
  properties: {
    level: 'CanNotDelete'
    notes: 'Storage account should not be deleted'
  }
  scope: storageAccount  // Apply lock to the storage account only
}

Add Lock To an Existing Resource

Locking an existing resource is very similar to the resource lock described previously in this post. The only difference is that we don’t create the resource in the template but instead just only create a symbolic name.

NOTE: See the use of the existing keyword on line 6 - this means that we are referencing an already existing resource.

Also, read more about how to Reference New Or Existing Resource In Azure Bicep.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// ========== existing-resource-lock.bicep ==========

param storageAccountName string = 'stcontoso'

// Creating symbolic name to an existing resource
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-06-01' existing = {
  name: storageAccountName
}

// Declaring a lock
resource lock 'Microsoft.Authorization/locks@2017-04-01' = {
  name: '${storageAccountName}-lock'
  properties: {
    level: 'CanNotDelete'
    notes: 'Storage account should not be deleted'
  }
  scope: storageAccount  // Passing the scope of the lock
}

Locking Resource Group or Subscription

In this section, we will see how to apply a lock at the resource group or subscription scope. The simplest way to do it is to create a Microsoft.Authorization/locks resource inside of the template where the target scope matches our resource group or subscription.

The examples below show how to lock a resource group and a subscription respectively. A few notes:

Resource Group Lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ========== resourcegroup-lock.bicep ==========

targetScope = 'resourceGroup'  // Default value, so this line could be omitted

// Declare other resources in the template
// resource res1 ..
// resource res2 ..

resource lock 'Microsoft.Authorization/locks@2017-04-01' = {
  name: 'myResourceGroupLock'
  properties: {
    level: 'CanNotDelete'
    notes: 'Lock to prevent resource group and its resources from being deleted'
  }
}

Subscription Lock

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ========== subscription-lock.bicep ==========

targetScope = 'subscription'  // Setting template deployment scope to subscription

// Declare other resources in the template
// resource res1 ..
// resource res2 ..

resource lock 'Microsoft.Authorization/locks@2017-04-01' = {
  name: 'mySubscriptionLock'
  properties: {
    level: 'CanNotDelete'
    notes: 'Lock to prevent subscription and its resources from being deleted'
  }
}

Create a Resource Group and Apply a Lock It

In the Locking Resource Group or Subscription section we covered how to apply a lock to an existing resource group, but what if we don’t have a resource group yet?

Consider the use case where we want to do the following:

  1. Create a resource group using Bicep
  2. Deploy some resources into this new resource group
  3. Add a lock on the resource group level

The code snippet below illustrates how to achieve it, a few notes:

NOTE: Microsoft.Authorization/locks resource itself is declared in the module resourcegroup-lock.bicep and not in the main template.

Read more about how to Create Resource Group With Azure Bicep and Deploy Resources In It.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ========== create-resourcegroup-and-lock.bicep ==========

targetScope = 'subscription'

// Creating a resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: 'rg-bicep'
  location: 'westus2'
}

// This part deploys resources as well as a lock into the resource group
module rgDeployment 'resourcegroup-lock.bicep' = {
  name: 'rgDeployment'
  scope: rg  // Passing newly created resource group as module's deployment scope
}

Multiple Locks For Multiple Resources

Now, let’s discuss how we can deploy multiple resources using for-loop and then add a lock to each resource individually. An alternative approach is to lock the entire resource group which was discussed earlier in Locking Resource Group or Subscription section, though it will lock all resources in that resource group and this could be not what we want.

Let’s assume we want to deploy multiple storage account resources using a loop each with a lock applied to it. The code is annotated with comments to help understand what’s happening, and just a few notes here:

NOTE: An alternative approach is to create a module which deploys one storage account and adds a lock to it. Then we just invoke this module in the main file in a loop. This could be even cleaner solution depending on the use case.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ========== lock-array-of-resources.bicep ==========

// Pass configuration in form of array of objects
param storageAccountConfigs array = [
  {
    name: 'stcontoso1'
    skuName: 'Standard_LRS'
    location: 'eastus'
  }
  {
    name: 'stcontoso2'
    skuName: 'Standard_GRS'
    location: 'westus2'
  }
]

// Create storage account resources using a for-loop
resource storageAccounts 'Microsoft.Storage/storageAccounts@2021-06-01' = [for sa in storageAccountConfigs: {
  name: sa.name
  location: sa.location
  sku: {
    name: sa.skuName
  }
  kind: 'StorageV2'
}]

// Add a lock for each storage account created above
resource locks 'Microsoft.Authorization/locks@2017-04-01' = [for (sa, idx) in storageAccountConfigs: {
  name: '${sa.name}-lock'
  properties: {
    level: 'CanNotDelete'
    notes: 'Storage account should not be deleted'
  }
  scope: storageAccounts[idx]  // Passing the right storage account as a scope for the lock
}]