Tuesday, February 14, 2023

Azure: Adding a resource lock via ARM template

 Resource locks can be added on Azure resources to prevent unintended deletion or unwanted changes. There are two types of locks: 1) Do not delete (where you can make updates/changes) and 2) Read only (where you can neither change nor delete), see more info here.

It can make sense to add locks to critical infrastructure resources, but note that it also comes with additional management overhead and some caveats, see link above.

Locks can be added to either a resource or a resource group.

In the example in this post, we'll look at a read only lock for a specific resource and how to add this to an ARM template.

It's fairly simple to add. The lock is a separate resource, an ExpressRoute circuit, but the principle is the same for all resources.

The lock resource itself is as follows:

    "condition": "[parameters('enableReadOnlyLock')]",
    "type": "Microsoft.Authorization/locks",
    "apiVersion": "2020-05-01",
    "name": "ER circuit lock",
    "scope": "[concat('Microsoft.Network/expressRouteCircuits/', parameters('circuitName'))]",
    "dependsOn": [
        "[resourceId('Microsoft.Network/expressRouteCircuits',    parameters('circuitName'))]"
    "properties": {
    "level": "ReadOnly",
    "notes": "ER circuit should not be updated or deleted"

A condition is added which can be set to true or false, this way it easy to disable the lock if required. The parameter is declared at the top of the ARM template:

"enableReadOnlyLock": {
    "type": "bool",
    "defaultValue": false,
    "metadata": {
    "description": "Determines if the resources should be locked to prevent changes or deletion."

The full template including parameters file is uploaded to GitHub, see below:

Updating ExpressRoute Circuit fails with error: Peering/Circuit was recently updated by service provider

 Recently I had to make some minor updates to an ExpressRoute Circuit via Azure DevOps. The change was to apply a read-only resource lock via code so no actual changes were introduced to the circuit configuration itself. Still, when running the pipeline it fails showing the error below:

"Error: The Peering/Circuit was recently updated by the service provider and is not currently in sync" (see screenshot below).

Looking in the portal after this failed push, the ExpressRoute (ER) circuit was in failed state and the error message suggests to refresh the circuit, see screenshot below. When clicking refresh, the status goes back into green. Note that even if the portal shows an error, the circuit itself did not drop any traffic (which is good as this is a pretty critical component).

This particular ER circuit has a private peering which has been configured by the service provider (this can either be done by the customer or by the service provider). However, we knew that the service provider had not recently updated the circuit so it did not make much sense.

We raised a ticket to Microsoft and they suggested to specify the "gatewayManagerEtag" in the code, see more info here. I didn't find much info around this parameter other than it's a string and supposedly it can be used to ensure that the service provider or the customer don't accidentally override a common setting with an older value. To identify the current value I went to the portal and exported the ARM template for the ER circuit (the value was 7).

After specifying this parameter, the push went through with no problems. And at the same time, we specified the private peering configuration info in the ARM template, which was not there from the beginning (as it had been set by the service provider). Additionally, we tried updating another ER circuit where we ourself had initially configured the private peering in the ARM template and where the gatewayManagerEtag value was empty or just showing as "". This also works fine and we included this on all circuits for consistency.

I've uploaded an example ARM template to GitHub which includes private peering configuration, the gatewayManagerEtag and a read-only resource lock, see links below (remember to update the content of the parameters file):



Friday, February 3, 2023

PowerShell command to deploy ARM templates

 This is just a quick note on the PowerShell command to deploy ARM templates (I usually push the templates via pipelines so one tend to forget :))

Command should be run from the location where the ARM templates are located (or update the file path).

New-AzResourceGroupDeployment -Name deploy_some_storage_account -ResourceGroupName rg-some_resource_group-001 `

  -TemplateFile .\deploy_storage_account.json `

  -TemplateParameterFile .\deploy_storage_account.parameters.json

There is another example here for deploying a key vault.

And here's link to the Microsoft documentation.

Deploy an Azure Firewall with no public IP for data plane with ARM (and forced tunneling)

 At current client we have multiple Azure Firewalls running with forced tunneling to on-prem. By default this requires two public IP addresses, one for the data plane (or for customer traffic) and the other other is for service management traffic (exclusively used by the Azure platform).

Since there is no ingress or egress to the internet on these firewalls as all traffic is routed to on-prem, there was a request from the security team to remove the public IP for the data plane (so there will just be one public IP left per firewall).

If deploying from the portal or via PowerShell, this cannot be configured during deployment, but can be done post deployment by going the Azure Firewall -> Public IP configuration -> Choose the three dots to the right of the public IP -> choose Edit -> And then choose None so that no public IP is associated., see screenshot below for example.

Note that you cannot choose to delete the public IP directly, and the other option to Manage Public IP also cannot be used to disassociate the public IP (which is perhaps a bit confusing).

Deploy with ARM template

If deploying the firewall using an ARM template, it is possible to configure just one public IP at the time of deployment.

I've put an example template on GitHub that can be used for reference:



The following resources should be deployed in advance:

  • A resource group
  • A VNet
  • 2 x subnets (AzureFirewallSubnet and AzureFirewallManagementSubnet, both should be /26 in size)
  • A firewall policy

References to the subnets and the policy should be updated in the parameters file before running it.

On the second screenshot below you can see the result post deployment and that the FW has no firewall public IP but it does have one for management traffic. On the first screenshot below you can that there is an entry for the public IP called "DoesNotExist" (just an example name) but it is not associated with a public IP.