Monday, March 20, 2023

Auto create DNS records for RSV with policy including region code

 When using private endpoints at scale, the recommended setup from Microsoft is to use Azure Policy to automatically create the DNS records in the central private DNS zones when the private endpoints are created. The reason for this is that users or owners of the spokes or landing zones do not have permissions to create A records in the central private DNS zones in the Hub.

The policies specified by Microsoft work as long as a region code does not have to be specified in the private DNS zone name (which for most of them, see full list here). However, for e.g. Recovery Services Vault for Azure Backup, this is the case.

The zones are region specific, for West Europe it's: and for Sweden Central it's

The default policy only does a check on the subResource (or groupId) value which in this case is: AzureBackup. The result of having two policies (e.g. one for West Europe and one for Sweden Central) running with the same subResource value is that DNS records for the private endpoints are randomly generated in the two private DNS zones.

A fix for this is to add a conditional check in the policy on the location of the private endpoint (PE). This way it is ensured that the DNS records are created in the correct zone that matches the location of the PE.

The full policy is available on Github, see link here.

The main change can be seen below:

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.

Tuesday, November 29, 2022

Azure: Bulk update secrets in Azure key vault using ARM

 At current client we have a requirement to periodically rotate some 50 secrets in Azure key vaults that are used for storing the shared keys for S2S VPN connections.

For keys (not secrets) there is an auto rotation option that is now GA, see link, however it doesn't apply to secrets.

There's an option to use logic apps to rotate secrets but the MS documentation I could find is not very good.

At first I was looking to write a PowerShell script that pulls the new secrets from a csv file and then applies them to a key vault, but it ended up being easier just creating an ARM template that does the same.

The secret names and secret values are stored in the parameters file (in the file there are a couple of test secrets for demonstration purposes). The name of the KV should also be updated in the parameters file. The ARM template itself does not have to be changed.

If you add a secret in the param file that already exists in the KV, it will overwrite the existing secret and add a new/current version and keep the previous version(s) as Older versions, see screenshot below. If you have secrets already in the KV that are not defined in the param file, these secrets will not be changed or deleted. And if you add new secrets in the param file that are not in the KV currently, they will be added.

The template is available on Github, the files are:



Powershell cmd to run ARM template.txt

Monday, July 11, 2022

Azure Firewall with Azure policy and IP groups using Bicep

 In the previous post I deployed a firewall with some additional components such as VMs, NSGs, and route tables to have a working test setup. In this post it will mainly be the FW, the FW policies and the IP groups that will be deployed.

This post is based on the this Quickstart template with only a few modifications. The main reason I do this is to verify that the files work in my environment to inspect deployed resources.

I have uploaded the modified files to Github (fw-with-policy-001.bicep and fw-with-policy-001.parameters.json). 

The resources deployed can be seen below:

The two IP groups are used in combination with the policy. The policy is associated with the firewall. The are two rule collection groups defined with a total of three rules, see below two screenshots: