In my previous blog, I introduced how to deploy resources in Azure via Terraform.

When deploying your resources, one crucial aspect to implement is guardrails. This is one of the critical security requirements when you deploy resources on an organizational level.

I will show how it can be done via Azure Policy and implemented via Terraform.

Understanding Guardrails:

In the context of cloud computing and infrastructure management, guardrails refer to a set of rules or policies that govern the deployment and operation of cloud resources.

They are proactive measures to ensure resources are within compliance standards and security policies for your organization to deploy securely in the public cloud space.

Where is it used in Azure?

In Azure, we can leverage Azure Policies to govern the configuration of the resources.

Policy-driven governance is one of the essential designs for their Cloud Adoption Framework (CAF). I will briefly show how it can be implemented in their enterprise-scale module toward the end of the article.

Azure Policies are written in JSON format to evaluate whether a resource is compliant.

Just an example of how the logic is written:

        "policyRule": {
"if": {
"not": {
"field": "location",
"in": "[parameters('allowedLocations')]"
}
},
"then": {
"effect": "deny"
}
}

It is a pretty straightforward policy. You can deploy as long the resource is within the allowed deployment location boundaries. If you are not in the permitted locations, you will be denied by the Policy.

In Azure, to implement any policies, there are two steps:

  1. Creating a Policy definition — that is where your policy logic comes in
  2. Assigning the Policy itself

How do you create and assign policies in the Azure portal?

You will see this page if you search for Policy in the search bar.

There are two options: you can create a Policy Definition or an Initiative definition. Let us skip the Initiative definition for now. Select to create Policy Definition.

For Policy Definition, it can be scoped to the management group level or subscription level. Here, I am scoping to my subscription.

In the policy rule section, you should see

Azure generates this policy rule by default. In line 11, change the effect value from "audit" to "deny." This will only allow the creation of resources if the policy rule is met. We will see this in effect shortly.

When you search for policies, you will now find your custom policy.

Click on the policy link and the Assign button.

If you notice, Azure allows you to scope the assignment on the entire subscription or the individual resource group.

Let us keep the scope on the subscription level.

Ignore the Advanced tab for now. I will write more about this setting in another article where I will dive deep into Azure Policies.

There is a possibility to narrow down the scope using Resource Selectors under the Advanced tab, however, it is currently in preview mode.

Under the Parameters tab, you can select the allowed locations. For this demo, I chose East US.

Click the Review+Create button below and click on Create on the subsequent page.

You will see this message in your notifications.

Deploy a Storage Account with Azure Policy in effect

I will use the previous setup that I have done. You can reference the setup here.

That is one of the benefits of Terraform; it is faster to test and deploy. :)

In main.tf , I change the location from eastus to southeastasia.

resource "azurerm_storage_account" "mystorage" {
name = "mystorage${random_string.storage_suffix.result}"
resource_group_name = azurerm_resource_group.sample_rg.name
location = "southeastasia"
account_tier = "Standard"
account_replication_type = "LRS"
}

Do note that if you change your existing storage account location, you will be forced to recreate the resource in Terraform. The -/+ indicator means that Terraform will destroy and recreate.

You will be faced with this error.

You might wonder how to make sense of this if you are new. It was not easy for me when I started.

Let me copy the section after AdditionalInfo to a JSON formatter.

{
"info": {
"evaluationDetails": {
"evaluatedExpressions": [
{
"expression": "location",
"expressionKind": "Field",
"expressionValue": "southeastasia",
"operator": "In",
"path": "location",
"result": "False",
"targetValue": ["eastus"]
}
]
},
"policyAssignmentDisplayName": "Only allow specific locations for resource deployment",
"policyAssignmentId": "/subscriptions/5b79d5a5-399e-439f-8baf-a8ca1654cc3b/providers/Microsoft.Authorization/policyAssignments/6aadfd1b92f54768b05e79e6",
"policyAssignmentName": "6aadfd1b92f54768b05e79e6",
"policyAssignmentParameters": { "allowedLocations": ["eastus"] },
"policyAssignmentScope": "/subscriptions/5b79d5a5-399e-439f-8baf-a8ca1654cc3b",
"policyDefinitionDisplayName": "Only allow specific locations for resource deployment",
"policyDefinitionEffect": "deny",
"policyDefinitionId": "/subscriptions/5b79d5a5-399e-439f-8baf-a8ca1654cc3b/providers/Microsoft.Authorization/policyDefinitions/1e45e263-bca5-4db0-a8c8-8b0f4dc0e07c",
"policyDefinitionName": "1e45e263-bca5-4db0-a8c8-8b0f4dc0e07c",
"policyExemptionIds": []
},
"type": "PolicyViolation"
}

After formatting, it makes more sense. You can see that the previous location I configured does not equal eastus, resulting in the Policy denying the resource creation.

Creating Azure Policy with Terraform

Now that you have seen the Policy in effect, how do we create it in Terraform? We use the azurerm_policy_definition resource block.

