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_groupsthe module looks up the definition as aMicrosoft.Authorization/policySetDefinitionsresource (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¶
- Make sure the definition exists:
- Built-in: look up its ID in the portal (Policy → Definitions).
- Custom: add it under
policies/policy_definitions/orpolicies/policy_set_definitions/(see Managed initiative on a management group). It is deployed by thefoundationlayer, which runs beforepostdeploymentin the samehubrun. - Create the assignment JSON in the folder for its scope.
- Validate the JSON and open a pull request.
- Run
hub. Expected plan: postdeployment(primary region):+assignment, plus role assignments (with an identity) and remediations (management group scope);foundation: only if you added a definition.- 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 orpolicy_definition_idreplaces 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_idorresource_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 usenull: the module iterates over the list, and the plan fails onnull.- Exemptions only work for assignments of an initiative. The reference-ID lookup always reads the assigned
definition as a
policySetDefinitionsresource (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.jasonfiles usepolicy_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, defaulttrue). Seedocs/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.