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 main.tf-file, add the following line to your module block:

module "azure_landing_zones_architecture" { 
  source = "Azure/caf-enterprise_scale/azurerm"
  version = "3.1.2"
  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

{
    "name": "Restrict_VNet_Peering",
    "type": "Microsoft.Authorization/policyAssignments",
    "apiVersion": "2019-09-01",
    "properties": {
        "description": "This policy denies the creation of vNet Peerings to non-approved vNets under the assigned scope",
        "displayName": "Deny vNet peering to non-approved vNets",
        "notScopes": [],
        "parameters": {},
        "policyDefinitionId": "${root_scope_resource_id}/providers/Microsoft.Authorization/policyDefinitions/Deny-VNET-Peering-To-Non-Approved-VNETs",
        "scope": "${current_scope_resource_id}",
        "enforcementMode": null,
        "nonComplianceMessages": [
            {
                "message": "VNet peering to non-approved vNets is not allowed"
            }
        ]
    },
    "location": "${default_location}",
    "identity": {
        "type": "SystemAssigned"
    }
}

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

  • name: The name used to reference the policy assignment from an archetype definition or archetype extension. Note that the max length is 24 characters
  • description: A short description of what the policy does
  • displayName: More human friendly name to make things easier to find the the Azure portal
  • 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
  • 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:

{
    "customer_online": {
        "policy_assignments": [
            "Restrict_VNet_Peering"
        ],
        "policy_definitions": [],
        "policy_set_definitions": [],
        "role_definitions": [],
        "archetype_config": {
            "parameters": {},
            "access_control": {}
        }
    }
}

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

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

Pass in the parameters in the terraform module

archetype_config = {
  archetype_id = "customer_online"
  parameters = {
    Restrict_VNet_Peering = {
      allowedVnets = [
        data.azurerm_virtual_network.hub.id
      ]
    }
  }
}