Skip to content

Policy Assignments (New, Update)

Standard changes: New Azure Policy assignment (assign an existing definition or initiative to a new scope) and Update Managed Azure Policy assignment.

Effect: an Azure Policy assignment is created or updated at a management group, subscription, resource group or resource. If the assignment has a managed identity, the required role assignments are created too. Only the postdeployment layer changes, in the primary region.

For the related change assign a managed initiative to a management group, see Managed initiative on a management group.

Request fields

Field Where it goes
Policy definition/initiative Name policy_definition_id (full resource ID)
Rationale pull request; also properties.description
Scope Folder plus scope key: managementGroupId, subscriptionId, resourceGroupId or resourceId
Comments e.g. parameters, exclusions (notScopes), enforcement mode

Where the files are

Set in <env-folder>/<primary region>/postdeployment.auto.tfvars:

policy_assignment_mg_json_file_relative_path  = "policies/policy_assignments/01_management_groups"
policy_assignment_mg_json_file_pattern        = "/**/*.json"
policy_assignment_sub_json_file_relative_path = "policies/policy_assignments/02_subscriptions"
policy_assignment_sub_json_file_pattern       = "/**/*.json"
policy_assignment_rg_json_file_relative_path  = "policies/policy_assignments/03_resource_groups"
policy_assignment_rg_json_file_pattern        = "/**/*.json"
policy_assignment_res_json_file_relative_path = "policies/policy_assignments/04_resources"
policy_assignment_res_json_file_pattern       = "/**/*.json"

These paths are only read in the primary region (infra/deploy/postdeployment/locals.tf).

Folder Scope key in the JSON Example value Terraform resource
01_management_groups managementGroupId /providers/Microsoft.Management/managementGroups/cloudplatform-landingzones azurerm_management_group_policy_assignment
02_subscriptions subscriptionId /subscriptions/<guid> azurerm_subscription_policy_assignment
03_resource_groups resourceGroupId /subscriptions/<guid>/resourceGroups/<rg> azurerm_resource_group_policy_assignment
04_resources resourceId full resource ID azurerm_resource_policy_assignment

Management group assignments must point to an initiative (policy set). For 01_management_groups the module looks up the definition as a Microsoft.Authorization/policySetDefinitions resource (infra/modules/policy_definition_references). A single policy definition ID fails there. To assign one policy at a management group, wrap it in an initiative first. The other scopes accept both definitions and initiatives.

Assignment JSON

From infra/modules/policy/main.tf. All keys shown are read. Use null where you have no value.

{
  "name": "lz-require-tags",
  "policy_definition_id": "/providers/Microsoft.Management/managementGroups/cloudplatform/providers/Microsoft.Authorization/policySetDefinitions/lz-polset-require-tags",
  "enforce": true,
  "identityType": null,
  "location": "westeurope",
  "notScopes": null,
  "managementGroupId": "/providers/Microsoft.Management/managementGroups/cloudplatform-landingzones",
  "non_compliance_message": {
    "content": "Resources in landing zones must carry the required tags.",
    "policy_definition_reference_id": null
  },
  "properties": {
    "displayName": "Require tags on landing zone resources",
    "description": "Change <ticket>: enforce costcentre and owner tags.",
    "metadata": { "assignedBy": "Infrastructure as Code (IaC)" },
    "parameters": {
      "tagName": { "value": "costcentre" }
    }
  }
}
Key Notes
name The Azure assignment name and the Terraform key. Max 24 characters for management group scope (Azure limit). Changing it replaces the assignment.
policy_definition_id Built-in: /providers/Microsoft.Authorization/policySetDefinitions/<guid> or /policyDefinitions/<guid>. Custom (deployed by this platform): /providers/Microsoft.Management/managementGroups/<cloudplatform id>/providers/Microsoft.Authorization/policySetDefinitions/<name>.
enforce true = Default (deny/deploy effects act), false = DoNotEnforce (evaluate only). The sample uses the string "true"; a boolean is clearer.
identityType "SystemAssigned" for policies with deployIfNotExists / modify effects, otherwise null.
location Required when an identity is used. Use the primary region name.
notScopes List of excluded child scopes (top-level key; properties.notScopes is not read).
non_compliance_message Optional message shown on denial; null to omit.
properties.parameters { "<param>": { "value": … } }, exactly as in the Azure portal JSON.

Role assignments for the identity are created automatically (ADR-0008). With identityType: "SystemAssigned", the module reads roleDefinitionIds from the definition, or from every definition in the initiative, and assigns those roles at the assignment scope (infra/modules/policy/main_role_assignments_auto.tf).

Remediation tasks are created only for management group assignments, and only when automatically_start_policy_remediation = true in postdeployment.auto.tfvars (the variable defaults to true). They are recreated whenever the assignment changes (ADR-0010). For other scopes, start remediation in the portal.

New assignment: steps

  1. Make sure the definition exists:
  2. Built-in: look up its ID in the portal (Policy → Definitions).
  3. Custom: add it under policies/policy_definitions/ or policies/policy_set_definitions/ (see Managed initiative on a management group). It is deployed by the foundation layer, which runs before postdeployment in the same hub run.
  4. Create the assignment JSON in the folder for its scope.
  5. Validate the JSON and open a pull request.
  6. Run hub. Expected plan:
  7. postdeployment (primary region): + assignment, plus role assignments (with an identity) and remediations (management group scope);
  8. foundation: only if you added a definition.
  9. Apply.

Update an existing assignment

Edit the JSON (parameters, enforce, notScopes, non-compliance message), then run hub.

  • An in-place update shows as ~.
  • Changing name, the scope key or policy_definition_id replaces the assignment (-/+). For a while the policy is not assigned, and exemptions that reference the old assignment ID break.

Remove an assignment

Delete the file (or rename it to .json.ignore) and run hub. The plan shows the assignment, and its role assignments and remediations, being destroyed. Remove exemptions that point to it in the same change.

Exemptions

The process (request, approval, review, mirroring to the secure score) is in Policy exemptions. This section is the file format.

Exemptions are JSON files in policies/policy_exemptions/01_management_groups … 04_resources (paths in postdeployment.auto.tfvars, policy_exemption_*_json_file_relative_path):

{
  "name": "exempt-app1-public-ip",
  "policy_assignment_id": "/providers/Microsoft.Management/managementGroups/cloudplatform-landingzones/providers/Microsoft.Authorization/policyAssignments/lz-deny-public-ip",
  "subscription_id": "/subscriptions/<guid>",
  "exemption_category": "Waiver",
  "description": "Change <ticket>: approved public IP for app1 until 2026-12-31.",
  "display_name": "app1 public IP waiver",
  "expires_on": "2026-12-31T00:00:00Z",
  "policy_definition_ids": [],
  "metadata": null
}
  • The scope key depends on the folder: management_group_id, subscription_id, resource_group_id or resource_id.
  • policy_definition_ids: to exempt only some policies of an initiative, list their definition IDs. They are converted to reference IDs automatically (infra/modules/policy_reference_ids). Use [] (empty list) for the whole assignment. Do not use null: the module iterates over the list, and the plan fails on null.
  • Exemptions only work for assignments of an initiative. The reference-ID lookup always reads the assigned definition as a policySetDefinitions resource (infra/modules/policy_reference_ids/main.tf). To exempt from a single-policy assignment, create the exemption outside this mechanism, or assign the policy through an initiative.
  • Use the key policy_definition_ids. The sample .jason files use policy_definition_reference_ids, which the module does not read.
  • Subscription, resource group and resource exemptions are also mirrored to the Microsoft cloud security benchmark assignment (mirror_exemptions_to_mcsb, default true). See docs/policy_exemptions/README.md.

Document exemptions that apply to the platform

Some exemptions are part of how the platform works and apply to the hub itself. Keep one list of them, with the policy, the scope and the rationale for each, and review it regularly. The reasons are usually of this kind:

Reason Example
The managed-service provider's engineers are guest accounts that need access Policies that flag guest accounts with read, write or owner permissions. The access is kept least-privilege with PIM.
A conscious cost or risk decision DDoS protection not enabled because the cost outweighs the benefit for this customer. Record who decided and when to review.
The deployment mechanism needs it A policy against administrative roles for service principals, because infrastructure as code deploys with a service principal.

An exemption without a rationale and an end date or review date is hard to defend in an audit.

Audit in non-production, enforce in production

A common approach is to audit the baseline initiatives in non-production and enforce them (deny effects) in production-like environments. Teams then see non-compliance in dev and tst without being blocked, and fix it before they get to acc and prd. With the JSON above: assign the initiative on the non-production management group with enforce set to false (DoNotEnforce), and on the production management group with enforce set to true. Make sure workload teams know which behaviour applies where (see Policy and exemptions).

Verify

  • Azure portal → Policy → Assignments, filtered on the scope: the assignment is present with the right parameters.
  • Compliance: the first evaluation can take up to about 30 minutes after assignment (Azure behavior).
  • For deployIfNotExists / modify: check the remediation task, or start one.

Roll back

Revert the pull request and run hub.