Customizing policies¶
Recipes for the two layers. The in-repo recipes change
_foundation/landing-zones/main.tf in the landing-zone repo and take effect on the
next apply. The in-library recipes change the vendored library under
landing-zones/lib/ and take effect once committed (no external version to bump; a
shared git catalog would need a ref bump instead).
Read Azure Policy first for the model and where each piece lives. The
examples below use the starter's tag-governance archetype (Enforce-Tag-Gov) as a
concrete illustration; substitute your own assignment names.
The landing-zone unit ships two commented reference blocks with worked, ready-to-lift
examples of everything here: common_customizations (day-one settings plus every
modify capability) and prod_hardening (the enforcement recommendation). See
Enforcement strategy.
In-repo: tune an existing assignment¶
policy_assignments_to_modify changes assignments the archetypes already created.
The outer key is the management group id, then policy_assignments, then the
assignment name. You only specify the properties you want to change.
locals {
policy_assignments_to_modify = {
alz = {
policy_assignments = {
Enforce-Tag-Gov = {
parameters = {
rgMandatoryTagsEffect = jsonencode({ value = "Deny" })
criticalityEffect = jsonencode({ value = "Deny" })
confidentialityEffect = jsonencode({ value = "Deny" })
environmentEffect = jsonencode({ value = "Deny" })
}
}
}
}
}
}
Every parameter value is wrapped in jsonencode({ value = ... }). That is a
workaround for the Terraform type system, not optional. The parameter names come
from the assignment's policy set, so check the set definition for the names and
their allowed values.
The attributes you can set on an assignment:
| Attribute | Effect |
|---|---|
parameters |
Override parameter values. Map of name = jsonencode({ value = ... }). |
enforcement_mode |
Default enforces, DoNotEnforce evaluates compliance without acting. |
non_compliance_messages |
Custom messages shown on non-compliant resources. |
not_scopes |
Resource ids excluded from evaluation, even below the assigned MG. |
overrides |
Change the effect of the whole assignment or selected definitions. |
resource_selectors |
Scope the assignment to certain locations or resource types. |
identity / identity_ids |
The managed identity for deployIfNotExists and modify. |
creation_enabled |
false stops the assignment being created at all. |
Change enforcement mode¶
DoNotEnforce reports compliance but never applies the effect. Use it to preview
the impact of an assignment before you let it act.
Set a non-compliance message¶
Enforce-Tag-Gov = {
non_compliance_messages = [
{ message = "Resource groups must carry workload, owner, criticality, and confidentiality tags." },
]
}
For an assignment that assigns an initiative, target one policy in the set with
policy_definition_reference_id.
Verify the plan
In the current provider version a non-compliance message change on an already-applied assignment may plan as no change, even though the input is valid. Confirm your edit shows in the plan before relying on it. Parameter and effect changes always diff.
Disable a single assignment¶
creation_enabled = false is a convenience for very small changes. For anything
beyond one or two assignments, the module recommends excluding the assignment in
your library archetype instead, so the intent is versioned rather than patched in
the consuming repo.
In-repo: feed a default value¶
policy_default_values supplies a value once and fans it out to every assignment
parameter in the library that declares that default name. The reference uses it for
the shared monitoring resources: the Log Analytics workspace, the data collection
rules, and the monitoring agent identity.
policy_default_values = {
log_analytics_workspace_id = jsonencode({ value = local.law_id })
# ...one entry per default name in the library
}
Known values only, no depends_on
The alz provider reads a data source at plan time, so it cannot take a value
that is unknown until apply, and the module does not support depends_on.
Build ids as strings from known names rather than reading them from another
module's output. When you genuinely must order against a computed value, use
the module's policy_assignments_dependencies and
policy_role_assignments_dependencies inputs, not depends_on.
In-library: add a custom policy definition¶
Author in the vendored library under landing-zones/lib/. A definition is one
file, <Name>.alz_policy_definition.json, holding the standard Azure Policy
definition. The starter library ships a worked example, Deny-Tag-NotAllowedValues,
which denies a resource when a given tag holds a value outside an allowed list.
To make a new definition live:
- Add
lib/policy_definitions/<Name>.alz_policy_definition.json. - Reference it from an initiative in
lib/policy_set_definitions/, or assign it directly. - List it on an archetype in
lib/archetype_definitions/so a management group actually receives it. - Commit and open a pull request. The provider reads
lib/directly, so the change is live on the next apply, with no version to bump. (With a shared git catalog instead, bump the libraryversion, tag the repo, and update therefinlibrary_references.)
The chain from definition to an assigned management group is: definition goes
into an initiative (policy set), the initiative is assigned by an assignment,
the assignment is listed on an archetype, and the archetype is attached to a
management group by the architecture. The starter's tag archetype shows the
whole chain: it lists a tag-governance assignment, the initiative behind it, and the
Deny-Tag-NotAllowedValues definition, and the architecture attaches that archetype
to the intermediate root.
In-library: replace a stock asset¶
To change a stock ALZ asset rather than add one, create an asset with the same
name in your library. library_overwrite_enabled = true is already set in the
provider, so your version wins. Use this sparingly: an overwritten asset no longer
tracks the upstream library when you update the stock ref.
Rolling out a change¶
Introduce policy advisory-first.
- Plan first. Every change here is a
terragrunt planon thelanding-zonesunit. Read the diff before you apply. See Test a policy change. - Advisory before enforced. Land a new control with its effect on
Audit(or the assignment onDoNotEnforce), review compliance, then move toDeny. - One control at a time. A single assignment or effect per pull request keeps the plan readable and the blast radius small.
This advisory-first path maps to the CAF Govern methodology and keeps the rollout inside WAF Operational Excellence. Enforcing afterwards is what delivers the WAF Security benefit.
Next: Test a policy change.