resource "azurerm_policy_definition" "testpolicy" {
name = "TestPolicy"
policy_type = "Custom"
mode = "All"
display_name = "Only allow specific locations for resource deployment"
metadata = <<METADATA
{
"category": "General",
"created_by": "Terraform"
}
METADATA

policy_rule = <<POLICY_RULE
{
"if": {
"not": {
"field": "location",
"in": "[parameters('allowedLocations')]"
}
},
"then": {
"effect": "[parameters('effect')]"
}
}
POLICY_RULE


parameters = <<PARAMETERS
{
"allowedLocations": {
"type": "Array",
"metadata": {
"displayName": "Allowed locations",
"description": "The list of allowed locations for resources.",
"strongType": "location"
}
},
"effect": {
"type": "String",
"metadata": {
"displayName": "Effect type for the policy",
"description": "Effect type for the policy"
},
"allowedValues": ["audit", "deny"]
}
}
PARAMETERS

}

I made a slight modification to the previous Policy Definition.

I added the metadata to show that Terraform creates it and added a new Parameter, "effect". This will allow me to toggle the policy effect during Policy Assignment.

The reason why I usually want to use the “audit” effect is to test the policy logic. I wont go into the details of Azure Policy structure here. However, if you would like to know more, you can refer to this documentation link.

Validate the Terraform script with terraform plan. If everything looks good, runterraform apply .

You will now see the same Policy Definition in the portal, with the metadata that says "created_by: Terraform" and 2 Available Effects.

Assigning Azure Policy with Terraform

To assign your Policy via Terraform

resource "azurerm_subscription_policy_assignment" "mypolicy_assignment" {
name = "mypolicy_assignment"
policy_definition_id = azurerm_policy_definition.testpolicy.id
subscription_id = data.azurerm_subscription.current.id
}

Remember earlier I mentioned there are 2 scopes the Policy Assignment can happen? 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.

Since I have 2 parameters in my Policy Definition, I must pass in 2 parameter values in my Policy Assignment.

After you run Terraform apply, you should see the Policy Assignment on your subscription (or Resource Group, depending on how you scope).

Deploying Azure Policy in Azure CAF

I will briefly discuss how you can integrate with the Azure CAF enterprise module.

The CAF module has many policies that will be deployed with the Landing Zones. However, CAF also allows you to add your own Custom Policies.

module "caf-enterprise-scale" {
source = "Azure/caf-enterprise-scale/azurerm"
version = "5.0.3"
default_location = "eastus"
root_parent_id = data.azurerm_client_config.core.tenant_id
root_id = "myorg"
root_name = "My Organization"

library_path = "${path.root}/customlib"

deploy_core_landing_zones = false
custom_landing_zones = {
"mysampleorg" = {
display_name = "${upper(var.root_id)} Example 1"
parent_management_group_id = ""
subscription_ids = [
#insert your subscription here
]
archetype_config = {
archetype_id = "example_archetype"
parameters = {}
access_control = {}
}
}
}

providers = {
azurerm = azurerm
azurerm.connectivity = azurerm
azurerm.management = azurerm
}
}

Here, I set deploy_core_landing_zones as false to not convolute with the Landing Zone Policies. Depending on your organizational requirements, you will add your custom policies to the Landing Zone Policies.

Inside custom_lib folder

$ tree -L 2
.
|-- archetype_definition
| `-- archetype_definition_example.json
|-- policy_definitions
| `-- policy_definition_es_enforce_storage_tags.json
`-- policy_set_definitions

I created a new Policy that checks if a Storage Account does not have any tags; it will deny the creation of the Storage Account.

{
"name": "Enforce-Storage-Tags",
"type": "Microsoft.Authorization/policyDefinitions",
"apiVersion": "2021-06-01",
"scope": null,
"properties": {
"displayName": "Storage must have mandatory tagging applied",
"policyType": "Custom",
"mode": "All",
"description": "Enforce tags on Storage Accounts",
"metadata": {
"version": "1.0.0",
"category": "Tags"
},
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Storage/storageAccounts"
},
{
"field": "tags",
"exists": false
}
]
},
"then": {
"effect": "deny"
}
},
"parameters": {}
}
}

In the archetype_definition_example.json, I just keyed in the Policy Definition name.

{
"example_archetype": {
"policy_assignments": [],
"policy_definitions": ["Enforce-Storage-Tags"],
"policy_set_definitions": [],
"role_definitions": [],
"archetype_config": {
"parameters": {},
"access_control": {}
}
}
}

After applying your deployment, you will then see your Custom Policy is now part of the management group (or Landing Zone, depending on how you configure it)

Conclusion

Understanding and implementing guardrails is critical in ensuring a secure, compliant, and efficient cloud environment.

Azure Policies provide a means for organizations to enforce compliance and governance standards, and Terraform can be used to implement policies in a scalable and efficient manner.

By leveraging the Azure CAF enterprise module, organizations can deploy policies at scale and ensure their resources are secure and compliant.

What challenges have you faced with Azure Policy or Terraform? Post in the comments below.

👋 If you find this helpful, please click the clap 👏 button below a few times to show your support for the author 👇

🚀Join FAUN Developer Community & Get Similar Stories in your Inbox Each Week

--

--