Now, denying unwanted configurations is fine and can be a great help, but would it not be much better if we could automatically fix unwanted configurations when they are deployed? Yes.., this has pros and cons. Automagically correcting errors is not always the best way forward, as there is not really a learning curve for the team member deploying the unwanted configuration. On the other hand, if you can fix it automatically, why not? Just weigh your options on a I guess.
Let’s take an example from the database world. Let’s say we have a requirement that says that we want to have an IP address added to the firewall of every single database server in our subscription. The policy that would allow us to specify an IP address to add to the firewall of every database server is as follows:
{ "if": { "field": "type", "equals": "Microsoft.Sql/servers" }, "then": { "effect": "DeployIfNotExists", "details": { "type": "Microsoft.Sql/servers/firewallrules", "name": "AllowAccessForExampleIp", "existenceCondition": { "allOf": [ { "field": "Microsoft.Sql/servers/firewallRules/startIpAddress", "equals": "[parameters('ipAddress')]" }, { "field": "Microsoft.Sql/servers/firewallrules/endIpAddress", "equals": "[parameters('ipAddress')]" } ] }, "deployment": { "properties": { "mode": "incremental", "template": { "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "serverName": { "type": "string" }, "ipAddress": { "type": "string" } }, "resources": [ { "name": "[concat(parameters('serverName'), '/AllowAccessForExampleIp')]", "type": "Microsoft.Sql/servers/firewallrules", "apiVersion": "2014-04-01", "properties": { "startIpAddress": "[parameters('ipAddress')]", "endIpAddress": "[parameters('ipAddress')]" } } ] }, "parameters": { "serverName": { "value": "[field('name')]" }, "ipAddress": { "value": "[parameters('ipAddress')]" } } } }, "roleDefinitionIds": [ "/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c" ] } } }
Quite the JSON. Let’s walk through this step by step. First of all we have the conditions under which this policy must apply, this whenever we are deploying something of type Microsoft.Sql/servers. The effect we are looking for is deployIfNotExists, which will do an additional ARM template deployment when ever the existenceCondition is not fulfilled. This template takes the same form as any nested template, which means we have to respecify all parameters to the template and provide them from the parameters of the policy or using field values.
Managed Identity for template deployment
Every ARM template deployment is done on behalf of an authenticated identity. When using any effect that causes another deployment, you have to create a Managed Identity when assigning the policy. In the policy property roleDefinitionIds you should list all roles that are needed to deploy the defined template. When assigning and executing this policy to a subscription or resourcegroup, Azure will automatically provision a service principal with these roles over the correct scope, which will be used to deploy the specified template.
Field() function and aliases
In the template itself (and also when passing parameters to the template), there is the usage of a function called field. With this function you can reference one or more properties of the resource that is triggering the policy. To see all available fields per resource, use the Get-AzureRmPolicyAlias Powershell command. This will provide a list of all aliases available. To filter this list by namespace, you can use:
Use Get-AzureRmPolicyAlias -ListAvailable | Where-Object -equals -Property Namespace -Value Microsoft.Sql
Policy in action
After creating and assigning this policy, we can see it in action by creating a SQL Server using the portal. This is just another interface for creating a template deployment. After successfully deploying this template, our policy will be evaluated and the first time it will create the intended firewall rule. We can see this when looking up all deployments to the resource group, which will also list a PolicyDeployment:
Next to that, when looking at the related events for the initial Microsoft.SQLServer deployment, we see that our policy is accepted for deployment after the initial deployment: