In Part 1, we saw how we can deploy custom policies and initiatives dynamically controlled by a JSON file. In Part 2, we will look at policy and initiative assignments. Until a policy is assigned, it doesn’t have any impact on the creation or modification of resources.
Azure Policies can be assigned to management groups, subscriptions, or resource groups. The policy definition itself must be placed at a level that encompasses all the policy assignment scopes, usually at a top-level management group. Even though policies can be assigned to resource groups, it is recommended that policies be assigned to management groups or subscriptions, as it would make policy governance easier. The lower levels would just inherit the policy assignments from the higher levels. The same goes for initiative assignments. In my implementation, I have included policy assignments at the resource group level for testing purposes.
The assignments.json file
The assignments are listed in the assignments.json file. Assignments are listed as an array, with each one specified as either a policy or an initiative assignment by using the type property. The policy_name and initiative_name properties must match the names in the policies.json file exactly.
{
"assignments": [
{
"name": "enforce-location-sub-targetsub",
"type": "policy",
"policy_name": "allowedlocations",
"management_group_name": "",
"subscription_id": "<subscription id>",
"resource_group_name": "",
"parameters": {
"allowedLocations": {
"value": [
"australiaeast"
]
}
}
},
{
"name": "location-initiative-mg",
"type": "initiative",
"initiative_name": "LocationInitiative",
"management_group_name": "<mgmt group name>",
"subscription_id": "",
"resource_group_name": "",
"parameters": {
"locations": {
"value": [
"australiaeast"
]
}
}
},
{
"name": "namingtagging-initiative-sub",
"type": "initiative",
"initiative_name": "TaggingNamingInitiative",
"management_group_name": "",
"subscription_id": "<subscription id>",
"resource_group_name": "",
"parameters": {
"costcentreTagName": {
"value": "costcentre"
},
"costcentreTagValues": {
"value": [
"finance",
"engineering"
]
},
"environmentTagName": {
"value": "environment"
},
"environmentTagValues": {
"value": [
"production",
"development",
"testing"
]
},
"namingPattern": {
"value": "projectxyz-dev-*-vm"
}
}
}
]
}
If the management_group_name property of an assignment is not empty, then the policy will be assigned at the management group level. If the resource_group_name property is empty and the subscription id is not empty, then the policy will be assigned at the subscription level. Finally, if the resource_group_name property is not empty, then the policy will be assigned at the resource group level.
Passing parameters
Care should be taken while passing parameters to initiatives. The parameters listed in the assignments.json file are the initiative parameters and not the policy parameters. The initiative definition will take of mapping the initiative parameters to the correct policy parameters.
main.tf file changes
Here are the new additions to the locals.
assignment_data = jsondecode(file("${path.root}/policies/assignments.json"))
# Filter assignments for subscriptions
policy_subscription_assignments = {
for assignment in local.assignment_data.assignments :
assignment.name => assignment
if assignment.type == "policy"
&& assignment.resource_group_name == ""
&& assignment.management_group_name == ""
&& assignment.subscription_id != ""
}
# Filter assignments for resource groups
policy_resource_group_assignments = {
for assignment in local.assignment_data.assignments :
assignment.name => assignment
if assignment.type == "policy"
&& assignment.resource_group_name != ""
&& assignment.subscription_id != ""
}
# Filter assignments for management groups
policy_management_group_assignments = {
for assignment in local.assignment_data.assignments :
assignment.name => assignment
if assignment.type == "policy"
&& assignment.management_group_name != ""
}
# Filter initiative assignments for management groups
initiative_management_group_assignments = {
for assignment in local.assignment_data.assignments :
assignment.name => assignment
if assignment.type == "initiative"
&& assignment.management_group_name != ""
}
# Filter initiative assignments for subscriptions
initiative_subscription_assignments = {
for assignment in local.assignment_data.assignments :
assignment.name => assignment
if assignment.type == "initiative"
&& assignment.resource_group_name == ""
&& assignment.management_group_name == ""
&& assignment.subscription_id != ""
}
The following are the data blocks required for the assignments.
data "azurerm_management_group" "assignment_management_groups" {
for_each = toset([for assignment in local.assignment_data.assignments :
assignment.management_group_name if assignment.management_group_name != ""])
name = each.value
}
data "azurerm_resource_group" "assignment_resource_groups" {
for_each = toset([for assignment in local.assignment_data.assignments :
assignment.resource_group_name if assignment.resource_group_name != ""])
name = each.value
}
data "azurerm_management_group" "initiative_assignment_management_groups" {
for_each = toset([for assignment in local.initiative_management_group_assignments :
assignment.management_group_name if assignment.management_group_name != ""])
name = each.value
}
Shown below are the resource blocks for policy and initiative assignments. Each scope of assignment has a different Terraform resource type. The policy_definition_id is set using the azurerm_policy_definition.custom_policy.custom_policy resource, as detailed in my previous post, as it contains the IDs of the custom policies deployed.
# Create management group-level policy assignments
resource "azurerm_management_group_policy_assignment" "management_group_assignments" {
for_each = local.policy_management_group_assignments
name = substr("Assignment-${each.value.policy_name}-mg", 0, 24)
management_group_id = data.azurerm_management_group.assignment_management_groups[each.value.management_group_name].id
policy_definition_id = azurerm_policy_definition.custom_policy[each.value.policy_name].id
display_name = "Assignment for ${each.value.policy_name}"
parameters = jsonencode(lookup(each.value, "parameters", {}))
}
# Create subscription-level policy assignments
resource "azurerm_subscription_policy_assignment" "subscription_assignments" {
for_each = local.policy_subscription_assignments
name = substr("Assignment-${each.value.policy_name}-sub", 0, 24)
subscription_id = "/subscriptions/${each.value.subscription_id}"
policy_definition_id = azurerm_policy_definition.custom_policy[each.value.policy_name].id
display_name = "Assignment for ${each.value.policy_name}"
parameters = jsonencode(lookup(each.value, "parameters", {}))
}
# Create resource group-level policy assignments
resource "azurerm_resource_group_policy_assignment" "resource_group_assignments" {
for_each = local.policy_resource_group_assignments
name = substr("Assignment-${each.value.policy_name}-rg", 0, 24)
resource_group_id = data.azurerm_resource_group.assignment_resource_groups[each.value.resource_group_name].id
policy_definition_id = azurerm_policy_definition.custom_policy[each.value.policy_name].id
display_name = "Assignment for ${each.value.policy_name}"
parameters = jsonencode(lookup(each.value, "parameters", {}))
}
# Create management group-level initiative assignments
resource "azurerm_management_group_policy_assignment" "initiative_managementgroup_assignments" {
for_each = local.initiative_management_group_assignments
name = substr("Assignment-${each.value.initiative_name}-mg", 0, 24)
management_group_id = data.azurerm_management_group.initiative_assignment_management_groups[each.value.management_group_name].id
policy_definition_id = azurerm_management_group_policy_set_definition.custom_initiative[each.value.initiative_name].id
display_name = "Assignment for ${each.value.initiative_name}"
parameters = jsonencode(lookup(each.value, "parameters", {}))
}
# Create subscription-level initiative assignments
resource "azurerm_subscription_policy_assignment" "initiative_subscription_assignments" {
for_each = local.initiative_subscription_assignments
name = substr("Assignment-${each.value.initiative_name}-sub", 0, 24)
subscription_id = "/subscriptions/${each.value.subscription_id}"
policy_definition_id = azurerm_management_group_policy_set_definition.custom_initiative[each.value.initiative_name].id
display_name = "Assignment for ${each.value.initiative_name}"
parameters = jsonencode(lookup(each.value, "parameters", {}))
}
Result
Applying the Terraform file will result in all the assignments being deployed to Azure. Here are the screenshots of the assignments at the subscription level.


The above image shows the parameters of the TagginNamingInitiative at the subscription level. After you deploy the assignments, check what is displayed under the “Policy assignment parameter reference type”. It will display “User defined parameter” if you are passing the parameters correctly. If not set correctly, the default value will be used.
Here is a screenshot of the management group scope assignment.

As with policy and initiative definitions, adding more assignments is as simple as modifying the assignments.json file. I’ll cover policy exemptions and remediations in a future post.
References
Resource: azurerm_management_group_policy_assignment