Azure Policy Exemption on Management Group using Terraform.

Problem

To assign an Azure policy exception in Terraform using azurerm_resource_group_policy_exemption you need to specify the scope. This works fine if you specify a Resource Group ID, but will fail when you specify the scope as a Management Group ID. The Management Group ID (for example corp) or even the extended id (for example /providers/microsoft.management/managementgroups/corp/) fail with an error:

 1│ Error: parsing "corp": parsing the ResourceGroup ID: the number of segments didn't match
 2 3│ Expected a ResourceGroup ID that matched (containing 4 segments):
 4 5│ > /subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group
 6 7│ However this value was provided (which was parsed into 0 segments):
 8 9│ > corp
1011│ The following Segments are expected:
1213│ * Segment 0 - this should be the literal value "subscriptions"
14│ * Segment 1 - this should be the UUID of the Azure Subscription
15│ * Segment 2 - this should be the literal value "resourceGroups"
16│ * Segment 3 - this should be the name of the Resource Group
1718│ The following Segments were parsed:
1920│ * Segment 0 - not found
21│ * Segment 1 - not found
22│ * Segment 2 - not found
23│ * Segment 3 - not found
24

Cause

The management group ID doesn’t follow the same format as a Resource Group ID:

1/subscriptions/12345678-1234-9876-4563-123456789012/resourceGroups/example-resource-group

so is rejected by the Terraform validation.

Solution

A workaround is needed to have the ID accepted. This can be done with a data block referencing the Management Group (in this case corp) and using the ID of that object for the scope when looking up the data block for the Assignment. The Exemption (in this case using a Resource Group) then works as normal.

 1#Lookup Management Group
 2data "azurerm_management_group" "corp" {
 3  name = "corp"
 4}
 5
 6#Lookup Policy Assignment 
 7data "azurerm_policy_assignment" "example" {
 8  scope_id = data.azurerm_management_group.corp.id 
 9  #Resolves to "/providers/Microsoft.Management/managementGroups/corp"
10  name = "example-policy-name"
11}
12
13#Define Exemption
14resource "azurerm_resource_group_policy_exemption" "example" {
15  name                 = "example-exemption"
16  resource_group_id    = azurerm_resource_group.rg.id
17  policy_assignment_id = data.azurerm_policy_assignment.example.id
18  exemption_category = "Mitigated"
19}

Using this method bypasses the string format validation on the ID value and enables the plan and apply to work.