- Azure Policy
Manage Azure Policy with Terraform
- Share on X/Twitter
- Share on Facebook
- Share on LinkedIn
- Share on Pinterest
Today we are going to talk about managing Azure Policy using Terraform. Azure Policy has a few components to it; Policy Definition, Policy Definition Set (also known as an Initiative), policy assignment, policy exemption and policy remediation. We will talk about all of these except for policy remediation.
I am going to reverse the order I would normally explain a concept, in this blog we will look at the call to the module first and then dive into each of the components.
The Module Call
Below we have a call to our azure-policy-initiative module, this module will create policies, assignments and exemptions for us. This is done by the use of an initiative definition . As can be seen, we are passing in three main things; assignment , exemptions , and the initiative_definition .
The Initiative Definition
Let's step into the initiative definition next.
There are four top-level keys that we have to set:
- name — unique name for the initiative.
- display_name — the name displayed with the initiative.
- description — a description of what the policy is for and does.
- policies — objects containing the value definitions of BuiltIn and Custom policies.
With these properties we will be able to pass in all of the relevant values into our policies for a range of environments, further, we can also set a default if we don't have values specific to an environment. Let's take a look at the two types of policy definitions.
First, let's take a look at the policy properties:
- type — the type of policy that we are referencing; Custom or BuiltIn .
- file — if the policy is Custom we require the name of the file to import. This will be forced into the ${path.root}/policies/ directory by the Terraform code.
- id — the GUID id of an existing Azure policy provided by Microsoft, this is required when type is set to BuiltIn .
- default — the default parameter and effect values.
- dev/uat/prd/... — the key is the environment and the keys must be the same as default , this provides an optional setting of policy parameters by the environment.
Inside the default or environment block we have the following few properties:
- parameters — a parameters object that MUST align to the parameters required by either the custom policy or an Azure BuiltIn policy.
- effect — the effect this policy will have, deny as an example. This property cannot be set on BuiltIn policies.
Custom Policy Definition
The key at the beginning AllowedLocations is how we will reference our policy and retrieve its components in the Terraform code. By allowing us to pass in the file to a json file it allows us to easily create custom policies alongside the fantastic baseline policies that Microsoft already give us. In the above example if we were running our Terraform code in the uat environment our code would use the properties we have defined in default as there are no environment-specific overrides. Allowing this makes our Terraform more powerful as perhaps when we start with Azure policy we don't necessarily understand what each environment requires, or they all explicitly require the same types of enforcement.
Built-in Policy Definition
The above is how we would set our parameters for a Built-in Azure policy. Remembering that we cannot set the effect of this policy as that is set by Microsoft, if you did need to alter that effect then it would be best to use a custom policy.
The Custom Policy Definition
We won't go into the mud on how to write an Azure Policy Definition if you're interested in that then check out the Azure Policy definition structure article by Microsoft.
The main point here is that you have a json definition of the Azure Policy either that you have written from scratch or perhaps you're pulling from the Azure portal so that you're now able to change the effect. As you can see on the high-lighted line below the value for the effect key is using string interpolation which will be set by templatefile in our Terraform code later. This is how we are going to be setting the effect on a per-environment basis.
Now we get into the fun stuff 🎉! Whilst going through the module I am going to split it up into some sub-sections to make it easier for us to talk through. Further, the module supports three scopes; Management Group ( mg ), Subscription ( sub ), and Resource Group ( rg ) I will just be referencing the resource group code below as it is almost identical to the other scopes.
The Module Interface
If you've worked with me or read my articles before you would know that I treat the variables.tf as our documented API interface, think of it like an OpenAPI definition for a REST API. We will go through each variable one by one.
Our first variable is initiative_definition this is where we pass the full path to our definition yaml file like we discussed in The Initiative Definition .
Secondly, we need to pass in an environment , this must be in whatever format you've used in the initiative definition otherwise our Terraform code won't be able to retrieve the properties for an environment.
The most important input variable for us is the assignment variable, this is where we pass in a single or list of resource IDs we are going to be assigning the policy initiative. Allowing a list of assignments means that we can deal with assignments on a larger scale than a single resource. This is especially powerful when operating in an enterprise environment.
The name property of the assignments object we will use as part of our exemptions process, this ensures there is an easy and intentional lookup for us when we are trying to exempt a resource from a given initiative.
We also have some validation ensuring that the scope passed in is valid for our scenario.
When Azure Policy is concerned there is always going to be a requirement to be able to exempt some resources from having that policy applied/enforced on them. We manage that here through the use of the exemptions variable. This variable allows us to pass in a list of our exemptions object. We have the assignment_reference which as we mentioned above is a reference to name in the assignments object. This allows us to cleanly look up which assignment we are looking to exempt a given resource for.
In this variable, we need to validate that our exemption scope is valid, not only valid for Azure but for our given scenario. For instance, you can exempt a single resource from a policy but our module only supports down to the resource group is the most granular level. The second thing we are validating is that the category on the exemption is one of the two valid strings as expected by Microsoft.
Local Variables and Setup
The first few pieces of setup that we are going to do is get some random_uuid 's setup that we can use for unique names of our policies, assignments and exemptions. Some properties in the azurerm the provider will auto-generate names for us, and others won't. In this instance, we are going to be dealing with the generation of the names.
Next, we need to decode our initiative_definition yaml into a Terraform object that we can use throughout our module. The policies local variable is a convenience variable for us so that we can quickly access the property. Also, if the way we access the policies object/key from our yaml file changes the code that consumes the policies doesn't need to know about that change.
Policy definitions
We use an azurerm_policy_definition resource for a Custom policy and the azurerm_policy_definition data source for our BuiltIn policies. Doing so allows us to support both in our module.
When we are creating a Custom policy we have an object that is the filename of a policy json file before creating these policy instances we need to complete the templatefile on each policy. We will loop through our local.policies object and decodes each file to json once the templatefile action has been performed and we have applied the effect either via a default key from our initiative definition or an environment-specific one. This will only occur when the type property is Custom . Then we simply take the properties from our json and plug them into the resource. Some properties such as; metadata , policy_rule , and parameters require to have jsonencode on the object we are retrieving from the policy json as when we do our for_each those are converted into objects that Terraform can deal with.
For the data source, we simply need to loop through our local.policies object and filter to only use objects where the type property is BuiltIn . We do this by using a for expression within the for_each block. You can read more about that in my post Terraform For Expressions .
Policy Initiative
Now that we have all of our policies in the state we require them its time to create our initiative and pass in the parameter values to each policy.
First off we will merge all our policies, both the resource and the data source. This will give us a single object to operate on. Using the new all_policies object we will get the parameter values, this will be environment specific if available otherwise it will return default . Having a pre-populated property for this allows for easy access within the azurerm_policy_set_definition resource.
Now we have two objects; all_policies and parameters these two combined are what allow us to set up all the policies within the initiative. Using a dynamic block -which you can read more about here - we will iterate over each policy in local.all_policies and assign the parameter_values from the local.parameters variable based on the key from our for_each . This is easily possible as when we created the local.parameters variable we did so by doing a for_each over the local.all_policies variable, this means that both the dynamic block and our parameters variable will use the same value as a key.
Policy Assignment
The actual policy assignment portion of the module is most likely the simplest part. In this, we simply for through the var.assignment.assignments list and return a map where the key is the name property and the value is the id property of our assignments object.
We do however do a check on scope to ensure that we are operating on the right scope for the right resource type. In this instance the resource group. If we were doing this on azurerm_management_group_policy_assignment the resource then our check would be if var.assignment.scope == "mg" . You can see that in the full module code the terraform-azurerm-policy-initiative repository on my GitHub.
Policy Exemption
The exemptions are where things get a little funkier, as we need to be able to match zero or more exemptions to the correct assignment.
Our first problem to solve is how we reference the correct Terraform resource block given each assignment type ( mg , sub , rg ) has its own Terraform resource. We do this by using the local variables' ability to reference a resource rather than a string. The try is important as Terraform will try to evaluate each of these even if they're not called which would be fine except that they will never all exist at the same time given assignment can only be done on a single scope.
With the above we can now access the right Terraform resource with the following:
To be honest, the ability to reference other resources with locals is INCREDIBLY powerful!!
Now that we can get the right policy assignment it's time to deal with the exemption side of things. For this, we are going to for through our assignments and our exemptions variables to create a new data structure containing all the relevant pieces of data. The assignment_id key will only ever return one value due to the use of the one function, this behavior is 💯 what we want if there was an instance where there were more than one assignment ID for a specific assignment_reference we would know someone has made a mistake. At this stage, we also validate that the assignment.scope is correct.
You can read more about the for expressions in my Terraform For Expressions post.
The name property is something that we construct out of the random_uuid for the exemptions as well as the last component of the resource ID. In the instance of a resource group that will be the name of the resource group. We also use this same logic to generate the id or key field on our for_each it is because of this that the resource we are referencing must exist before this code is run. If the resource does not exist then Terraform will error out saying that it is unable to determine the value of something that is part of the ID of a map. Whilst this behavior is not ideal I also don't think that it is that bad. The reason being is that should we ever try and exempt a policy on a resource that doesn't exist Terraform/Azure is going to wig out, therefore the behavior is more or less the same just at a different place in the run.
Closing Out
Today we have gone through a module I've created to deal with creating Azure Policy initiatives. We went through the initiative definition, the custom policy definition and the module itself. By using this module we are now easily able to deploy and manage Azure Policies and exemptions on our cloud platform at scale. We also ensured that we can have the right level of flexibility when it comes to setting the parameter values and the effects on an Azure Policy.
For me, this was not what I would call an easy module to write, as it required me to think about how I could get the most amount of configuration information into the module without making it overly complex to consume. However, going back to My Development Workflow helped me through the process. This module had four iterations before it got to what we have here today.
You can find this module at BrendanThompson/terraform-azurerm-policy-initiative
I would love to hear from you on if you think this module is useful and what you have done to manage something as complex as Azure Policy in your cloud environment!
Supporting the Blog
Did you enjoy this post or find it helpful? If so I would really appreciate your support by a small donation.
Schema validation for Terraform input configuration
Learn how to validate input configuration using CUE for Terraform. This guide covers creating a manifest in yaml, a schema with CUE and validating it. Enhance readability, control, and ensure correctness in your Terraform projects. Dive in to streamline your workflow!
How to structure Terraform code
Explore the evolving best practices for structuring Terraform code. This guide covers essential files like main.tf, variables.tf, and outputs.tf, offering rules and strategies to enhance your projects. Improve code quality, set standards, and streamline collaboration. Dive in to learn more!
What is Terraform state?
Constrained extension methods in swift.
Learn how to implement extension methods in Swift with specific constraints. This technique ensures that added functionalities are only available under certain conditions, enhancing code safety and reusability.
Subscribe to Newsletter
Join me on this exciting journey as we explore the boundless world of Terraform, Azure, Swift and Golang together.
Azure Policy Assignment
This page shows how to write Terraform and Azure Resource Manager for Policy Assignment and write them securely.
Review your .tf file for Azure best practices
Shisho Cloud, our free checker to make sure your Terraform configuration follows best practices, is available (beta).
azurerm_resource_policy_assignment (Terraform)
The Assignment in Policy can be configured in Terraform with the resource name azurerm_resource_policy_assignment . The following sections describe 1 example of how to use the resource and its parameters.
- Example Usage from GitHub
Review your Terraform file for Azure best practices
The following arguments are supported:
name - (Required) The name which should be used for this Policy Assignment. Changing this forces a new Resource Policy Assignment to be created.
policy_definition_id - (Required) The ID of the Policy Definition or Policy Definition Set. Changing this forces a new Policy Assignment to be created.
resource_id - (Required) The ID of the Resource (or Resource Scope) where this should be applied. Changing this forces a new Resource Policy Assignment to be created.
To create a Policy Assignment at a Management Group use the azurerm_management_group_policy_assignment resource, for a Resource Group use the azurerm_resource_group_policy_assignment and for a Subscription use the azurerm_subscription_policy_assignment resource.
description - (Optional) A description which should be used for this Policy Assignment.
display_name - (Optional) The Display Name for this Policy Assignment.
enforce - (Optional) Specifies if this Policy should be enforced or not?
identity - (Optional) An identity block as defined below.
- > Note: The location field must also be specified when identity is specified.
location - (Optional) The Azure Region where the Policy Assignment should exist. Changing this forces a new Policy Assignment to be created.
metadata - (Optional) A JSON mapping of any Metadata for this Policy.
not_scopes - (Optional) Specifies a list of Resource Scopes (for example a Subscription, or a Resource Group) within this Management Group which are excluded from this Policy.
parameters - (Optional) A JSON mapping of any Parameters for this Policy. Changing this forces a new Management Group Policy Assignment to be created.
A identity block supports the following:
- type - (Optional) The Type of Managed Identity which should be added to this Policy Definition. The only possible value is SystemAssigned .
In addition to the Arguments listed above - the following Attributes are exported:
- id - The ID of the Resource Policy Assignment.
The identity block exports the following:
principal_id - The Principal ID of the Policy Assignment for this Resource.
tenant_id - The Tenant ID of the Policy Assignment for this Resource.
>> from Terraform Registry
- Explanation in Terraform Registry
Manages a Policy Assignment to a Resource.
Microsoft.Authorization/policyAssignments (Azure Resource Manager)
The policyAssignments in Microsoft.Authorization can be configured in Azure Resource Manager with the resource name Microsoft.Authorization/policyAssignments . The following sections describe how to use the resource and its parameters.
The Other Related Azure Policy Resources
Azure Policy Configuration Policy Assignment
Azure Policy Definition
Azure Policy Policy Assignment
Azure Policy Remediation
Azure Policy Resource Group Policy Assignment
Azure Policy Set Definition
Azure Policy Subscription Policy Assignment
Azure Policy Virtual Machine Configuration Assignment
- Frequently asked questions
What is Azure Policy Assignment?
Azure Policy Assignment is a resource for Policy of Microsoft Azure. Settings can be wrote in Terraform.
Where can I find the example code for the Azure Policy Assignment?
For Terraform, the floriandorau/opa-aks source code example is useful. See the Terraform Example section for further details.
For Azure Resource Manager, the lolittle/azure , microsoft/azure_arc and karlochacon/my-arc-repo source code examples are useful. See the Azure Resource Manager Example section for further details.
Automate config file reviews on your commits
Fix issues in your infrastructure as code with auto-generated patches.
Table of Contents
Efficiently Creating Azure Management Groups In Terraform
As part of a recent project I have been writing a Terraform module to bring all of our tenant IAM settings into state. This includes amongst many other things Azure management groups.
However, avoiding copying a separate resource block for each management group and instead using a for_each loop led me to an interesting dilemma, namely…
- Management groups can be nested to a level of 6, each level depending on the level above to exist before it can be created. e.g you cant create a level 3 management group if you don’t have management groups at levels 1 & 2 already existing as parents.
- Nested management groups can get complex. Trying to create an efficient structure and work out the complete parental chain for a new management group in a Terraform loop turned out to be the biggest headache.
So, what was I ultimately trying to achieve? I wanted to be able to supply the following map (as an example of the structure) –
Which would produce the following –
Spoiler alert. I achieved it. Read on to find out how…
Firstly, lets tackle the slightly less complex issue of nesting and making sure things are created in the correct order. I did this by creating a resource block for each management group level, then adding a depends_on referencing the parent level. To start with, lets look at level 1 –
Initially I set a local containing the resource ID of the tenant root management group. Ultimately all other management groups we create will live under this.
Level 1 is then pretty simple, its just a case of looping through the var.management_groups variable at the top level and creating them.
Level 2 is where it starts to get a bit more complex – this is mostly due to setting the parent management group correctly and matching the Terraform map hierarchy with what ends up in Azure.
Firstly I create a local –
This local does two things – firstly, it creates a map of the children variable maps only, one level down for each parent value. e.g loop through each parent value, get the map set for the children variable and use it to create a new map containing only those results. This will become clearer in a minute after I visualize the changes.
Secondly, it adds to the key (set as the management group ID) the parent management group ID separated using a forward slash. Doing this ensures we can correctly set the parent_management_group_id when looping through the for_each in the management group resource block for the relevant level.
Note also the use of if can(value.children) , this skips any parents that don’t have children management groups.
So if var.management_groups was set as above then local.level_2 would contain the following –
Moving on to level 3, we use the exact same code however referencing the level 2 local, like so –
By referencing local.level_2 we continue the chain, getting the next level down children management groups.
This would result in local.level_3 containing the following –
This chain then continues down until level 6, which is the maximum depth supported for Azure management groups.
The resource block for creating the management groups after level 1 looks like the following –
In this example for level 2 I have used the basename() function to extract the last part of the key, being the current management group ID. basename() is designed for use with file paths, however works well in this scenario as the key is made up of the management group ID and parents using a forward slash as a delimiter, just like a file path.
For the parent_management_group_id I effectively need the inverse of the name within the key, so the equivalent of the “file” path without the filename itself. Terraform does have a function for this – dirname() which “takes a string containing a filesystem path and removes the last portion from it”, however it is unpredictable due to the path segment separator being different on Windows and Linux host systems.
Therefore I opted to use trimsuffix(each.key, "/${basename(each.key)}") which simply removes the basename(each.key) with a forward slash prefix, from the end of the each.key string containing the management group path. This would convert MH/MH-Prod/level-3 to MH/MH-Prod .
I then used this as the key selector in the parent management group level resource, so for level 2 its azurerm_management_group.level_1 . We can then get the .id value which should return and set the correct parent management group ID.
Links to the full code made up into a Terraform module can be found on my Github – https://github.com/mhosker/terraform-azure-management-groups
This browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Microsoft.Authorization policyAssignments
- 1 contributor
Bicep resource definition
The policyAssignments resource type is an extension resource , which means you can apply it to another resource.
Use the scope property on this resource to set the scope for this resource. See Set scope on extension resources in Bicep .
The policyAssignments resource type can be deployed with operations that target:
- Resource groups - See resource group deployment commands
- Subscriptions - See subscription deployment commands
- Management groups - See management group deployment commands
For a list of changed properties in each API version, see change log .
Resource format
To create a Microsoft.Authorization/policyAssignments resource, add the following Bicep to your template.
Property values
Policyassignments, identityuserassignedidentities, userassignedidentitiesvalue.
This object doesn't contain any properties to set during deployment. All properties are ReadOnly.
PolicyAssignmentProperties
Noncompliancemessage, parametervalues, parametervaluesvalue, resourceselector, quickstart templates.
The following quickstart templates deploy this resource type.
ARM template resource definition
Use the scope property on this resource to set the scope for this resource. See Set scope on extension resources in ARM templates .
To create a Microsoft.Authorization/policyAssignments resource, add the following JSON to your template.
Terraform (AzAPI provider) resource definition
Use the parent_id property on this resource to set the scope for this resource.
- Resource groups
- Subscriptions
- Management groups
To create a Microsoft.Authorization/policyAssignments resource, add the following Terraform to your template.
Was this page helpful?
IMAGES
VIDEO
COMMENTS
Module to assign Azure policies to Management groups based on a predefined Management group 'Type'. This module is intended to be used for assigning Azure Policies to Management Groups only. A defintion file, known as an 'archetype' is used to simplify the application of policies to management ...
The Terraform resources for Azure Policy use the Azure Provider. Create a new folder named policy-assignment and change directories into it. Create main.tf with the following code: Note. To create a Policy Assignment at a Management Group use the azurerm_management_group_policy_assignment resource, for a Resource Group use the azurerm_resource ...
I want to assign one of the built-in policies of Azure to a management group using Terraform. The problem I'm facing is, while assigning policies with Terraform can be fairly easily done by setting the scope properly to the subscription id or resource group or specific resource that the policy is to be applied upon, changing it to management ...
There's a maximum count 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:
Content Management. SQL Server. Microsoft Viva. Connect and learn from experts and peers . ... Considering Azure Policy, Terraform provides 4 different modules for different purpose. To find the arguments supported please refer inline links for the Terraform modules. ... azurerm_policy_assignment : To create Policy Assignments using Terraform ...
November 18, 2022 · 12 min read. Today we are going to talk about managing Azure Policy using Terraform. Azure Policy has a few components to it; Policy Definition, Policy Definition Set (also known as an Initiative), policy assignment, policy exemption and policy remediation. We will talk about all of these except for policy remediation.
In your /lib directory create a policy_set_definitions subdirectory.. NOTE: Creating a policy_set_definitions subdirectory is a recommendation only. If you prefer not to create one or to call it something else, the custom policies will still work. In the policy_set_definitions subdirectory, create a policy_set_definition_enforce_mandatory_tags.json file. This file will contain the Policy Set ...
azurerm_management_group_policy_assignment (Terraform) The Policy Assignment in Policy can be configured in Terraform with the resource name azurerm_management_group_policy_assignment. The following sections describe 10 examples of how to use the resource and its parameters.
In Terraform, there are 2 different resource blocks for Policy Assignments as well. azurerm_subscription_policy_assignment and azurerm_resource_group_policy_assignment. There is another called azurerm_management_group_policy_assignment if you have a management group for your subscriptions and want to assign your Policy at that level.
location - (Optional) The Azure Region where the Policy Assignment should exist. Changing this forces a new Policy Assignment to be created. metadata - (Optional) A JSON mapping of any Metadata for this Policy. not_scopes - (Optional) Specifies a list of Resource Scopes (for example a Subscription, or a Resource Group) within this Management ...
Now, owing to terraform's azurerm_policy_set_definition, (or we can assume the policy initiative definition to already exist as well) the entire policy initiative set can be assigned using the policy_assignment block where one can pass : resource azurerm_policy_assignment "polassgn1" { ...
Please refer to the Microsoft documentation for setting a value for parameters within a Policy Assignment template.. This approach can be used when adding new custom Policy Assignments to a custom lib folder, as specified by the library_path input variable.. NOTE: Whilst possible, we don't recommend using this approach if you want to set different values for custom Policy Assignments provided ...
Efficiently Creating Azure Management Groups In Terraform. February 24, 2024. As part of a recent project I have been writing a Terraform module to bring all of our tenant IAM settings into state. This includes amongst many other things Azure management groups. However, avoiding copying a separate resource block for each management group and ...
The display name of the policy assignment. string: enforcementMode: The policy assignment enforcement mode. Possible values are Default and DoNotEnforce. 'Default' 'DoNotEnforce' metadata: The policy assignment metadata. Metadata is an open ended object and is typically a collection of key value pairs. For Bicep, you can use the any() function.