This post describes an outdated experience of authoring Azure Policy with ARM templates. For a new approach, check out my post on “How to deploy Azure Policy with Bicep.”
When creating custom Azure Policy definitions and assignments for them, basically, there are a few options for doing this programmatically:
- using the REST API;
- running the PowerShell cmdlets;
- executing the Azure CLI commands;
- defining them in ARM templates.
Let’s make a brief overview of them.
If I were you, I would consider using Azure REST API as a fallback option when there are no other ways to interact with an Azure resource. I’m not a big fan of it because I see it as a low-level interface that requires more effort to use and maintain in automation solutions.
Regarding Azure PowerShell and Azure CLI for working Azure Policies, I feel somewhat confused about the implementation of their cmdlets and commands. For example, ‘New-AzPolicyDefinition’ cmdlet requires you to provide policy rules and policy parameters as separate files and also to specify policy name, display name, description and metadata as cmdlet input parameters. So, instead of keeping all information defining an Azure Policy in one place, you have to synchronize it among multiple files.
Azure CLI commands resemble the same usage pattern that introduces duplication in the contribution process. This, in turn, makes automated deployments for Azure Policies a mixture of data and logic when using the out-of-the-box tools.
To make your deployment pipeline more organized, you might look for creating a custom deployment scripts to parse all required information from a single ‘azurepolicy.json’ file or describe your Azure Policy definitions and assignments in an ARM template, which is my choice for consistent deployment approach in Azure.
Note: At the time of writing, it is not possible to use ARM templates to deploy resources on a Management Group level. Use Azure PowerShell or Azure CLI options for that.
How to define policy definitions in ARM templates
Unfortunately, the documentation about defining Azure Policy resources in ARM templates is not very descriptive about the technical aspects of this approach: just a short example of defining a policy definition in a template, which, for an unknown reason, is located in a completely different section of the documentation. So, what to pay attention to?
Firstly, when creating ARM templates with Azure Policy definitions, use the schema for subscription-level deployment:
Also, to deploy the subscription-level templates, if using Azure PowerShell, use ‘New-AzDeployment’ cmdlet instead of ‘New-AzResourceGroupDeployment.’
Secondly, if your custom policy definition requires input parameters or uses policy functions, such as ‘concat,’ for instance, use escape characters for them in policy definition body, so they are not invoked during the template deployment and just passed through as parts of the policy definition:
Thirdly, remember that you can use strongTypes to validate the input parameters of your policy.
And lastly, don’t forget to specify an ‘apiVersion’ for ‘Microsoft.Authorization/policyDefinitions’ resource provider in your template. This property is not required in the bare ‘azurepolicy.json’ format, but it is needed for Azure Resource Manager to ‘talk’ to a specific API version.
A sample Azure Policy definition in an ARM template:
How to define policy assignments in ARM templates
If you were able to define your Azure Policy definition in an ARM template and successfully deploy it, then creating a policy assignment will be a piece of cake. There is no need to use escape characters as for policy definitions – policy assignments can be treated as regular ARM resources. However, to pass the policy parameters, you should define them as an object input both in the template and parameter file (see ‘Use an object as a parameter in an Azure Resource Manager template’ for details):
Also, there is one important aspect to be aware of. When creating Azure Policy assignments on the portal, you can limit the assignment scope to a specific resource group, which might be especially handy for testing policy effects before deploying it to production. To achieve the same effect in an ARM template, you can you specify ‘scope’ property in the following format:
and stumble at the following non-descriptive error:
“The policy assignment <policy_definition_name> create request is invalid. Policy assignment scope ‘/subscriptions/<subscription_id>/resourceGroups/<resourcegroup_name>’ must match the scope specified on the Uri ‘/subscriptions/<subscription_id>’.”
If you expand the scope back to the subscription level, the deployment completes without errors.
So, the trick here is to:
- use a regular deployment schema like ‘https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#’,
- deploy the ARM template with policy assignment with ‘New-AzResourceGroupDeployment’ cmdlet when scoping for a specific resource group and ‘New-AzDeployment’ when targeting a subscription.
In terms of automation, you can create a single unified deployment template that takes policy definition and its required parameters as inputs and separate parameter files for each policy assignment:
Deployment scripts for Azure Policy definitions and assignments
Now, when you have your ARM templates for policy definitions and assignments ready, it is time to deploy them.
Even though it is possible to define Azure Policy definitions and assignments in the same template file, I prefer to make the deployment a two-step process: deploy policy definitions first, then create assignments for them. The reason for that is flexibility. I create a policy definition on the subscription level but want to create an assignment for it limited to a resource group.
If you structure your repository for policy definitions according to the official recommendations, you might use the following Azure PowerShell script to automate their deployment:
Regarding the deploying templates with policy assignment, you might consider a more dynamic approach:
Depending on the environment you are creating Azure Policy assignments in, you can construct input parameters for the sample ARM template mentioned in the previous section on the fly. The Azure PowerShell deployment cmdlets can take hash tables as template parameter inputs, so you can specify your environment-specific parameters as Azure DevOps pipeline variables and add them during the deployment. Doing so can help you to reduce the number of duplicate parameter files for different environments in your repository.
Have you tried to create a deployment pipeline for Azure Policies? Share your experience in the comments!