Daniel's Tech Blog

Cloud Computing, Cloud Native & Kubernetes

How to not block Terraform with Azure resource locks

Azure resource locks are an essential building block protecting Azure resources from accidental deletion or modifications.

In today’s blog post, I show you how to use Azure resource locks to protect your Azure resources and how to not block your Terraform infrastructure as code processes.

Common setup and the Terraform issue

Resources in Azure inherit the resource lock from their parent resource. Therefore, in most setups, a resource lock is created either on the resource group or the resource itself. In such a setup, you cannot leverage Terraform to its fullest, as delete operations are blocked by the resource lock. Which is intended as we want to prevent accidental deletions.

The following example shows the setup for a resource lock on an Azure DNS zone where we can only add DNS records. But we cannot delete DNS records that we do not need anymore.

resource "azurerm_resource_group" "rg" {
  name     = "resource-locks"
  location = "northeurope"
}

resource "azurerm_dns_zone" "zone" {
  name                = "locks.local"
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_management_lock" "rg" {
  name       = "resource-group-lock"
  scope      = azurerm_resource_group.rg.id
  lock_level = "CanNotDelete"
}

resource "azurerm_dns_cname_record" "cname" {
  name                = "test"
  zone_name           = azurerm_dns_zone.zone.name
  resource_group_name = azurerm_resource_group.rg.name
  ttl                 = 300
  record              = "locks.local"
}

Azure portal - resource lock on resource group

When we try to delete the record, we retrieve the following error message.

resource "azurerm_resource_group" "rg" {
  name     = "resource-locks"
  location = "northeurope"
}

resource "azurerm_dns_zone" "zone" {
  name                = "locks.local"
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_management_lock" "rg" {
  name       = "resource-group-lock"
  scope      = azurerm_resource_group.rg.id
  lock_level = "CanNotDelete"
}

# resource "azurerm_dns_cname_record" "cname" {
#   name                = "test"
#   zone_name           = azurerm_dns_zone.zone.name
#   resource_group_name = azurerm_resource_group.rg.name
#   ttl                 = 300
#   record              = "locks.local"
# }
Error: deleting Record Type (
  Subscription: "<subscription_id>"
  Resource Group Name: "resource-locks"
  Dns Zone Name: "locks.local"
  Record Type: "CNAME"
  Relative Record Set Name: "test"
): unexpected status 409 with error: ScopeLocked: The scope '/subscriptions/<subscription_id>/resourceGroups/resource-locks/providers/Microsoft.Network/dnsZones/locks.local/CNAME/test' cannot perform delete operation because following scope(s) are locked: '/subscriptions/<subscription_id>/resourceGroups/resource-locks'. Please remove the lock and try again.

How can we use Terraform to its fullest and still protect the Azure resource and the resource group from accidental deletion?

Using resource locks on child resources

The solution is quite simple: moving the resource lock down the inheritance chain to the end. Instead of putting the resource lock on the resource group or parent resource level, we set it on the child resource.

Understandably, why should this protect the resource group and the parent resource from accidental deletion?

It has something to do with how resource deletion works in Azure. Before a resource group and all its containing child resources are deleted a check is done, if a resource lock exists or not. When a resource lock exists, the deletion gets canceled, and an error message is returned like the one below.

Azure portal - resources in resource group

❯ az group delete --name resource-locks --yes
(ScopeLocked) The scope '/subscriptions/<subscription_id>/resourcegroups/resource-locks' cannot perform delete operation because following scope(s) are locked: '/subscriptions/<subscription_id>/resourceGroups/resource-locks/providers/Microsoft.Network/dnsZones/locks.local/TXT/delete-protected'. Please remove the lock and try again.
Code: ScopeLocked
Message: The scope '/subscriptions/<subscription_id>/resourcegroups/resource-locks' cannot perform delete operation because following scope(s) are locked: '/subscriptions/<subscription_id>/resourceGroups/resource-locks/providers/Microsoft.Network/dnsZones/locks.local/TXT/delete-protected'. Please remove the lock and try again.

It is only applicable to the Azure portal, Azure CLI, etc. When you use Terraform’s destroy option, Terraform deletes the resources according to its dependency tree.

That brings us to the point I have not clarified at the beginning. Accidental deletion means manual resource deletion by a human individual using the Azure portal or Azure CLI.

Unblock Terraform infrastructure as code processes

Let us start to unblock Terraform by using resource locks on child resources. Below is our adjusted example where we set the resource lock on a dummy DNS record instead of the resource group.

resource "azurerm_resource_group" "rg" {
  name     = "resource-locks"
  location = "northeurope"
}

resource "azurerm_dns_zone" "zone" {
  name                = "locks.local"
  resource_group_name = azurerm_resource_group.rg.name
}

resource "azurerm_dns_cname_record" "cname" {
  name                = "test"
  zone_name           = azurerm_dns_zone.zone.name
  resource_group_name = azurerm_resource_group.rg.name
  ttl                 = 300
  record              = "locks.local"
}

resource "azurerm_dns_txt_record" "txt" {
  name                = "delete-protected"
  zone_name           = azurerm_dns_zone.zone.name
  resource_group_name = azurerm_resource_group.rg.name
  ttl                 = 300

  record {
    value = "delete-protected"
  }
}

resource "azurerm_management_lock" "rg" {
  name       = "child-resource-lock"
  scope      = azurerm_dns_txt_record.txt.id
  lock_level = "CanNotDelete"
}

Azure portal - resource lock on child resource

Now we can delete the DNS record with Terraform without being blocked by the resource lock and still protecting the DNS zone from accidental deletion.

resource "azurerm_resource_group" "rg" {
  name     = "resource-locks"
  location = "northeurope"
}

resource "azurerm_dns_zone" "zone" {
  name                = "locks.local"
  resource_group_name = azurerm_resource_group.rg.name
}

# resource "azurerm_dns_cname_record" "cname" {
#   name                = "test"
#   zone_name           = azurerm_dns_zone.zone.name
#   resource_group_name = azurerm_resource_group.rg.name
#   ttl                 = 300
#   record              = "locks.local"
# }

resource "azurerm_dns_txt_record" "txt" {
  name                = "delete-protected"
  zone_name           = azurerm_dns_zone.zone.name
  resource_group_name = azurerm_resource_group.rg.name
  ttl                 = 300

  record {
    value = "delete-protected"
  }
}

resource "azurerm_management_lock" "rg" {
  name       = "child-resource-lock"
  scope      = azurerm_dns_txt_record.txt.id
  lock_level = "CanNotDelete"
}
Plan: 0 to add, 0 to change, 1 to destroy.
azurerm_dns_cname_record.cname: Destroying... [id=/subscriptions/<subscription_id>/resourceGroups/resource-locks/providers/Microsoft.Network/dnsZones/locks.local/CNAME/test]
azurerm_dns_cname_record.cname: Destruction complete after 1s

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

Azure portal - DNS zone deletion denied

The Azure DNS zone was only one example. Many Azure resources have child resources like Azure Database for PostgreSQL or Azure Data Explorer, where we can use this approach.

Azure resources without child resources still get the resource lock directly, for instance, an Azure Log Analytics workspace.

Summary

Unblocking Terraform requires a change in your resource lock design by moving them down the inheritance chain onto child resources.

Whether you do it or not depends on your strategy. The benefit of doing so is using Terraform to its fullest and protecting parent resources from accidental deletion.

WordPress Cookie Notice by Real Cookie Banner