diff --git a/modules/def_assignment/TEMPLATE.md b/.config/templ-def_assignment.md
similarity index 100%
rename from modules/def_assignment/TEMPLATE.md
rename to .config/templ-def_assignment.md
diff --git a/modules/definition/TEMPLATE.md b/.config/templ-definition.md
similarity index 89%
rename from modules/definition/TEMPLATE.md
rename to .config/templ-definition.md
index 52f7d28..8a8d0b9 100644
--- a/modules/definition/TEMPLATE.md
+++ b/.config/templ-definition.md
@@ -53,7 +53,22 @@ module "file_path_test" {
}
```
+Loop around a folders contents to create multiple definitions:
+
+```hcl
+module "iam_test" {
+ source = "gettek/policy-as-code/azurerm//modules/definition"
+ for_each = {
+ for p in fileset(path.module, "../../azure/governance/policies/Storage/*.json") :
+ trimsuffix(basename(p), ".json") => pathexpand(p)
+ }
+ file_path = each.value
+ management_group_id = data.azurerm_management_group.org.id
+}
+```
+
You will also be able to supply object properties at runtime such as:
+
```hcl
locals {
policy_file = jsondecode(file("onboard_to_automation_dsc_linux.json"))
diff --git a/examples-machine-config/TEMPLATE.md b/.config/templ-examples-machine-config.md
similarity index 100%
rename from examples-machine-config/TEMPLATE.md
rename to .config/templ-examples-machine-config.md
diff --git a/examples/TEMPLATE.md b/.config/templ-examples.md
similarity index 100%
rename from examples/TEMPLATE.md
rename to .config/templ-examples.md
diff --git a/modules/exemption/TEMPLATE.md b/.config/templ-exemption.md
similarity index 100%
rename from modules/exemption/TEMPLATE.md
rename to .config/templ-exemption.md
diff --git a/modules/initiative/TEMPLATE.md b/.config/templ-initiative.md
similarity index 86%
rename from modules/initiative/TEMPLATE.md
rename to .config/templ-initiative.md
index 130006d..4f76932 100644
--- a/modules/initiative/TEMPLATE.md
+++ b/.config/templ-initiative.md
@@ -4,10 +4,19 @@ Dynamically creates a policy set based on multiple custom or built-in policy def
> ⚠️ **Warning:** To simplify assignments, if any `member_definitions` contain the same parameter names they will be [merged](https://www.terraform.io/language/functions/merge) unless you specify `merge_effects = false` or `merge_parameters = false` as described in the second example below.
-> 💡 **Note:** Multiple entries of the same `member_definitions` are not currently supported, if you require the same definition to be present more than once you may use this module to create the initiative json which you can then edit to add unique parameter and definition references. Some examples can be found in discussion [#67](https://github.com/gettek/terraform-azurerm-policy-as-code/discussions/67)
-
## Examples
+
+### Create an Initiative with a duplicate member definitions
+
+In many cases, some initiatives such as those for tagging, may need to reuse the same definition multiple times but with different parameters to simplify assignments.
+
+Please see [duplicate_members.tf](../../examples/duplicate_members.tf) as en example use case.
+
+> 💡 **Note:** you must set `duplicate_members=true` and `merge_parameters=false` when building initiatives with duplicate members.
+> 💡 **Note:** Be cautious when changing the position of `member_definitions` as these reflect the index numbers used in `assignment_parameters`.
+
+
### Create an Initiative with custom Policy definitions
```hcl
@@ -63,7 +72,7 @@ output "list_of_initiative_parameters" {
}
```
-### Populate member_definitions with a for loop (not explicit)
+### Populate member_definitions with a for loop
```hcl
locals {
diff --git a/modules/set_assignment/TEMPLATE.md b/.config/templ-set_assignment.md
similarity index 90%
rename from modules/set_assignment/TEMPLATE.md
rename to .config/templ-set_assignment.md
index cd9a999..697cd7e 100644
--- a/modules/set_assignment/TEMPLATE.md
+++ b/.config/templ-set_assignment.md
@@ -36,11 +36,9 @@ module org_mg_configure_asc_initiative {
data.azurerm_management_group.team_a.id
]
- # optional non-compliance messages. Key/Value pairs map as policy_definition_reference_id = 'content'
- non_compliance_messages = {
- null = "The Default non-compliance message for all member definitions"
- AutoEnrollSubscriptions = "The non-compliance message for the auto_enroll_subscriptions definition"
- }
+ # use the 'non_compliance_messages' output from the initiative module to use auto generated messages based off policy properties: descriptions/display names/custom ones found in metadata
+ # override with your own Key/Value pairs map as 'policy_definition_reference_id = content', use null = 'content' to specify the Default non-compliance message for all member definitions.
+ non_compliance_messages = module.configure_asc_initiative.non_compliance_messages
# optional overrides (preview)
overrides = [
diff --git a/.config/terraform-docs.yml b/.config/terraform-docs.yml
new file mode 100644
index 0000000..566ba89
--- /dev/null
+++ b/.config/terraform-docs.yml
@@ -0,0 +1,28 @@
+---
+formatter: "markdown"
+
+settings:
+ anchor: false
+ lockfile: false
+ escape: false
+ hide-empty: true
+
+output:
+ file: "README.md"
+
+sections:
+ hide: [providers]
+
+content: |-
+ {{ .Header }}
+
+ {{ .Requirements }}
+
+ {{ .Modules }}
+
+ {{ .Resources }}
+
+ {{ .Inputs }}
+
+ {{ .Outputs }}
+...
\ No newline at end of file
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index 02d187b..b35c7be 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -12,6 +12,7 @@ jobs:
env:
TF_IN_AUTOMATION: true
TF_INPUT: false
+ TF_CLI_ARGS_init: "-backend-config=storage_account_name=${{ secrets.STORAGE_NAME }} -backend-config=resource_group_name=cgc-cd -backend-config=container_name=tfstate -backend-config=key=policy.tfstate"
TF_CLI_ARGS_apply: "-auto-approve -parallelism=30"
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
@@ -22,7 +23,7 @@ jobs:
- uses: actions/checkout@v3
- uses: hashicorp/setup-terraform@v2
with:
- terraform_version: ~1.3.0
+ terraform_version: ~1.4.0
- name: Terraform Init
id: init
@@ -34,25 +35,3 @@ jobs:
if: ${{ success() }}
run: terraform apply
working-directory: examples
-
- - name: Azure Login
- uses: azure/login@v1
- if: ${{ failure() }} || ${{ success() }}
- with:
- creds: ${{ secrets.AZURE_CREDENTIALS }}
- enable-AzPSSession: true
-
- # Used by GitHub Workflows to clean deployed resources quicker than tf destroy
- # Quicker during CD as remediation tasks must be in a terminal provisioning state (Succeeded, Canceled, Failed) before they can be deleted.
- - name: Clean Resources with PowerShell
- id: destroy
- uses: azure/powershell@v1
- if: ${{ failure() }} || ${{ success() }}
- with:
- azPSVersion: "latest"
- inlineScript: |
- Get-AzPolicyAssignment -Scope "/providers/Microsoft.Management/managementgroups/team_a" | Remove-AzPolicyAssignment -Verbose
- Get-AzPolicyAssignment -Scope "/providers/Microsoft.Management/managementgroups/policy_dev" | Remove-AzPolicyAssignment -Verbose
- Get-AzPolicySetDefinition -ManagementGroupName "policy_dev" -Custom | Remove-AzPolicySetDefinition -Force -Verbose
- Get-AzPolicyDefinition -ManagementGroupName "policy_dev" -Custom | Remove-AzPolicyDefinition -Force -Verbose
- Remove-AzPolicyExemption -Name "Subscription Diagnostic Settings Exemption" -Scope ("/subscriptions/" + (Get-AzContext).Subscription.Id) -Force -Verbose -ErrorAction SilentlyContinue
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 12b0d38..6dd1d1c 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,6 +11,7 @@ jobs:
TF_IN_AUTOMATION: true
TF_INPUT: false
TF_WORKING_DIR: examples
+ TF_CLI_ARGS_init: "-backend-config=storage_account_name=${{ secrets.STORAGE_NAME }} -backend-config=resource_group_name=cgc-cd -backend-config=container_name=tfstate -backend-config=key=policy.tfstate"
ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
@@ -20,7 +21,7 @@ jobs:
- uses: actions/checkout@v3
- uses: hashicorp/setup-terraform@v2
with:
- terraform_version: ~1.3.0
+ terraform_version: ~1.4.0
- name: Terraform Format
id: fmt
diff --git a/README.md b/README.md
index 8096e6e..5329412 100644
--- a/README.md
+++ b/README.md
@@ -153,7 +153,7 @@ module org_mg_platform_diagnostics_initiative {
workspaceId = data.azurerm_log_analytics_workspace.workspace.id
storageAccountId = data.azurerm_storage_account.sa.id
eventHubName = data.azurerm_eventhub_namespace.ehn.name
- eventHubAuthorizationRuleId = data.azurerm_eventhub_namespace_authorization_rule.ehnar.id
+ eventHubAuthorizationRuleId = data.azurerm_eventhub_namespace_authorization_rule.ehr.id
metricsEnabled = "True"
logsEnabled = "True"
}
@@ -259,7 +259,7 @@ To trigger an on-demand [compliance scan](https://learn.microsoft.com/en-us/azur
## Limitations
-- `DefinitionName` and `InitiativeName` has a maximum length of **64** characters
+- `DefinitionName` and `InitiativeName` have a maximum length of **64** characters
- `AssignmentName` has maximum length of **24** characters at Management Group Scope and **64** characters at all other Scopes
- `DisplayName` has a maximum length of **128** characters and `description` a maximum length of **512** characters
- There's a [maximum count](https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/azure-subscription-service-limits#azure-policy-limits) for each object type for Azure Policy. For definitions, an entry of Scope means the management group or subscription. For assignments and exemptions, an entry of Scope means the management group, subscription, resource group, or individual resource:
diff --git a/examples-machine-config/README.md b/examples-machine-config/README.md
index 72ae607..66c401b 100644
--- a/examples-machine-config/README.md
+++ b/examples-machine-config/README.md
@@ -1,3 +1,4 @@
+
# Azure Policy Machine Configuration for Virtual Machines
[![cd-machine-config](https://github.com/gettek/terraform-azurerm-policy-as-code/actions/workflows/cd-guest-config.yml/badge.svg)](https://github.com/gettek/terraform-azurerm-policy-as-code/actions/workflows/cd-guest-config.yml)
@@ -31,28 +32,20 @@ Definitions will stored in the local repo library under [Guest Configuration](..
- 📙 [DSC GitHub Community](https://github.com/dsccommunity)
- 📙 [Terraform Provider: azurerm_policy_virtual_machine_configuration_assignment](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/policy_virtual_machine_configuration_assignment)
-
## Requirements
| Name | Version |
|------|---------|
-| [azurerm](#requirement\_azurerm) | >=3.49.0 |
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [azurerm](#provider\_azurerm) | 3.53.0 |
-| [null](#provider\_null) | 3.2.1 |
+| azurerm | >=3.49.0 |
## Modules
| Name | Source | Version |
|------|--------|---------|
-| [custom\_guest\_configs](#module\_custom\_guest\_configs) | ..//modules/definition | n/a |
-| [custom\_guest\_configs\_initiative](#module\_custom\_guest\_configs\_initiative) | ..//modules/initiative | n/a |
-| [team\_a\_mg\_guest\_config\_prereqs\_initiative](#module\_team\_a\_mg\_guest\_config\_prereqs\_initiative) | ..//modules/set_assignment | n/a |
-| [team\_a\_mg\_vm\_custom\_guest\_configs](#module\_team\_a\_mg\_vm\_custom\_guest\_configs) | ..//modules/set_assignment | n/a |
+| custom_guest_configs | ..//modules/definition | n/a |
+| custom_guest_configs_initiative | ..//modules/initiative | n/a |
+| team_a_mg_guest_config_prereqs_initiative | ..//modules/set_assignment | n/a |
+| team_a_mg_vm_custom_guest_configs | ..//modules/set_assignment | n/a |
## Resources
@@ -70,10 +63,9 @@ Definitions will stored in the local repo library under [Guest Configuration](..
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [re\_evaluate\_compliance](#input\_re\_evaluate\_compliance) | Should the module re-evaluate compliant resources for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
-| [skip\_remediation](#input\_skip\_remediation) | Skip creation of all remediation tasks for policies that DeployIfNotExists and Modify | `bool` | `true` | no |
-| [skip\_role\_assignment](#input\_skip\_role\_assignment) | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| re_evaluate_compliance | Should the module re-evaluate compliant resources for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| skip_remediation | Skip creation of all remediation tasks for policies that DeployIfNotExists and Modify | `bool` | `true` | no |
+| skip_role_assignment | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
-## Outputs
-No outputs.
+
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
index bc9a7db..e0c1e99 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,47 +1,45 @@
+
# Azure Policy Deployments
This examples folder demonstrates an effective deployment of Azure Policy Definitions and Assignments. The order of execution is generally from `definitions.tf` -> `initiatives.tf` -> `assignments_.tf` -> `exemptions.tf`
> 💡 **Note:** `built-in.tf` demonstrates how to assign Built-In definitions.
-
## Requirements
| Name | Version |
|------|---------|
-| [azurerm](#requirement\_azurerm) | >=3.49.0 |
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [azurerm](#provider\_azurerm) | 3.53.0 |
+| terraform | >= 1.4 |
+| azurerm | >=3.49.0 |
## Modules
| Name | Source | Version |
|------|--------|---------|
-| [configure\_asc](#module\_configure\_asc) | ..//modules/definition | n/a |
-| [configure\_asc\_initiative](#module\_configure\_asc\_initiative) | ..//modules/initiative | n/a |
-| [deny\_nic\_public\_ip](#module\_deny\_nic\_public\_ip) | ..//modules/definition | n/a |
-| [deny\_resource\_types](#module\_deny\_resource\_types) | ..//modules/definition | n/a |
-| [deploy\_resource\_diagnostic\_setting](#module\_deploy\_resource\_diagnostic\_setting) | ..//modules/definition | n/a |
-| [exemption\_subscription\_diagnostics\_settings](#module\_exemption\_subscription\_diagnostics\_settings) | ..//modules/exemption | n/a |
-| [inherit\_resource\_group\_tags\_modify](#module\_inherit\_resource\_group\_tags\_modify) | ..//modules/definition | n/a |
-| [org\_mg\_configure\_asc\_initiative](#module\_org\_mg\_configure\_asc\_initiative) | ..//modules/set_assignment | n/a |
-| [org\_mg\_configure\_az\_monitor\_and\_security\_vm\_initiative](#module\_org\_mg\_configure\_az\_monitor\_and\_security\_vm\_initiative) | ..//modules/set_assignment | n/a |
-| [org\_mg\_platform\_diagnostics\_initiative](#module\_org\_mg\_platform\_diagnostics\_initiative) | ..//modules/set_assignment | n/a |
-| [org\_mg\_storage\_enforce\_https](#module\_org\_mg\_storage\_enforce\_https) | ..//modules/def_assignment | n/a |
-| [org\_mg\_storage\_enforce\_minimum\_tls1\_2](#module\_org\_mg\_storage\_enforce\_minimum\_tls1\_2) | ..//modules/def_assignment | n/a |
-| [org\_mg\_whitelist\_regions](#module\_org\_mg\_whitelist\_regions) | ..//modules/def_assignment | n/a |
-| [parameterised\_test](#module\_parameterised\_test) | ..//modules/definition | n/a |
-| [platform\_diagnostics\_initiative](#module\_platform\_diagnostics\_initiative) | ..//modules/initiative | n/a |
-| [storage\_enforce\_https](#module\_storage\_enforce\_https) | ..//modules/definition | n/a |
-| [storage\_enforce\_minimum\_tls1\_2](#module\_storage\_enforce\_minimum\_tls1\_2) | ..//modules/definition | n/a |
-| [team\_a\_mg\_deny\_nic\_public\_ip](#module\_team\_a\_mg\_deny\_nic\_public\_ip) | ..//modules/def_assignment | n/a |
-| [team\_a\_mg\_deny\_resource\_types](#module\_team\_a\_mg\_deny\_resource\_types) | ..//modules/def_assignment | n/a |
-| [team\_a\_mg\_inherit\_resource\_group\_tags\_modify](#module\_team\_a\_mg\_inherit\_resource\_group\_tags\_modify) | ..//modules/def_assignment | n/a |
-| [whitelist\_regions](#module\_whitelist\_regions) | ..//modules/definition | n/a |
+| configure_asc | ..//modules/definition | n/a |
+| configure_asc_initiative | ..//modules/initiative | n/a |
+| deny_nic_public_ip | ..//modules/definition | n/a |
+| deny_resource_types | ..//modules/definition | n/a |
+| deploy_resource_diagnostic_setting | ..//modules/definition | n/a |
+| exemption_subscription_diagnostics_settings | ..//modules/exemption | n/a |
+| file_path_test | ..//modules/definition | n/a |
+| inherit_resource_group_tags_modify | ..//modules/definition | n/a |
+| org_mg_configure_asc_initiative | ..//modules/set_assignment | n/a |
+| org_mg_configure_az_monitor_and_security_vm_initiative | ..//modules/set_assignment | n/a |
+| org_mg_platform_diagnostics_initiative | ..//modules/set_assignment | n/a |
+| org_mg_storage_enforce_https | ..//modules/def_assignment | n/a |
+| org_mg_storage_enforce_minimum_tls1_2 | ..//modules/def_assignment | n/a |
+| org_mg_whitelist_regions | ..//modules/def_assignment | n/a |
+| parameterised_test | ..//modules/definition | n/a |
+| platform_diagnostics_initiative | ..//modules/initiative | n/a |
+| require_resource_group_tags | ..//modules/definition | n/a |
+| resource_group_tags | ..//modules/initiative | n/a |
+| storage_enforce_https | ..//modules/definition | n/a |
+| storage_enforce_minimum_tls1_2 | ..//modules/definition | n/a |
+| team_a_mg_deny_nic_public_ip | ..//modules/def_assignment | n/a |
+| team_a_mg_deny_resource_types | ..//modules/def_assignment | n/a |
+| team_a_mg_resource_group_tags | ..//modules/set_assignment | n/a |
+| whitelist_regions | ..//modules/definition | n/a |
## Resources
@@ -60,10 +58,9 @@ This examples folder demonstrates an effective deployment of Azure Policy Defini
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [re\_evaluate\_compliance](#input\_re\_evaluate\_compliance) | Should the module re-evaluate compliant resources for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
-| [skip\_remediation](#input\_skip\_remediation) | Skip creation of all remediation tasks for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
-| [skip\_role\_assignment](#input\_skip\_role\_assignment) | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| re_evaluate_compliance | Should the module re-evaluate compliant resources for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| skip_remediation | Skip creation of all remediation tasks for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| skip_role_assignment | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
-## Outputs
-No outputs.
+
\ No newline at end of file
diff --git a/examples/assignments_org.tf b/examples/assignments_org.tf
index 6174ad5..8417456 100644
--- a/examples/assignments_org.tf
+++ b/examples/assignments_org.tf
@@ -41,7 +41,7 @@ module "org_mg_configure_asc_initiative" {
source = "..//modules/set_assignment"
initiative = module.configure_asc_initiative.initiative
assignment_scope = data.azurerm_management_group.org.id
- assignment_description = "WIP - Deploys and configures Defender settings and defines exports"
+ assignment_description = "Deploys and configures Defender settings and defines exports"
assignment_effect = "DeployIfNotExists"
assignment_location = "ukwest"
@@ -49,7 +49,7 @@ module "org_mg_configure_asc_initiative" {
re_evaluate_compliance = var.re_evaluate_compliance
skip_remediation = var.skip_remediation
skip_role_assignment = var.skip_role_assignment
- role_assignment_scope = data.azurerm_management_group.team_a.id # using explicit scopes
+ role_assignment_scope = data.azurerm_management_group.team_a.id # set explicit scopes (defaults to assignment scope)
assignment_parameters = {
workspaceId = local.dummy_resource_ids.azurerm_log_analytics_workspace
@@ -58,11 +58,9 @@ module "org_mg_configure_asc_initiative" {
securityContactsPhone = "44897654987"
}
- # optional non-compliance messages. Key/Value pairs map as policy_definition_reference_id = 'content'
- non_compliance_messages = {
- null = "The Default non-compliance message for all member definitions"
- AutoEnrollSubscriptions = "The non-compliance message for the auto_enroll_subscriptions definition"
- }
+ # use the `non_compliance_messages` output from the initiative module to set auto generated messages based off policy properties: descriptions/display names/custom ones found in metadata
+ # or overried with you own Key/Value pairs map e.g. policy_definition_reference_id = 'message content'
+ non_compliance_messages = module.configure_asc_initiative.non_compliance_messages
# optional overrides (preview)
overrides = [
@@ -90,6 +88,7 @@ module "org_mg_platform_diagnostics_initiative" {
skip_role_assignment = var.skip_role_assignment
role_definition_ids = [data.azurerm_role_definition.contributor.id] # using explicit roles
+ # NOTE: You may omit parameters at assignment to use the definitions 'defaultValue'
assignment_parameters = {
workspaceId = local.dummy_resource_ids.azurerm_log_analytics_workspace
storageAccountId = local.dummy_resource_ids.azurerm_storage_account
@@ -110,6 +109,8 @@ module "org_mg_platform_diagnostics_initiative" {
effect_DeployVnetDiagnosticSetting = "AuditIfNotExists"
effect_DeployVnetGatewayDiagnosticSetting = "AuditIfNotExists"
}
+
+ non_compliance_messages = module.platform_diagnostics_initiative.non_compliance_messages
}
diff --git a/examples/assignments_team_a.tf b/examples/assignments_team_a.tf
index 2318c14..cfc3814 100644
--- a/examples/assignments_team_a.tf
+++ b/examples/assignments_team_a.tf
@@ -29,23 +29,3 @@ module "team_a_mg_deny_nic_public_ip" {
assignment_scope = data.azurerm_management_group.team_a.id
assignment_effect = "Deny"
}
-
-##################
-# Tags
-##################
-module "team_a_mg_inherit_resource_group_tags_modify" {
- source = "..//modules/def_assignment"
- definition = module.inherit_resource_group_tags_modify.definition
- assignment_scope = data.azurerm_management_group.team_a.id
- assignment_effect = "Modify"
-
- # resource remediation options
- re_evaluate_compliance = var.re_evaluate_compliance
- skip_remediation = var.skip_remediation
- remediation_scope = data.azurerm_subscription.current.id # change the scope of remediation tasks, defaults to assignment_scope
- identity_ids = [data.azurerm_user_assigned_identity.policy_rem.id] # use User Managed Identities
-
- assignment_parameters = {
- tagName = "environment"
- }
-}
diff --git a/examples/backend.tf b/examples/backend.tf
index 210c76b..4778ab4 100644
--- a/examples/backend.tf
+++ b/examples/backend.tf
@@ -1,10 +1,12 @@
terraform {
+ required_version = ">= 1.4"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">=3.49.0"
}
}
+ backend "azurerm" {}
}
provider "azurerm" {
diff --git a/examples/definitions.tf b/examples/definitions.tf
index 7d4dcbc..f769169 100644
--- a/examples/definitions.tf
+++ b/examples/definitions.tf
@@ -23,8 +23,11 @@ module "whitelist_regions" {
# create definitions by looping around all files found under the Monitoring category folder
module "deploy_resource_diagnostic_setting" {
- source = "..//modules/definition"
- for_each = toset([for p in fileset(path.cwd, "../policies/Monitoring/*.json") : trimsuffix(basename(p), ".json")])
+ source = "..//modules/definition"
+ for_each = toset([
+ for p in fileset(path.module, "../policies/Monitoring/*.json") :
+ trimsuffix(basename(p), ".json")
+ ])
policy_name = each.key
policy_category = "Monitoring"
management_group_id = data.azurerm_management_group.org.id
@@ -45,7 +48,7 @@ module "deny_nic_public_ip" {
# Security Center
##################
-# create definitions by calling them explicitly from a local (as above)
+# create definitions by listing them explicitly
module "configure_asc" {
source = "..//modules/definition"
for_each = toset([
@@ -82,20 +85,16 @@ module "storage_enforce_minimum_tls1_2" {
}
##################
-# Tags
+# Point to a specific filepath
##################
-
-module "inherit_resource_group_tags_modify" {
+module "file_path_test" {
source = "..//modules/definition"
- policy_name = "inherit_resource_group_tags_modify"
- display_name = "Resources should inherit Resource Group Tags and Values with Modify Remediation"
- policy_category = "Tags"
- policy_mode = "Indexed"
+ file_path = "${path.module}/../policies/Automation/onboard_to_automation_dsc_windows.json"
management_group_id = data.azurerm_management_group.org.id
}
##################
-# object properties at runtime:
+# Supply some or all policy object properties at runtime
##################
locals {
policy_file = jsondecode(file("${path.module}/../policies/Automation/onboard_to_automation_dsc_linux.json"))
diff --git a/examples/duplicate_members.tf b/examples/duplicate_members.tf
new file mode 100644
index 0000000..a12a1a0
--- /dev/null
+++ b/examples/duplicate_members.tf
@@ -0,0 +1,74 @@
+##################
+# This ResourceTags example demonstrates an initiative containing duplicate member definitions
+##################
+
+### DEFINITIONS
+module "inherit_resource_group_tags_modify" {
+ source = "..//modules/definition"
+ policy_name = "inherit_resource_group_tags_modify"
+ display_name = "Resources should inherit Resource Group Tags and Values with Modify Remediation"
+ policy_category = "Tags"
+ policy_mode = "Indexed"
+ management_group_id = data.azurerm_management_group.org.id
+}
+
+module "require_resource_group_tags" {
+ source = "..//modules/definition"
+ policy_name = "require_resource_group_tags"
+ display_name = "ResourceGroups require specific tags to be present"
+ policy_category = "Tags"
+ policy_mode = "Indexed"
+ management_group_id = data.azurerm_management_group.org.id
+}
+
+### INITIATIVE
+module "resource_group_tags" {
+ source = "..//modules/initiative"
+ initiative_name = "resource_group_tags"
+ initiative_display_name = "[Tags]: Require & Inherit ResourceGroup Tags"
+ initiative_description = "Ensures ResourceGroup tags are inherited by its resources"
+ initiative_category = "Tags"
+ management_group_id = data.azurerm_management_group.team_a.id
+ duplicate_members = true # this must be 'true' for the module to handle duplicate defs
+ merge_parameters = false # this must be 'false' for each occurance to have unique params and references
+
+ # include the same policy as many time as needed
+ # NOTE: be cautious when changing the position of members as these reflect the index numbers used in 'assignment_parameters' below
+ member_definitions = [
+ module.inherit_resource_group_tags_modify.definition,
+ module.inherit_resource_group_tags_modify.definition,
+ module.inherit_resource_group_tags_modify.definition,
+ module.inherit_resource_group_tags_modify.definition,
+ module.require_resource_group_tags.definition,
+ module.require_resource_group_tags.definition,
+ module.require_resource_group_tags.definition,
+ module.require_resource_group_tags.definition,
+ ]
+}
+
+### ASSIGNMENT
+module "team_a_mg_resource_group_tags" {
+ source = "..//modules/set_assignment"
+ initiative = module.resource_group_tags.initiative
+ assignment_scope = data.azurerm_management_group.team_a.id
+ assignment_location = "ukwest"
+
+ # resource remediation options
+ re_evaluate_compliance = var.re_evaluate_compliance
+ skip_remediation = var.skip_remediation
+ skip_role_assignment = var.skip_role_assignment
+
+ assignment_parameters = {
+ tagName_0_InheritResourceGroupTagsModify = "DepartmentName"
+ tagName_1_InheritResourceGroupTagsModify = "CostCode"
+ tagName_2_InheritResourceGroupTagsModify = "ProductCode"
+ tagName_3_InheritResourceGroupTagsModify = "Environment"
+ tagName_4_RequireResourceGroupTags = "DepartmentName"
+ tagName_5_RequireResourceGroupTags = "CostCode"
+ tagName_6_RequireResourceGroupTags = "ProductCode"
+ tagName_7_RequireResourceGroupTags = "Environment"
+ effect_7_RequireResourceGroupTags = "Disabled"
+ }
+
+ non_compliance_messages = module.resource_group_tags.non_compliance_messages
+}
diff --git a/examples/initiatives.tf b/examples/initiatives.tf
index f4b0f08..4986ad5 100644
--- a/examples/initiatives.tf
+++ b/examples/initiatives.tf
@@ -9,7 +9,7 @@ module "configure_asc_initiative" {
initiative_category = "Security Center"
management_group_id = data.azurerm_management_group.org.id
- # Populate member_definitions with a for loop (explicit)
+ # Populate member_definitions
member_definitions = [
module.configure_asc["auto_enroll_subscriptions"].definition,
module.configure_asc["auto_provision_log_analytics_agent_custom_workspace"].definition,
@@ -31,6 +31,6 @@ module "platform_diagnostics_initiative" {
merge_effects = false # will not merge "effect" parameters
management_group_id = data.azurerm_management_group.org.id
- # Populate member_definitions with a for loop (not explicit)
+ # Populate member_definitions with a for loop
member_definitions = [for mon in module.deploy_resource_diagnostic_setting : mon.definition]
}
diff --git a/modules/def_assignment/README.md b/modules/def_assignment/README.md
index 5257877..285c65b 100644
--- a/modules/def_assignment/README.md
+++ b/modules/def_assignment/README.md
@@ -1,3 +1,4 @@
+
# POLICY DEFINITION ASSIGNMENT MODULE
Assignments can be scoped from overarching management groups right down to individual resources.
@@ -6,7 +7,6 @@ Assignments can be scoped from overarching management groups right down to indiv
## Examples
-
### Assign a definition with Modify effect to automatically create a role assignment and remediation task
```hcl
@@ -141,23 +141,14 @@ module "org_mg_whitelist_regions" {
}
```
-
## Requirements
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 0.13 |
-| [azurerm](#requirement\_azurerm) | >=3.49.0 |
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [azurerm](#provider\_azurerm) | 3.53.0 |
+| terraform | >= 0.13 |
+| azurerm | >=3.49.0 |
-## Modules
-No modules.
## Resources
@@ -177,36 +168,37 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [assignment\_description](#input\_assignment\_description) | A description to use for the Policy Assignment, defaults to definition description. Changing this forces a new resource to be created | `string` | `null` | no |
-| [assignment\_display\_name](#input\_assignment\_display\_name) | The policy assignment display name, defaults to definition display\_name. Changing this forces a new resource to be created | `string` | `null` | no |
-| [assignment\_effect](#input\_assignment\_effect) | The effect of the policy. Changing this forces a new resource to be created | `string` | `null` | no |
-| [assignment\_enforcement\_mode](#input\_assignment\_enforcement\_mode) | Control whether the assignment is enforced | `bool` | `true` | no |
-| [assignment\_location](#input\_assignment\_location) | The Azure location where this policy assignment should exist, required when an Identity is assigned. Defaults to UK South. Changing this forces a new resource to be created | `string` | `"uksouth"` | no |
-| [assignment\_metadata](#input\_assignment\_metadata) | The optional metadata for the policy assignment. | `any` | `null` | no |
-| [assignment\_name](#input\_assignment\_name) | The name which should be used for this Policy Assignment, defaults to definition name. Changing this forces a new Policy Assignment to be created | `string` | `null` | no |
-| [assignment\_not\_scopes](#input\_assignment\_not\_scopes) | A list of the Policy Assignment's excluded scopes. Must be full resource IDs | `list(any)` | `[]` | no |
-| [assignment\_parameters](#input\_assignment\_parameters) | The policy assignment parameters. Changing this forces a new resource to be created | `any` | `null` | no |
-| [assignment\_scope](#input\_assignment\_scope) | The scope at which the policy will be assigned. Must be full resource IDs. Changing this forces a new resource to be created | `string` | n/a | yes |
-| [definition](#input\_definition) | Policy Definition resource node | `any` | n/a | yes |
-| [failure\_percentage](#input\_failure\_percentage) | (Optional) A number between 0.0 to 1.0 representing the percentage failure threshold. The remediation will fail if the percentage of failed remediation operations (i.e. failed deployments) exceeds this threshold. | `number` | `null` | no |
-| [identity\_ids](#input\_identity\_ids) | Optional list of User Managed Identity IDs which should be assigned to the Policy Definition | `list(any)` | `null` | no |
-| [location\_filters](#input\_location\_filters) | Optional list of the resource locations that will be remediated | `list(any)` | `[]` | no |
-| [non\_compliance\_message](#input\_non\_compliance\_message) | The optional non-compliance message text. | `string` | `null` | no |
-| [parallel\_deployments](#input\_parallel\_deployments) | (Optional) Determines how many resources to remediate at any given time. Can be used to increase or reduce the pace of the remediation. If not provided, the default parallel deployments value is used. | `number` | `null` | no |
-| [re\_evaluate\_compliance](#input\_re\_evaluate\_compliance) | Sets the remediation task resource\_discovery\_mode for policies that DeployIfNotExists and Modify. false = 'ExistingNonCompliant' and true = 'ReEvaluateCompliance'. Defaults to false. Applies at subscription scope and below | `bool` | `false` | no |
-| [remediation\_scope](#input\_remediation\_scope) | The scope at which the remediation tasks will be created. Must be full resource IDs. Defaults to the policy assignment scope. Changing this forces a new resource to be created | `string` | `null` | no |
-| [resource\_count](#input\_resource\_count) | (Optional) Determines the max number of resources that can be remediated by the remediation job. If not provided, the default resource count is used. | `number` | `null` | no |
-| [resource\_selectors](#input\_resource\_selectors) | Optional list of Resource selectors (preview), max 10. These facilitate safe deployment practices (SDP) by enabling you to gradually roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location | `list(any)` | `[]` | no |
-| [role\_assignment\_scope](#input\_role\_assignment\_scope) | The scope at which role definition(s) will be assigned, defaults to Policy Assignment Scope. Must be full resource IDs. Ignored when using Managed Identities. Changing this forces a new resource to be created | `string` | `null` | no |
-| [role\_definition\_ids](#input\_role\_definition\_ids) | List of Role definition ID's for the System Assigned Identity, defaults to roles included in the definition. Ignored when using Managed Identities. Changing this forces a new resource to be created | `list(any)` | `[]` | no |
-| [skip\_remediation](#input\_skip\_remediation) | Should the module skip creation of a remediation task for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
-| [skip\_role\_assignment](#input\_skip\_role\_assignment) | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| assignment_description | A description to use for the Policy Assignment, defaults to definition description. Changing this forces a new resource to be created | `string` | `null` | no |
+| assignment_display_name | The policy assignment display name, defaults to definition display_name. Changing this forces a new resource to be created | `string` | `null` | no |
+| assignment_effect | The effect of the policy. Changing this forces a new resource to be created | `string` | `null` | no |
+| assignment_enforcement_mode | Control whether the assignment is enforced | `bool` | `true` | no |
+| assignment_location | The Azure location where this policy assignment should exist, required when an Identity is assigned. Defaults to UK South. Changing this forces a new resource to be created | `string` | `"westeurope"` | no |
+| assignment_metadata | The optional metadata for the policy assignment. | `any` | `null` | no |
+| assignment_name | The name which should be used for this Policy Assignment, defaults to definition name. Changing this forces a new Policy Assignment to be created | `string` | `null` | no |
+| assignment_not_scopes | A list of the Policy Assignment's excluded scopes. Must be full resource IDs | `list(any)` | `[]` | no |
+| assignment_parameters | The policy assignment parameters. Changing this forces a new resource to be created | `any` | `{}` | no |
+| assignment_scope | The scope at which the policy will be assigned. Must be full resource IDs. Changing this forces a new resource to be created | `string` | n/a | yes |
+| definition | Policy Definition resource node | `any` | n/a | yes |
+| failure_percentage | (Optional) A number between 0.0 to 1.0 representing the percentage failure threshold. The remediation will fail if the percentage of failed remediation operations (i.e. failed deployments) exceeds this threshold. | `number` | `null` | no |
+| identity_ids | Optional list of User Managed Identity IDs which should be assigned to the Policy Definition | `list(any)` | `null` | no |
+| location_filters | Optional list of the resource locations that will be remediated | `list(any)` | `[]` | no |
+| non_compliance_message | The optional non-compliance message text. | `string` | `null` | no |
+| parallel_deployments | (Optional) Determines how many resources to remediate at any given time. Can be used to increase or reduce the pace of the remediation. If not provided, the default parallel deployments value is used. | `number` | `null` | no |
+| re_evaluate_compliance | Sets the remediation task resource_discovery_mode for policies that DeployIfNotExists and Modify. false = 'ExistingNonCompliant' and true = 'ReEvaluateCompliance'. Defaults to false. Applies at subscription scope and below | `bool` | `false` | no |
+| remediation_scope | The scope at which the remediation tasks will be created. Must be full resource IDs. Defaults to the policy assignment scope. Changing this forces a new resource to be created | `string` | `null` | no |
+| resource_count | (Optional) Determines the max number of resources that can be remediated by the remediation job. If not provided, the default resource count is used. | `number` | `null` | no |
+| resource_selectors | Optional list of Resource selectors (preview), max 10. These facilitate safe deployment practices (SDP) by enabling you to gradually roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location | `list(any)` | `[]` | no |
+| role_assignment_scope | The scope at which role definition(s) will be assigned, defaults to Policy Assignment Scope. Must be full resource IDs. Ignored when using Managed Identities. Changing this forces a new resource to be created | `string` | `null` | no |
+| role_definition_ids | List of Role definition ID's for the System Assigned Identity, defaults to roles included in the definition. Ignored when using Managed Identities. Changing this forces a new resource to be created | `list(any)` | `[]` | no |
+| skip_remediation | Should the module skip creation of a remediation task for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| skip_role_assignment | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
## Outputs
| Name | Description |
|------|-------------|
-| [id](#output\_id) | The Policy Assignment Id |
-| [identity\_id](#output\_identity\_id) | The Managed Identity block containing Principal Id & Tenant Id of this Policy Assignment if type is SystemAssigned |
-| [remediation\_id](#output\_remediation\_id) | The Id of the remediation task |
-| [role\_definition\_ids](#output\_role\_definition\_ids) | The List of Role Definition Ids assignable to the managed identity |
+| id | The Policy Assignment Id |
+| identity_id | The Managed Identity block containing Principal Id & Tenant Id of this Policy Assignment if type is SystemAssigned |
+| remediation_id | The Id of the remediation task |
+| role_definition_ids | The List of Role Definition Ids assignable to the managed identity |
+
\ No newline at end of file
diff --git a/modules/def_assignment/variables.tf b/modules/def_assignment/variables.tf
index 224b50b..e48360c 100644
--- a/modules/def_assignment/variables.tf
+++ b/modules/def_assignment/variables.tf
@@ -41,7 +41,7 @@ variable "assignment_effect" {
variable "assignment_parameters" {
type = any
description = "The policy assignment parameters. Changing this forces a new resource to be created"
- default = null
+ default = {}
}
variable "assignment_metadata" {
@@ -59,7 +59,7 @@ variable "assignment_enforcement_mode" {
variable "assignment_location" {
type = string
description = "The Azure location where this policy assignment should exist, required when an Identity is assigned. Defaults to UK South. Changing this forces a new resource to be created"
- default = "uksouth"
+ default = "westeurope"
}
variable "non_compliance_message" {
@@ -158,12 +158,12 @@ locals {
parameters = local.parameter_values != null ? var.assignment_effect != null ? jsonencode(merge(local.parameter_values, { effect = { value = var.assignment_effect } })) : jsonencode(local.parameter_values) : null
# create the optional non-compliance message contents block if present
- non_compliance_message = var.non_compliance_message != null ? { content = var.non_compliance_message } : {}
+ non_compliance_message = contains(["All", "Indexed"], try(var.definition.mode, "")) ? { content = try(coalesce(var.non_compliance_message, local.description, local.display_name, "Flagged by Policy: ${local.assignment_name}", "")) } : {}
# determine if a managed identity should be created with this assignment
identity_type = length(try(coalescelist(var.role_definition_ids, lookup(jsondecode(var.definition.policy_rule).then.details, "roleDefinitionIds", [])), [])) > 0 ? var.identity_ids != null ? { type = "UserAssigned" } : { type = "SystemAssigned" } : {}
- # try to use policy definition roles if explicit roles are ommitted
+ # try to use policy definition roles if explicit roles are omitted
role_definition_ids = var.skip_role_assignment == false && try(values(local.identity_type)[0], "") == "SystemAssigned" ? try(coalescelist(var.role_definition_ids, lookup(jsondecode(var.definition.policy_rule).then.details, "roleDefinitionIds", [])), []) : []
# policy assignment scope will be used if omitted
diff --git a/modules/definition/README.md b/modules/definition/README.md
index 49112bf..7103467 100644
--- a/modules/definition/README.md
+++ b/modules/definition/README.md
@@ -1,3 +1,4 @@
+
# POLICY DEFINITION MODULE
This module depends on populating `var.policy_name` and `var.policy_category` to correspond with the respective custom policy definition `json` file found in the [local library](../../policies). You can also parse in other template files and data sources at runtime, see below for examples and acceptable inputs.
@@ -53,7 +54,22 @@ module "file_path_test" {
}
```
+Loop around a folders contents to create multiple definitions:
+
+```hcl
+module "iam_test" {
+ source = "gettek/policy-as-code/azurerm//modules/definition"
+ for_each = {
+ for p in fileset(path.module, "../../azure/governance/policies/Storage/*.json") :
+ trimsuffix(basename(p), ".json") => pathexpand(p)
+ }
+ file_path = each.value
+ management_group_id = data.azurerm_management_group.org.id
+}
+```
+
You will also be able to supply object properties at runtime such as:
+
```hcl
locals {
policy_file = jsondecode(file("onboard_to_automation_dsc_linux.json"))
@@ -74,23 +90,14 @@ module "parameterised_test" {
}
```
-
## Requirements
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 0.13 |
-| [azurerm](#requirement\_azurerm) | >=3.23.0 |
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [azurerm](#provider\_azurerm) | 3.53.0 |
+| terraform | >= 0.13 |
+| azurerm | >=3.23.0 |
-## Modules
-No modules.
## Resources
@@ -102,25 +109,26 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [display\_name](#input\_display\_name) | Display Name to be used for this policy | `string` | `""` | no |
-| [file\_path](#input\_file\_path) | The filepath to the custom policy. Omitting this assumes the policy is located in the module library | `any` | `null` | no |
-| [management\_group\_id](#input\_management\_group\_id) | The management group scope at which the policy will be defined. Defaults to current Subscription if omitted. Changing this forces a new resource to be created. | `string` | `null` | no |
-| [policy\_category](#input\_policy\_category) | The category of the policy, when using the module library this should correspond to the correct category folder under /policies/var.policy\_category | `string` | `null` | no |
-| [policy\_description](#input\_policy\_description) | Policy definition description | `string` | `""` | no |
-| [policy\_metadata](#input\_policy\_metadata) | The metadata for the policy definition. This is a JSON object representing additional metadata that should be stored with the policy definition. Omitting this will fallback to meta in the definition or merge var.policy\_category and var.policy\_version | `any` | `null` | no |
-| [policy\_mode](#input\_policy\_mode) | Specify which Resource Provider modes will be evaluated, defaults to All. Possible values are All, Indexed, Microsoft.Kubernetes.Data, Microsoft.KeyVault.Data or Microsoft.Network.Data | `string` | `null` | no |
-| [policy\_name](#input\_policy\_name) | Name to be used for this policy, when using the module library this should correspond to the correct category folder under /policies/policy\_category/policy\_name. Changing this forces a new resource to be created. | `string` | `""` | no |
-| [policy\_parameters](#input\_policy\_parameters) | Parameters for the policy definition. This field is a JSON object that allows you to parameterise your policy definition. Omitting this assumes the parameters are located in /policies/var.policy\_category/var.policy\_name.json | `any` | `null` | no |
-| [policy\_rule](#input\_policy\_rule) | The policy rule for the policy definition. This is a JSON object representing the rule that contains an if and a then block. Omitting this assumes the rules are located in /policies/var.policy\_category/var.policy\_name.json | `any` | `null` | no |
-| [policy\_version](#input\_policy\_version) | The version for this policy, if different from the one stored in the definition metadata, defaults to 1.0.0 | `string` | `null` | no |
+| display_name | Display Name to be used for this policy | `string` | `""` | no |
+| file_path | The filepath to the custom policy. Omitting this assumes the policy is located in the module library | `any` | `null` | no |
+| management_group_id | The management group scope at which the policy will be defined. Defaults to current Subscription if omitted. Changing this forces a new resource to be created. | `string` | `null` | no |
+| policy_category | The category of the policy, when using the module library this should correspond to the correct category folder under /policies/ | `string` | `null` | no |
+| policy_description | Policy definition description | `string` | `""` | no |
+| policy_metadata | The metadata for the policy definition. This is a JSON object representing additional metadata that should be stored with the policy definition. Omitting this will fallback to meta in the definition or merge var.policy_category and var.policy_version | `any` | `null` | no |
+| policy_mode | Specify which Resource Provider modes will be evaluated, defaults to All. Possible values are All, Indexed, Microsoft.Kubernetes.Data, Microsoft.KeyVault.Data or Microsoft.Network.Data | `string` | `null` | no |
+| policy_name | Name to be used for this policy, when using the module library this should correspond to the correct category folder under /policies/policy_category/policy_name. Changing this forces a new resource to be created. | `string` | `""` | no |
+| policy_parameters | Parameters for the policy definition. This field is a JSON object representing the parameters of your policy definition. Omitting this assumes the parameters are located in the policy file | `any` | `null` | no |
+| policy_rule | The policy rule for the policy definition. This is a JSON object representing the rule that contains an if and a then block. Omitting this assumes the rules are located in the policy file | `any` | `null` | no |
+| policy_version | The version for this policy, if different from the one stored in the definition metadata, defaults to 1.0.0 | `string` | `null` | no |
## Outputs
| Name | Description |
|------|-------------|
-| [definition](#output\_definition) | The combined Policy Definition resource node |
-| [id](#output\_id) | The Id of the Policy Definition |
-| [metadata](#output\_metadata) | The metadata of the Policy Definition |
-| [name](#output\_name) | The name of the Policy Definition |
-| [parameters](#output\_parameters) | The parameters of the Policy Definition |
-| [rules](#output\_rules) | The rules of the Policy Definition |
+| definition | The combined Policy Definition resource node |
+| id | The Id of the Policy Definition |
+| metadata | The metadata of the Policy Definition |
+| name | The name of the Policy Definition |
+| parameters | The parameters of the Policy Definition |
+| rules | The rules of the Policy Definition |
+
\ No newline at end of file
diff --git a/modules/definition/outputs.tf b/modules/definition/outputs.tf
index e1c8623..c681dfc 100644
--- a/modules/definition/outputs.tf
+++ b/modules/definition/outputs.tf
@@ -30,7 +30,7 @@ output "definition" {
name = local.policy_name
display_name = local.display_name
description = local.description
- mode = var.policy_mode
+ mode = local.mode
management_group_id = var.management_group_id
metadata = jsonencode(local.metadata)
parameters = jsonencode(local.parameters)
diff --git a/modules/definition/variables.tf b/modules/definition/variables.tf
index b612e55..e6a17ce 100644
--- a/modules/definition/variables.tf
+++ b/modules/definition/variables.tf
@@ -50,7 +50,7 @@ variable "policy_mode" {
variable "policy_category" {
type = string
- description = "The category of the policy, when using the module library this should correspond to the correct category folder under /policies/var.policy_category"
+ description = "The category of the policy, when using the module library this should correspond to the correct category folder under /policies/"
default = null
}
@@ -62,13 +62,13 @@ variable "policy_version" {
variable "policy_rule" {
type = any
- description = "The policy rule for the policy definition. This is a JSON object representing the rule that contains an if and a then block. Omitting this assumes the rules are located in /policies/var.policy_category/var.policy_name.json"
+ description = "The policy rule for the policy definition. This is a JSON object representing the rule that contains an if and a then block. Omitting this assumes the rules are located in the policy file"
default = null
}
variable "policy_parameters" {
type = any
- description = "Parameters for the policy definition. This field is a JSON object that allows you to parameterise your policy definition. Omitting this assumes the parameters are located in /policies/var.policy_category/var.policy_name.json"
+ description = "Parameters for the policy definition. This field is a JSON object representing the parameters of your policy definition. Omitting this assumes the parameters are located in the policy file"
default = null
}
diff --git a/modules/exemption/README.md b/modules/exemption/README.md
index 5016de3..80d9c43 100644
--- a/modules/exemption/README.md
+++ b/modules/exemption/README.md
@@ -1,3 +1,4 @@
+
# POLICY EXEMPTION MODULE
Exemptions can be used where `not_scopes` become time sensitive or require alternative methods of approval for audit trails. Learn more about Azure Policy [exemption structure](https://learn.microsoft.com/en-us/azure/governance/policy/concepts/exemption-structure).
@@ -103,23 +104,14 @@ module exemption_team_a_mg_key_vaults_require_purge_protection {
}
```
-
## Requirements
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 0.13 |
-| [azurerm](#requirement\_azurerm) | >=3.23.0 |
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [azurerm](#provider\_azurerm) | 3.53.0 |
+| terraform | >= 0.13 |
+| azurerm | >=3.23.0 |
-## Modules
-No modules.
## Resources
@@ -134,19 +126,20 @@ No modules.
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [description](#input\_description) | Description for the Policy Exemption | `string` | n/a | yes |
-| [display\_name](#input\_display\_name) | Display name for the Policy Exemption | `string` | n/a | yes |
-| [exemption\_category](#input\_exemption\_category) | The policy exemption category. Possible values are Waiver or Mitigated. Defaults to Waiver | `string` | `"Waiver"` | no |
-| [expires\_on](#input\_expires\_on) | Optional expiration date (format yyyy-mm-dd) of the policy exemption. Defaults to no expiry | `string` | `null` | no |
-| [member\_definition\_names](#input\_member\_definition\_names) | Generate the definition reference Ids from the member definition names when 'policy\_definition\_reference\_ids' are unknown. Ommit to exempt all member definitions | `list(string)` | `[]` | no |
-| [metadata](#input\_metadata) | Optional policy exemption metadata. For example but not limited to; requestedBy, approvedBy, approvedOn, ticketRef, etc | `any` | `null` | no |
-| [name](#input\_name) | Name for the Policy Exemption | `string` | n/a | yes |
-| [policy\_assignment\_id](#input\_policy\_assignment\_id) | The ID of the policy assignment that is being exempted | `string` | n/a | yes |
-| [policy\_definition\_reference\_ids](#input\_policy\_definition\_reference\_ids) | The optional policy definition reference ID list when the associated policy assignment is an assignment of a policy set definition. Ommit to exempt all member definitions | `list(string)` | `[]` | no |
-| [scope](#input\_scope) | Scope for the Policy Exemption | `string` | n/a | yes |
+| description | Description for the Policy Exemption | `string` | n/a | yes |
+| display_name | Display name for the Policy Exemption | `string` | n/a | yes |
+| exemption_category | The policy exemption category. Possible values are Waiver or Mitigated. Defaults to Waiver | `string` | `"Waiver"` | no |
+| expires_on | Optional expiration date (format yyyy-mm-dd) of the policy exemption. Defaults to no expiry | `string` | `null` | no |
+| member_definition_names | Generate the definition reference Ids from the member definition names when 'policy_definition_reference_ids' are unknown. Omit to exempt all member definitions | `list(string)` | `[]` | no |
+| metadata | Optional policy exemption metadata. For example but not limited to; requestedBy, approvedBy, approvedOn, ticketRef, etc | `any` | `null` | no |
+| name | Name for the Policy Exemption | `string` | n/a | yes |
+| policy_assignment_id | The ID of the policy assignment that is being exempted | `string` | n/a | yes |
+| policy_definition_reference_ids | The optional policy definition reference ID list when the associated policy assignment is an assignment of a policy set definition. Omit to exempt all member definitions | `list(string)` | `[]` | no |
+| scope | Scope for the Policy Exemption | `string` | n/a | yes |
## Outputs
| Name | Description |
|------|-------------|
-| [exemption](#output\_exemption) | The Policy Exemption Details |
+| exemption | The Policy Exemption Details |
+
\ No newline at end of file
diff --git a/modules/exemption/variables.tf b/modules/exemption/variables.tf
index 802afd0..a401a0f 100644
--- a/modules/exemption/variables.tf
+++ b/modules/exemption/variables.tf
@@ -25,13 +25,13 @@ variable "policy_assignment_id" {
variable "policy_definition_reference_ids" {
type = list(string)
- description = "The optional policy definition reference ID list when the associated policy assignment is an assignment of a policy set definition. Ommit to exempt all member definitions"
+ description = "The optional policy definition reference ID list when the associated policy assignment is an assignment of a policy set definition. Omit to exempt all member definitions"
default = []
}
variable "member_definition_names" {
type = list(string)
- description = "Generate the definition reference Ids from the member definition names when 'policy_definition_reference_ids' are unknown. Ommit to exempt all member definitions"
+ description = "Generate the definition reference Ids from the member definition names when 'policy_definition_reference_ids' are unknown. Omit to exempt all member definitions"
default = []
}
diff --git a/modules/initiative/README.md b/modules/initiative/README.md
index 04b9550..f3a58f5 100644
--- a/modules/initiative/README.md
+++ b/modules/initiative/README.md
@@ -1,13 +1,21 @@
+
# POLICY INITIATIVE MODULE
Dynamically creates a policy set based on multiple custom or built-in policy definitions
> ⚠️ **Warning:** To simplify assignments, if any `member_definitions` contain the same parameter names they will be [merged](https://www.terraform.io/language/functions/merge) unless you specify `merge_effects = false` or `merge_parameters = false` as described in the second example below.
-> 💡 **Note:** Multiple entries of the same `member_definitions` are not currently supported, if you require the same definition to be present more than once you may use this module to create the initiative json which you can then edit to add unique parameter and definition references. Some examples can be found in discussion [#67](https://github.com/gettek/terraform-azurerm-policy-as-code/discussions/67)
-
## Examples
+### Create an Initiative with a duplicate member definitions
+
+In many cases, some initiatives such as those for tagging, may need to reuse the same definition multiple times but with different parameters to simplify assignments.
+
+Please see [duplicate_members.tf](../../examples/duplicate_members.tf) as en example use case.
+
+> 💡 **Note:** you must set `duplicate_members=true` and `merge_parameters=false` when building initiatives with duplicate members.
+> 💡 **Note:** Be cautious when changing the position of `member_definitions` as these reflect the index numbers used in `assignment_parameters`.
+
### Create an Initiative with custom Policy definitions
```hcl
@@ -63,7 +71,7 @@ output "list_of_initiative_parameters" {
}
```
-### Populate member_definitions with a for loop (not explicit)
+### Populate member_definitions with a for loop
```hcl
locals {
@@ -98,52 +106,47 @@ module guest_config_prereqs_initiative {
}
```
-
## Requirements
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 0.13 |
-| [azurerm](#requirement\_azurerm) | >=3.23.0 |
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [azurerm](#provider\_azurerm) | 3.53.0 |
+| terraform | >= 1.4 |
+| azurerm | >=3.23.0 |
-## Modules
-No modules.
## Resources
| Name | Type |
|------|------|
| [azurerm_policy_set_definition.set](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/policy_set_definition) | resource |
+| [terraform_data.set_replace](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [initiative\_category](#input\_initiative\_category) | The category of the initiative | `string` | `"General"` | no |
-| [initiative\_description](#input\_initiative\_description) | Policy initiative description | `string` | `""` | no |
-| [initiative\_display\_name](#input\_initiative\_display\_name) | Policy initiative display name | `string` | n/a | yes |
-| [initiative\_metadata](#input\_initiative\_metadata) | The metadata for the policy initiative. This is a JSON object representing additional metadata that should be stored with the policy initiative. Omitting this will default to merge var.initiative\_category and var.initiative\_version | `any` | `null` | no |
-| [initiative\_name](#input\_initiative\_name) | Policy initiative name. Changing this forces a new resource to be created | `string` | n/a | yes |
-| [initiative\_version](#input\_initiative\_version) | The version for this initiative, defaults to 1.0.0 | `string` | `"1.0.0"` | no |
-| [management\_group\_id](#input\_management\_group\_id) | The management group scope at which the initiative will be defined. Defaults to current Subscription if omitted. Changing this forces a new resource to be created. Note: if you are using azurerm\_management\_group to assign a value to management\_group\_id, be sure to use name or group\_id attribute, but not id. | `string` | `null` | no |
-| [member\_definitions](#input\_member\_definitions) | Policy Defenition resource nodes that will be members of this initiative | `any` | n/a | yes |
-| [merge\_effects](#input\_merge\_effects) | Should the module merge all member definition effects? Defauls to true | `bool` | `true` | no |
-| [merge\_parameters](#input\_merge\_parameters) | Should the module merge all member definition parameters? Defauls to true | `bool` | `true` | no |
+| duplicate_members | Does the Initiative contain duplicate member definitions? Defaults to false | `bool` | `false` | no |
+| initiative_category | The category of the initiative | `string` | `"General"` | no |
+| initiative_description | Policy initiative description | `string` | `""` | no |
+| initiative_display_name | Policy initiative display name | `string` | n/a | yes |
+| initiative_metadata | The metadata for the policy initiative. This is a JSON object representing additional metadata that should be stored with the policy initiative. Omitting this will default to merge var.initiative_category and var.initiative_version | `any` | `null` | no |
+| initiative_name | Policy initiative name. Changing this forces a new resource to be created | `string` | n/a | yes |
+| initiative_version | The version for this initiative, defaults to 1.0.0 | `string` | `"1.0.0"` | no |
+| management_group_id | The management group scope at which the initiative will be defined. Defaults to current Subscription if omitted. Changing this forces a new resource to be created. Note: if you are using azurerm_management_group to assign a value to management_group_id, be sure to use name or group_id attribute, but not id. | `string` | `null` | no |
+| member_definitions | Policy Definition resource nodes that will be members of this initiative | `list(any)` | n/a | yes |
+| merge_effects | Should the module merge all member definition effects? Defaults to true | `bool` | `true` | no |
+| merge_parameters | Should the module merge all member definition parameters? Defaults to true | `bool` | `true` | no |
## Outputs
| Name | Description |
|------|-------------|
-| [id](#output\_id) | The Id of the Policy Set Definition |
-| [initiative](#output\_initiative) | The combined Policy Initiative resource node |
-| [metadata](#output\_metadata) | The metadata of the Policy Set Definition |
-| [name](#output\_name) | The name of the Policy Set Definition |
-| [parameters](#output\_parameters) | The combined parameters of the Policy Set Definition |
-| [role\_definition\_ids](#output\_role\_definition\_ids) | Role definition IDs for remediation |
+| id | The Id of the Policy Set Definition |
+| initiative | The combined Policy Initiative resource node |
+| metadata | The metadata of the Policy Set Definition |
+| name | The name of the Policy Set Definition |
+| non_compliance_messages | Generated Key/Value map of non-compliance messages |
+| parameters | The combined parameters of the Policy Set Definition |
+| role_definition_ids | Role definition IDs for remediation |
+
\ No newline at end of file
diff --git a/modules/initiative/main.tf b/modules/initiative/main.tf
index e7337fa..2b72742 100644
--- a/modules/initiative/main.tf
+++ b/modules/initiative/main.tf
@@ -1,35 +1,35 @@
-resource "azurerm_policy_set_definition" "set" {
- name = var.initiative_name
- display_name = var.initiative_display_name
- description = var.initiative_description
- policy_type = "Custom"
+resource "terraform_data" "set_replace" {
+ input = md5(jsonencode(local.parameters))
+}
+resource "azurerm_policy_set_definition" "set" {
+ name = var.initiative_name
+ display_name = var.initiative_display_name
+ description = var.initiative_description
management_group_id = var.management_group_id
-
- metadata = jsonencode(local.metadata)
- parameters = length(local.parameters) > 0 ? jsonencode(local.parameters) : null
+ policy_type = "Custom"
+ metadata = jsonencode(local.metadata)
+ parameters = length(local.parameters) > 0 ? jsonencode(local.parameters) : null
dynamic "policy_definition_reference" {
- for_each = [for d in var.member_definitions : {
- id = d.id
- ref_id = replace(substr(title(replace(d.name, "/-|_|\\s/", " ")), 0, 64), "/\\s/", "")
- parameters = try(jsondecode(d.parameters), {})
- groups = []
- }]
-
+ for_each = local.member_properties
content {
policy_definition_id = policy_definition_reference.value.id
- reference_id = policy_definition_reference.value.ref_id
+ reference_id = policy_definition_reference.value.reference
parameter_values = length(policy_definition_reference.value.parameters) > 0 ? jsonencode({
for k in keys(policy_definition_reference.value.parameters) :
k => {
- value = k == "effect" && var.merge_effects == false ? "[parameters('${format("%s_%s", k, policy_definition_reference.value.ref_id)}')]" : var.merge_parameters == false ? "[parameters('${format("%s_%s", k, policy_definition_reference.value.ref_id)}')]" : "[parameters('${k}')]"
+ value = k == "effect" && var.merge_effects == false ? "[parameters('${k}_${policy_definition_reference.value.reference}')]" : var.merge_parameters == false ? "[parameters('${k}_${policy_definition_reference.value.reference}')]" : "[parameters('${k}')]"
}
}) : null
- policy_group_names = policy_definition_reference.value.groups
+ policy_group_names = []
}
}
+ lifecycle {
+ replace_triggered_by = [terraform_data.set_replace]
+ }
+
timeouts {
read = "10m"
}
diff --git a/modules/initiative/outputs.tf b/modules/initiative/outputs.tf
index 875281e..d5cec32 100644
--- a/modules/initiative/outputs.tf
+++ b/modules/initiative/outputs.tf
@@ -23,6 +23,11 @@ output "role_definition_ids" {
value = local.all_role_definition_ids
}
+output "non_compliance_messages" {
+ description = "Generated Key/Value map of non-compliance messages"
+ value = local.non_compliance_messages
+}
+
output "initiative" {
description = "The combined Policy Initiative resource node"
value = {
diff --git a/modules/initiative/variables.tf b/modules/initiative/variables.tf
index d85581e..7f20b5a 100644
--- a/modules/initiative/variables.tf
+++ b/modules/initiative/variables.tf
@@ -48,8 +48,8 @@ variable "initiative_version" {
}
variable "member_definitions" {
- type = any
- description = "Policy Defenition resource nodes that will be members of this initiative"
+ type = list(any)
+ description = "Policy Definition resource nodes that will be members of this initiative"
}
variable "initiative_metadata" {
@@ -60,55 +60,75 @@ variable "initiative_metadata" {
variable "merge_effects" {
type = bool
- description = "Should the module merge all member definition effects? Defauls to true"
+ description = "Should the module merge all member definition effects? Defaults to true"
default = true
}
variable "merge_parameters" {
type = bool
- description = "Should the module merge all member definition parameters? Defauls to true"
+ description = "Should the module merge all member definition parameters? Defaults to true"
default = true
}
+variable "duplicate_members" {
+ type = bool
+ description = "Does the Initiative contain duplicate member definitions? Defaults to false"
+ default = false
+}
+
locals {
- # colate all definition parameters into a single object
- member_parameters = {
- for d in var.member_definitions :
- d.name => try(jsondecode(d.parameters), {})
+ # colate all definition properties into a single reusable object
+ # index numbers (idx) will be prefixed to references when using duplicate member definitions
+ member_properties = {
+ for idx, d in var.member_definitions :
+ var.duplicate_members == false ? d.name : "${idx}_${d.name}" => {
+ id = d.id
+ reference = var.duplicate_members == false ? "${replace(substr(title(replace(d.name, "/-|_|\\s/", " ")), 0, 64), "/\\s/", "")}" : "${idx}_${replace(substr(title(replace(d.name, "/-|_|\\s/", " ")), 0, 61), "/\\s/", "")}"
+ parameters = coalesce(null, jsondecode(d.parameters), null)
+ mode = try(d.mode, "")
+ role_definition_ids = try(jsondecode(d.policy_rule).then.details.roleDefinitionIds, [])
+ non_compliance_message = try(jsondecode(d.metadata).non_compliance_message, d.description, d.display_name, "Flagged by Policy: ${d.name}")
+ }
}
# combine all discovered definition parameters using interpolation
parameters = merge(values({
- for definition, params in local.member_parameters :
+ for definition, properties in local.member_properties :
definition => {
- for parameter_name, parameter_value in params :
+ for parameter_name, parameter_value in properties.parameters :
# if do not merge parameters (or only effects) then suffix parameters with definition references
var.merge_parameters == false || parameter_name == "effect" && var.merge_effects == false ?
- "${parameter_name}_${replace(substr(title(replace(definition, "/-|_|\\s/", " ")), 0, 64), "/\\s/", "")}" :
+ "${parameter_name}_${properties.reference}" :
parameter_name => {
for k, v in parameter_value :
k => (
# if do not merge parameters (or only effects) then suffix displayNames with definition references
k == "metadata" && var.merge_parameters == false || var.merge_effects == false && try(v.displayName, "") == "Effect" ?
- merge(v, { displayName = "${v.displayName} For Policy: ${replace(substr(title(replace(definition, "/-|_|\\s/", " ")), 0, 64), "/\\s/", "")}" }) :
+ merge(v, { displayName = "${v.displayName} For Policy: ${properties.reference}" }) :
v
)
}
}
})...)
- # get role definition IDs
- role_definition_ids = {
- for d in var.member_definitions :
- d.name => try(jsondecode(d.policy_rule).then.details.roleDefinitionIds, [])
- }
-
- # combine all discovered role definition IDs
- all_role_definition_ids = try(distinct([for v in flatten(values(local.role_definition_ids)) : lower(v)]), [])
+ # combine all role definition IDs present in the policyRule
+ all_role_definition_ids = try(distinct([for v in flatten(values({
+ for k, v in local.member_properties :
+ k => v.role_definition_ids
+ })) : lower(v)]), [])
metadata = coalesce(null, var.initiative_metadata, merge({ category = var.initiative_category }, { version = var.initiative_version }))
+ # build non-compliance messages from metadata, or default to description/display_name if not present
+ non_compliance_messages = merge(
+ { null = "Flagged by Initiative: ${var.initiative_name}" }, # default non-compliance message
+ { for k, v in local.member_properties :
+ v.reference => v.non_compliance_message
+ if contains(["All", "Indexed"], v.mode) && var.duplicate_members == false # messages fail on other modes
+ }
+ )
+
# manually generate the initiative Id to prevent "Invalid for_each argument" on potential consumer modules
initiative_id = var.management_group_id != null ? "${var.management_group_id}/providers/Microsoft.Authorization/policySetDefinitions/${var.initiative_name}" : azurerm_policy_set_definition.set.id
}
diff --git a/modules/initiative/versions.tf b/modules/initiative/versions.tf
index b953190..ad88672 100644
--- a/modules/initiative/versions.tf
+++ b/modules/initiative/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 0.13"
+ required_version = ">= 1.4"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
diff --git a/modules/set_assignment/README.md b/modules/set_assignment/README.md
index d0b9a36..751f870 100644
--- a/modules/set_assignment/README.md
+++ b/modules/set_assignment/README.md
@@ -1,3 +1,4 @@
+
# POLICY INITIATIVE ASSIGNMENT MODULE
Assignments can be scoped from overarching management groups right down to individual resources
@@ -36,11 +37,9 @@ module org_mg_configure_asc_initiative {
data.azurerm_management_group.team_a.id
]
- # optional non-compliance messages. Key/Value pairs map as policy_definition_reference_id = 'content'
- non_compliance_messages = {
- null = "The Default non-compliance message for all member definitions"
- AutoEnrollSubscriptions = "The non-compliance message for the auto_enroll_subscriptions definition"
- }
+ # use the 'non_compliance_messages' output from the initiative module to use auto generated messages based off policy properties: descriptions/display names/custom ones found in metadata
+ # override with your own Key/Value pairs map as 'policy_definition_reference_id = content', use null = 'content' to specify the Default non-compliance message for all member definitions.
+ non_compliance_messages = module.configure_asc_initiative.non_compliance_messages
# optional overrides (preview)
overrides = [
@@ -107,23 +106,14 @@ module org_mg_configure_az_monitor_linux_vm_initiative {
}
```
-
## Requirements
| Name | Version |
|------|---------|
-| [terraform](#requirement\_terraform) | >= 0.13 |
-| [azurerm](#requirement\_azurerm) | >=3.49.0 |
-
-## Providers
-
-| Name | Version |
-|------|---------|
-| [azurerm](#provider\_azurerm) | 3.53.0 |
+| terraform | >= 1.4 |
+| azurerm | >=3.49.0 |
-## Modules
-No modules.
## Resources
@@ -138,43 +128,45 @@ No modules.
| [azurerm_role_assignment.rem_role](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/role_assignment) | resource |
| [azurerm_subscription_policy_assignment.set](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subscription_policy_assignment) | resource |
| [azurerm_subscription_policy_remediation.rem](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/subscription_policy_remediation) | resource |
+| [terraform_data.set_assign_replace](https://registry.terraform.io/providers/hashicorp/terraform/latest/docs/resources/data) | resource |
## Inputs
| Name | Description | Type | Default | Required |
|------|-------------|------|---------|:--------:|
-| [assignment\_description](#input\_assignment\_description) | A description to use for the Policy Assignment, defaults to initiative description. Changing this forces a new resource to be created | `string` | `null` | no |
-| [assignment\_display\_name](#input\_assignment\_display\_name) | The policy assignment display name, defaults to initiative display\_name. Changing this forces a new resource to be created | `string` | `null` | no |
-| [assignment\_effect](#input\_assignment\_effect) | The effect of the policy. Changing this forces a new resource to be created | `string` | `null` | no |
-| [assignment\_enforcement\_mode](#input\_assignment\_enforcement\_mode) | Control whether the assignment is enforced | `bool` | `true` | no |
-| [assignment\_location](#input\_assignment\_location) | The Azure location where this policy assignment should exist, required when an Identity is assigned. Defaults to UK South. Changing this forces a new resource to be created | `string` | `"uksouth"` | no |
-| [assignment\_metadata](#input\_assignment\_metadata) | The optional metadata for the policy assignment. | `any` | `null` | no |
-| [assignment\_name](#input\_assignment\_name) | The name which should be used for this Policy Assignment, defaults to initiative name. Changing this forces a new Policy Assignment to be created | `string` | `null` | no |
-| [assignment\_not\_scopes](#input\_assignment\_not\_scopes) | A list of the Policy Assignment's excluded scopes. Must be full resource IDs | `list(any)` | `[]` | no |
-| [assignment\_parameters](#input\_assignment\_parameters) | The policy assignment parameters. Changing this forces a new resource to be created | `any` | `null` | no |
-| [assignment\_scope](#input\_assignment\_scope) | The scope at which the policy initiative will be assigned. Must be full resource IDs. Changing this forces a new resource to be created | `string` | n/a | yes |
-| [failure\_percentage](#input\_failure\_percentage) | (Optional) A number between 0.0 to 1.0 representing the percentage failure threshold. The remediation will fail if the percentage of failed remediation operations (i.e. failed deployments) exceeds this threshold. | `number` | `null` | no |
-| [identity\_ids](#input\_identity\_ids) | Optional list of User Managed Identity IDs which should be assigned to the Policy Initiative | `list(any)` | `null` | no |
-| [initiative](#input\_initiative) | Policy Initiative resource node | `any` | n/a | yes |
-| [location\_filters](#input\_location\_filters) | Optional list of the resource locations that will be remediated | `list(any)` | `[]` | no |
-| [non\_compliance\_messages](#input\_non\_compliance\_messages) | The optional non-compliance message(s). Key/Value pairs map as policy\_definition\_reference\_id = 'content', use null = 'content' to specify the Default non-compliance message for all member definitions. | `any` | `{}` | no |
-| [overrides](#input\_overrides) | Optional list of assignment Overrides (preview), max 10. Allows you to change the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition | `list(any)` | `[]` | no |
-| [parallel\_deployments](#input\_parallel\_deployments) | (Optional) Determines how many resources to remediate at any given time. Can be used to increase or reduce the pace of the remediation. If not provided, the default parallel deployments value is used. | `number` | `null` | no |
-| [re\_evaluate\_compliance](#input\_re\_evaluate\_compliance) | Sets the remediation task resource\_discovery\_mode for policies that DeployIfNotExists and Modify. false = 'ExistingNonCompliant' and true = 'ReEvaluateCompliance'. Defaults to false. Applies at subscription scope and below | `bool` | `false` | no |
-| [remediation\_scope](#input\_remediation\_scope) | The scope at which the remediation tasks will be created. Must be full resource IDs. Defaults to the policy assignment scope. Changing this forces a new resource to be created | `string` | `null` | no |
-| [resource\_count](#input\_resource\_count) | (Optional) Determines the max number of resources that can be remediated by the remediation job. If not provided, the default resource count is used. | `number` | `null` | no |
-| [resource\_selectors](#input\_resource\_selectors) | Optional list of Resource selectors (preview), max 10. These facilitate safe deployment practices (SDP) by enabling you to gradually roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location | `list(any)` | `[]` | no |
-| [role\_assignment\_scope](#input\_role\_assignment\_scope) | The scope at which role definition(s) will be assigned, defaults to Policy Assignment Scope. Must be full resource IDs. Ignored when using Managed Identities. Changing this forces a new resource to be created | `string` | `null` | no |
-| [role\_definition\_ids](#input\_role\_definition\_ids) | List of Role definition ID's for the System Assigned Identity. Omit this to use those located in policy definitions. Ignored when using Managed Identities. Changing this forces a new resource to be created | `list(string)` | `[]` | no |
-| [skip\_remediation](#input\_skip\_remediation) | Should the module skip creation of a remediation task for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
-| [skip\_role\_assignment](#input\_skip\_role\_assignment) | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| assignment_description | A description to use for the Policy Assignment, defaults to initiative description. Changing this forces a new resource to be created | `string` | `null` | no |
+| assignment_display_name | The policy assignment display name, defaults to initiative display_name. Changing this forces a new resource to be created | `string` | `null` | no |
+| assignment_effect | The effect of the policy. Changing this forces a new resource to be created | `string` | `null` | no |
+| assignment_enforcement_mode | Control whether the assignment is enforced | `bool` | `true` | no |
+| assignment_location | The Azure location where this policy assignment should exist, required when an Identity is assigned. Defaults to West Europe. Changing this forces a new resource to be created | `string` | `"westeurope"` | no |
+| assignment_metadata | The optional metadata for the policy assignment. | `any` | `null` | no |
+| assignment_name | The name which should be used for this Policy Assignment, defaults to initiative name. Changing this forces a new Policy Assignment to be created | `string` | `null` | no |
+| assignment_not_scopes | A list of the Policy Assignment's excluded scopes. Must be full resource IDs | `list(any)` | `[]` | no |
+| assignment_parameters | The policy assignment parameters. Changing this forces a new resource to be created | `any` | `null` | no |
+| assignment_scope | The scope at which the policy initiative will be assigned. Must be full resource IDs. Changing this forces a new resource to be created | `string` | n/a | yes |
+| failure_percentage | (Optional) A number between 0.0 to 1.0 representing the percentage failure threshold. The remediation will fail if the percentage of failed remediation operations (i.e. failed deployments) exceeds this threshold. | `number` | `null` | no |
+| identity_ids | Optional list of User Managed Identity IDs which should be assigned to the Policy Initiative | `list(any)` | `null` | no |
+| initiative | Policy Initiative resource node | `any` | n/a | yes |
+| location_filters | Optional list of the resource locations that will be remediated | `list(any)` | `[]` | no |
+| non_compliance_messages | The optional non-compliance message(s). Key/Value pairs map as policy_definition_reference_id = 'content', use null = 'content' to specify the Default non-compliance message for all member definitions. | `any` | `{}` | no |
+| overrides | Optional list of assignment Overrides (preview), max 10. Allows you to change the effect of a policy definition without modifying the underlying policy definition or using a parameterized effect in the policy definition | `list(any)` | `[]` | no |
+| parallel_deployments | (Optional) Determines how many resources to remediate at any given time. Can be used to increase or reduce the pace of the remediation. If not provided, the default parallel deployments value is used. | `number` | `null` | no |
+| re_evaluate_compliance | Sets the remediation task resource_discovery_mode for policies that DeployIfNotExists and Modify. false = 'ExistingNonCompliant' and true = 'ReEvaluateCompliance'. Defaults to false. Applies at subscription scope and below | `bool` | `false` | no |
+| remediation_scope | The scope at which the remediation tasks will be created. Must be full resource IDs. Defaults to the policy assignment scope. Changing this forces a new resource to be created | `string` | `null` | no |
+| resource_count | (Optional) Determines the max number of resources that can be remediated by the remediation job. If not provided, the default resource count is used. | `number` | `null` | no |
+| resource_selectors | Optional list of Resource selectors (preview), max 10. These facilitate safe deployment practices (SDP) by enabling you to gradually roll out policy assignments based on factors like resource location, resource type, or whether a resource has a location | `list(any)` | `[]` | no |
+| role_assignment_scope | The scope at which role definition(s) will be assigned, defaults to Policy Assignment Scope. Must be full resource IDs. Ignored when using Managed Identities. Changing this forces a new resource to be created | `string` | `null` | no |
+| role_definition_ids | List of Role definition ID's for the System Assigned Identity. Omit this to use those located in policy definitions. Ignored when using Managed Identities. Changing this forces a new resource to be created | `list(string)` | `[]` | no |
+| skip_remediation | Should the module skip creation of a remediation task for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
+| skip_role_assignment | Should the module skip creation of role assignment for policies that DeployIfNotExists and Modify | `bool` | `false` | no |
## Outputs
| Name | Description |
|------|-------------|
-| [definition\_reference\_ids](#output\_definition\_reference\_ids) | The Member Definition Reference Ids |
-| [definition\_references](#output\_definition\_references) | The Member Definition References |
-| [id](#output\_id) | The Policy Assignment Id |
-| [principal\_id](#output\_principal\_id) | The Principal Id of this Policy Assignment's Managed Identity if type is SystemAssigned |
-| [remediation\_tasks](#output\_remediation\_tasks) | The Remediation Task Ids and related Policy Definition Ids |
+| definition_reference_ids | The Member Definition Reference Ids |
+| definition_references | The Member Definition References |
+| id | The Policy Assignment Id |
+| principal_id | The Principal Id of this Policy Assignment's Managed Identity if type is SystemAssigned |
+| remediation_tasks | The Remediation Task Ids and related Policy Definition Ids |
+
\ No newline at end of file
diff --git a/modules/set_assignment/main.tf b/modules/set_assignment/main.tf
index 8daa679..6d25ecd 100644
--- a/modules/set_assignment/main.tf
+++ b/modules/set_assignment/main.tf
@@ -1,3 +1,7 @@
+resource "terraform_data" "set_assign_replace" {
+ input = md5(jsonencode(var.initiative.parameters))
+}
+
resource "azurerm_management_group_policy_assignment" "set" {
count = local.assignment_scope.mg
name = local.assignment_name
@@ -12,7 +16,7 @@ resource "azurerm_management_group_policy_assignment" "set" {
location = local.assignment_location
dynamic "non_compliance_message" {
- for_each = local.non_compliance_message
+ for_each = var.non_compliance_messages
content {
content = non_compliance_message.value
policy_definition_reference_id = non_compliance_message.key == "null" ? null : non_compliance_message.key
@@ -49,6 +53,10 @@ resource "azurerm_management_group_policy_assignment" "set" {
}
}
}
+
+ lifecycle {
+ replace_triggered_by = [terraform_data.set_assign_replace]
+ }
}
resource "azurerm_subscription_policy_assignment" "set" {
@@ -65,7 +73,7 @@ resource "azurerm_subscription_policy_assignment" "set" {
location = local.assignment_location
dynamic "non_compliance_message" {
- for_each = local.non_compliance_message
+ for_each = var.non_compliance_messages
content {
content = non_compliance_message.value
policy_definition_reference_id = non_compliance_message.key == "null" ? null : non_compliance_message.key
@@ -102,6 +110,10 @@ resource "azurerm_subscription_policy_assignment" "set" {
}
}
}
+
+ lifecycle {
+ replace_triggered_by = [terraform_data.set_assign_replace]
+ }
}
resource "azurerm_resource_group_policy_assignment" "set" {
@@ -118,7 +130,7 @@ resource "azurerm_resource_group_policy_assignment" "set" {
location = local.assignment_location
dynamic "non_compliance_message" {
- for_each = local.non_compliance_message
+ for_each = var.non_compliance_messages
content {
content = non_compliance_message.value
policy_definition_reference_id = non_compliance_message.key == "null" ? null : non_compliance_message.key
@@ -155,6 +167,10 @@ resource "azurerm_resource_group_policy_assignment" "set" {
}
}
}
+
+ lifecycle {
+ replace_triggered_by = [terraform_data.set_assign_replace]
+ }
}
resource "azurerm_resource_policy_assignment" "set" {
@@ -171,7 +187,7 @@ resource "azurerm_resource_policy_assignment" "set" {
location = local.assignment_location
dynamic "non_compliance_message" {
- for_each = local.non_compliance_message
+ for_each = var.non_compliance_messages
content {
content = non_compliance_message.value
policy_definition_reference_id = non_compliance_message.key == "null" ? null : non_compliance_message.key
@@ -208,6 +224,10 @@ resource "azurerm_resource_policy_assignment" "set" {
}
}
}
+
+ lifecycle {
+ replace_triggered_by = [terraform_data.set_assign_replace]
+ }
}
## role assignments ##
diff --git a/modules/set_assignment/variables.tf b/modules/set_assignment/variables.tf
index 85893a3..9d0979f 100644
--- a/modules/set_assignment/variables.tf
+++ b/modules/set_assignment/variables.tf
@@ -58,8 +58,8 @@ variable "assignment_enforcement_mode" {
variable "assignment_location" {
type = string
- description = "The Azure location where this policy assignment should exist, required when an Identity is assigned. Defaults to UK South. Changing this forces a new resource to be created"
- default = "uksouth"
+ description = "The Azure location where this policy assignment should exist, required when an Identity is assigned. Defaults to West Europe. Changing this forces a new resource to be created"
+ default = "westeurope"
}
variable "non_compliance_messages" {
@@ -163,16 +163,10 @@ locals {
# merge effect and parameter_values if specified, will use definition default effects if omitted
parameters = local.parameter_values != null ? var.assignment_effect != null ? jsonencode(merge(local.parameter_values, { effect = { value = var.assignment_effect } })) : jsonencode(local.parameter_values) : null
- # create the optional non-compliance message content block(s) if present
- non_compliance_message = var.non_compliance_messages != {} ? {
- for reference_id, message in var.non_compliance_messages :
- reference_id => message
- } : {}
-
# determine if a managed identity should be created with this assignment
identity_type = length(try(coalescelist(var.role_definition_ids, try(var.initiative.role_definition_ids, [])), [])) > 0 ? var.identity_ids != null ? { type = "UserAssigned" } : { type = "SystemAssigned" } : {}
- # try to use policy definition roles if explicit roles are ommitted
+ # try to use policy definition roles if explicit roles are omitted
role_definition_ids = var.skip_role_assignment == false && try(values(local.identity_type)[0], "") == "SystemAssigned" ? try(coalescelist(var.role_definition_ids, try(var.initiative.role_definition_ids, [])), []) : []
# assignment location is required when identity is specified
@@ -211,7 +205,7 @@ locals {
azurerm_subscription_policy_assignment.set[0],
azurerm_resource_group_policy_assignment.set[0],
azurerm_resource_policy_assignment.set[0],
- "")
+ {})
remediation_tasks = try(
azurerm_management_group_policy_remediation.rem,
azurerm_subscription_policy_remediation.rem,
diff --git a/modules/set_assignment/versions.tf b/modules/set_assignment/versions.tf
index b8e9855..a618a3e 100644
--- a/modules/set_assignment/versions.tf
+++ b/modules/set_assignment/versions.tf
@@ -1,5 +1,5 @@
terraform {
- required_version = ">= 0.13"
+ required_version = ">= 1.4"
required_providers {
azurerm = {
source = "hashicorp/azurerm"
diff --git a/policies/README.md b/policies/README.md
index 7646bbb..9fbd6d2 100644
--- a/policies/README.md
+++ b/policies/README.md
@@ -1,6 +1,6 @@
# Custom Policy Definition Library
-Compile time: 05/17/2023 12:18:07 UTC
+Compile time: 02/15/2024 11:33:43 UTC
Example custom definitions located in the local library
## Categories
diff --git a/scripts/build_machine_config_packages.ps1 b/scripts/build_machine_config_packages.ps1
index 98ccaa7..7d1f235 100644
--- a/scripts/build_machine_config_packages.ps1
+++ b/scripts/build_machine_config_packages.ps1
@@ -61,19 +61,25 @@ if ($env:checkDependancies) {
'xWebAdministration'
'nx'
).ForEach({
- Write-Host "Checking dependancies for $_" -ForegroundColor Green
try {
- Find-Module -Name $_ | Select-Object Version, Name | ForEach-Object {
+ Find-Module -Name $_ -Verbose | ForEach-Object {
$installedVersion = (Get-InstalledModule -Name $_.Name -ErrorAction SilentlyContinue).Version
if (!($installedVersion)) {
Write-Host '🟢 Installing New Module' $_.Name $_.Version -ForegroundColor Green
- Install-Module $_.Name -Force -AcceptLicense -Confirm:$false -AllowClobber
}
elseif ($installedVersion -lt $_.Version) {
- Write-Host '🔷 Updating' $_.Name 'to the latest version:' $_.Version -ForegroundColor Blue
- Update-Module -Name $_.Name -Force -AcceptLicense -Confirm:$false
+ Write-Host '🔷 Updating' $_.Name $installedVersion '->' $_.Version -ForegroundColor Blue
}
- Import-Module $_.Name
+ $command = @{
+ Name = $_.Name
+ RequiredVersion = $_.Version
+ Scope = 'AllUsers'
+ Force = $true
+ AcceptLicense = $true
+ Confirm = $false
+ Verbose = $true
+ }
+ Install-Module @command
}
}
catch { Write-Host "🥵 Could not install module: $_" -ForegroundColor Red }
diff --git a/scripts/precommit.ps1 b/scripts/precommit.ps1
index e76da5d..558f3ae 100644
--- a/scripts/precommit.ps1
+++ b/scripts/precommit.ps1
@@ -7,8 +7,12 @@ Param(
[switch] [Parameter(Mandatory = $False)] $library
)
+$docConfigs = Resolve-Path -Path "$PSScriptRoot/../.config"
+$modules = Resolve-Path -Path "$PSScriptRoot/../modules"
+$examples = Resolve-Path -Path "$PSScriptRoot/../"
+
# Modules
-Push-Location -Path $PSScriptRoot/../modules
+Push-Location $modules
(Get-ChildItem -Directory).BaseName | Foreach-Object {
try {
Push-Location -Path $_
@@ -19,7 +23,11 @@ Push-Location -Path $PSScriptRoot/../modules
terraform validate
}
Write-Host "📜 Generating '$_' Docs..." -ForegroundColor Magenta
- Get-Content TEMPLATE.md > README.md; "`n" >> README.md; terraform-docs md . >> README.md
+ terraform-docs `
+ -c "$docConfigs\terraform-docs.yml" . `
+ --header-from "$docConfigs\templ-$_.md" `
+ --hide modules `
+ --output-mode replace | Out-Null
}
catch {
Write-Host "🥵 Could not complete precommit tasks: $_" -ForegroundColor Red
@@ -29,7 +37,7 @@ Push-Location -Path $PSScriptRoot/../modules
}
}
# Examples
-Push-Location -Path $PSScriptRoot/../
+Push-Location $examples
(Get-ChildItem -Directory -Path examples*).BaseName | Foreach-Object {
try {
Push-Location -Path $_
@@ -40,7 +48,10 @@ Push-Location -Path $PSScriptRoot/../
terraform validate
}
Write-Host "📜 Generating '$_' Docs..." -ForegroundColor Magenta
- Get-Content TEMPLATE.md > README.md; "`n" >> README.md; terraform-docs md . >> README.md
+ terraform-docs `
+ -c "$docConfigs\terraform-docs.yml" . `
+ --header-from "$docConfigs\templ-$_.md" `
+ --output-mode replace | Out-Null
}
catch {
Write-Host "🥵 Could not complete precommit tasks: $_" -ForegroundColor Red
@@ -52,6 +63,7 @@ Push-Location -Path $PSScriptRoot/../
# Return to original working directory
Pop-Location; Pop-Location
+
# Update custom definition library readme
if ($library) {
Write-Host "📜 Generating Custom Library Docs..." -ForegroundColor Magenta