Assign policy definitions from Azure landing zones Terraform module

A little while back I spent an hour or so writing an Azure policy, only to discover that the Azure landing zones Terraform module already has a policy definition that does exactly what I wanted to accomplish, but no assignments linked to it. It took me another hour of confusion and frustration to figure out how to actually assign the policy, as there is a step I completely overlooked. So here is a quick summary of how to use these policies so you can save yourself the trouble.

Set the library_path

First off we need to make sure the [library_path}( variable is set to make sure the Azure landing zones-module can find our customizations. This is a requirement for adding custom landing zone archetypes, policy definitions, policy assignments etc.

First, create a new folder named lib in the root of your Terraform project. Then, in the, add the following line to your module block:

1module "azure_landing_zones_architecture" {
2    source = "Azure/caf-enterprise_scale/azurerm"
3    version = "3.1.2"
4    library_path = "${path.root}/lib"

Create the policy assignment

Under the lib-folder, create a new folder named policy_assignments and add a new file named policy_assignment_<name of policy>.tf. The Azure landing zones-module will pick up all files prefixed with policy_assignment_, but the rest of the name is only important for your own mental health. Name if something useful and related to what the policy does.

In the example below I use the Deny vNet peering to non-approved vNets-policy to only allow spoke networks to peer with the hub network. You can see a complete list of policy definitions in GitHub

 2    "name": "Restrict_VNet_Peering",
 3    "type": "Microsoft.Authorization/policyAssignments",
 4    "apiVersion": "2019-09-01",
 5    "properties": {
 6        "description": "This policy denies the creation of vNet Peerings to non-approved vNets under the assigned scope",
 7        "displayName": "Deny vNet peering to non-approved vNets",
 8        "notScopes": [],
 9        "parameters": {
10        } ,
11        "policyDefinitionId": "${root_scope_resource_id}/providers/Microsoft.Authorization/policyDefinitions/Deny-VNET-Peering-To-Non-Approved-VNETs",
12        "scope": "${current_scope_resource_id}",
13        "enforcementMode": null,
14        "nonComplianceMessages": [
15        {
16            "message": "VNet peering to non-approved vNets is not allowed"
17        }
18        ]
19    },
20    "location": "${default_location}",
21    "identity": {
22        "type": "SystemAssigned"
23    }

The important lines to change from my example are highlighted in the code block:

# Parameter Description
2 name The name used to reference the policy assignment from an archetype definition or archetype extension. Note that the max length is 24 characters
6 description A short description of what the policy does
7 displayName More human friendly name to make things easier to find the the Azure portal
11 policyDefinitionId The resource ID of the policy definition. A good practice is to keep most definitions in the root scope. Keep ${root_scope_resource_id}/providers/Microsoft.Authorization/policyDefinitions/ and append the string with the name of the policy definition you want to use
16 message The message that will be displayed in the Azure portal if the policy is violated

Assign the policy

Finally, we need to actually assign the policy to a management group. We have two different options to achieve this:

  1. We can extend a built in archetype definition (e.g. “corp” or “online”)
  2. We can create a custom landing zone archetype

In the example below I use the second option and create my own custom landing zone. You can follow the link to get a complete example on how this is created. The only difference from that example is the policy assignment in lib/archetype_definition_customer_online.json:

 2    "customer_online": {
 3      "policy_assignments": ["Restrict_VNet_Peering"],
 4      "policy_definitions": [],
 5      "policy_set_definitions": [],
 6      "role_definitions": [],
 7      "archetype_config": {
 8        "parameters": {
 9        },
10        "access_control": {}
11      }
12    }
13  }

We also have two different options for setting the policy parameters (i.e. the list of allowed virtual networks):

Create the list directly in our archetype definition/extension file:

1"parameters": {
2    "allowedVnets": {
3        "value": [
4            "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-hub/providers/Microsoft.Network/virtualNetworks/vnet-hub"
5        ]
6    }

Pass in the parameters in the terraform module:

 1archetype_config = {
 2    archetype_id = "customer_online"
 3    parameters = {
 4        Restrict_VNet_Peering = {
 5            allowedVnets = [
 7            ]
 8        }
 9    }

I prefer the second method, as it gives me a nice and clean method of dynamically getting the resource ID by using Data Source: azurerm_virtual_network