Manage resource lock in Azure using Terraform

Manage resource lock in Azure using Terraform
Photo by FLY:D / Unsplash

Resource lock in Azure

In Azure, an administrator can lock a subscription, resource group or resource to prevent other users from deleting or modifying critical resources. The lock overrides any permissions the user might have.

There are two lock levels :

  • CanNotDelete means authorized users can still read and modify a resource, but they can't delete the resource.
  • ReadOnly means authorized users can read a resource, but they can't delete or update the resource. Applying this lock is similar to restricting all authorized users to the permissions granted by the Reader role.

Locks can be really interesting to protect critical resource like : gateway, firewall, etc...

If you are using Infrastructure as Code, you want probably managed the lock with your favorite tool. We will see how to do this using Terraform.

Resource lock in Azure using Terraform

Put a lock on an Azure resource using Terraform is really easy.
Configure a CanNotDelete lock can be done like this :

resource "azurerm_resource_group" "resource-group" {
  name     = "my-resource-group"
  location = "West Europe"
}

resource "azurerm_public_ip" "public-ip" {
  name                    = "locked-publicip"
  location                = azurerm_resource_group.resource-group.location
  resource_group_name     = azurerm_resource_group.resource-group.name
  allocation_method       = "Static"
}

resource "azurerm_management_lock" "lock-public-ip" {
  name       = "lock-public-ip"
  scope      = azurerm_public_ip.public-ip.id
  lock_level = "CanNotDelete"
  notes      = "Lock managed by Terraform"
}

Configure a ReadOnly lock is exactly the same :

resource "azurerm_management_lock" "lock-public-ip" {
  name       = "lock-public-ip"
  scope      = azurerm_public_ip.public-ip.id
  lock_level = "ReadOnly"
  notes      = "Lock managed by Terraform"
}

As you can see configure lock is not a big deal. The real problem is how to manage it.

CanNotDelete lock can be directly managed on the code with no problem. But as soon as you have configure ReadOnly lock, it means even Terraform will not be able to modify the resource :

│
│ Error: creating/updating Public Ip Address: (Name "locked-publicip" / Resource Group "my-resource-group"): network.PublicIPAddressesClient#CreateOrUpdate: Failure sending request: StatusCode=0 -- Original Error: autorest/azure: Service returned an error. Status=<nil> Code="ScopeLocked" Message="The scope '/subscriptions/0000-0000-0000-0000-0000/resourceGroups/my-resource-group/providers/Microsoft.Network/publicIPAddresses/locked-publicip' cannot perform write operation because following scope(s) are locked: '/subscriptions/0000-0000-0000-0000-0000/resourceGroups/my-resource-group/providers/Microsoft.Network/publicIPAddresses/locked-publicip'. Please remove the lock and try again."
│

Lock management using Terrafom in a dedicated stack

One solution to manage lock is to have a dedicated Terraform stack only for the lock management. Each time you want to update your resource you will have to destroy the lock and apply it back after the modification.

data "azurerm_public_ip" "existing-pip" {
  name                = "existing-public_ip"
  resource_group_name = "my-resource-group"
}

resource "azurerm_management_lock" "lock-public-ip" {
  name       = "lock-public-ip"
  scope      = data.azurerm_public_ip.existing-pip.id
  lock_level = "ReadOnly"
  notes      = "Lock managed by Terraform"
}

It's not a perfect solution but it can be fine if your locked resources are not changed very often.

Lock management using Terrafom in the code

Another solution is to use a trick to force the lock recreation each time (and if necessary using dependency management) you can manage the lock of your resource in a single file :

resource "azurerm_public_ip" "public-ip" {
  name                    = "locked-publicip"
  location                = azurerm_resource_group.resource-group.location
  resource_group_name     = azurerm_resource_group.resource-group.name
  allocation_method       = "Static"
}

resource "random_id" "rng" {
  keepers = {
    id = "${timestamp()}"
  }
  byte_length = 8
}

resource "azurerm_management_lock" "lock-public-ip" {
  name       = "automatic lock ${random_id.rng.hex}"
  scope      = azurerm_public_ip.public-ip.id
  lock_level = "ReadOnly"
  notes      = "Lock managed by Terraform"
}

Using random resource, you will generate a random ID based on timestamp so it will be recreated after each terraform apply. As this random ID part of the lock name, a new lock will recreated each time :

azurerm_management_lock.lock-public-ip: Destroying...
azurerm_management_lock.lock-public-ip: Destruction complete after 0s
random_id.rng: Destroying... [id=tvNiNbVIouk]
random_id.rng: Destruction complete after 0s
random_id.rng: Creating...
azurerm_public_ip.public-ip: Modifying... 
random_id.rng: Creation complete after 0s [id=pytmHXbFoFE]
azurerm_public_ip.public-ip: Modifications complete after 2s 
azurerm_management_lock.lock-public-ip: Creating...
azurerm_management_lock.lock-public-ip: Creation complete after 1s 

Apply complete! Resources: 2 added, 1 changed, 2 destroyed.

It's not a perfect solution because the lock will be recreated on every terraform apply but I think it's an acceptable solution to manage ReadOnly lock in a single Terraform code.

Conclusion

I hope you enjoy this small hack to manage lock inside your Terraform code. If you have any other solution, you hesitate to share it with me.

Lock resources to prevent changes - Azure Resource Manager
Prevent users from updating or deleting Azure resources by applying a lock for all users and roles.

Terraform azurerm_management_lock : https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/management_lock

Terraform random_id : https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id