Dynamically managing rules in Azure Firewall in Terraform
Intro
The purpose of this post is to give you inspiration, on how to create a dynamic Terraform configuration for Azure Firewall rules.
Let me know if you have suggestions for improvements.
Disclaimer
The code has been used as PoC in my lab. It’s not meant for production.
Code
The code shown below is just the snippets of the configuration.
You can see the full code at the link down below: https://github.com/danielthorvil/danielthorvil.github.io/tree/main/assets/post-content/0003/tf/
main.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# Azure Firewall policy
resource "azurerm_firewall_policy" "azfw_policy" {
name = "${var.name-prefix}-azfw-policy"
resource_group_name = azurerm_resource_group.rg.name
location = var.region
sku = "Basic"
threat_intelligence_mode = "Alert"
}
# IP Groups
resource "azurerm_ip_group" "ip_groups" {
resource_group_name = azurerm_resource_group.rg.name
location = var.region
for_each = { for ipgroup in var.ip-groups : ipgroup.name => ipgroup.cidrs }
name = each.key
cidrs = each.value
}
locals {
# Used for lookup in the azurerm_firewall_policy_rule_collection_group.azfw_rules resource.
ip_group_mapping = { for ip_group in azurerm_ip_group.ip_groups : ip_group.name => ip_group.id }
}
# Rule collection groups, rule collections, rules
resource "azurerm_firewall_policy_rule_collection_group" "azfw_rules" {
firewall_policy_id = azurerm_firewall_policy.azfw_policy.id
for_each = { for ruleCollectionGroup in var.ruleCollectionGroups : ruleCollectionGroup.ruleCollectionGroupName => ruleCollectionGroup }
name = each.key
priority = each.value.ruleCollectionGroupPriority
dynamic "network_rule_collection" {
for_each = { for ruleCollection in coalesce(each.value.NetworkRuleCollections, []) : ruleCollection.ruleCollectionName => ruleCollection }
content {
name = network_rule_collection.value.ruleCollectionName
priority = network_rule_collection.value.ruleCollectionPriority
action = network_rule_collection.value.ruleCollectionAction
dynamic "rule" {
for_each = { for rule in network_rule_collection.value.rules : rule.name => rule }
content {
name = rule.value.name
description = rule.value.description
source_addresses = rule.value.source_addresses
source_ip_groups = [for ip_group in coalesce(rule.value.source_ip_groups, []) : lookup(local.ip_group_mapping, ip_group)]
destination_addresses = rule.value.destination_addresses
destination_ip_groups = [for ip_group in coalesce(rule.value.destination_ip_groups, []) : lookup(local.ip_group_mapping, ip_group)]
destination_fqdns = rule.value.destination_fqdns
protocols = rule.value.protocols
destination_ports = rule.value.destination_ports
}
}
}
}
dynamic "application_rule_collection" {
for_each = { for ruleCollection in coalesce(each.value.ApplicationRuleCollections, []) : ruleCollection.ruleCollectionName => ruleCollection if ruleCollection.ruleCollectionName != null }
content {
name = application_rule_collection.value.ruleCollectionName
priority = application_rule_collection.value.ruleCollectionPriority
action = application_rule_collection.value.ruleCollectionAction
dynamic "rule" {
for_each = { for rule in application_rule_collection.value.rules : rule.name => rule }
content {
name = rule.value.name
description = rule.value.description
source_addresses = rule.value.source_addresses
source_ip_groups = [for ip_group in coalesce(rule.value.source_ip_groups, []) : lookup(local.ip_group_mapping, ip_group)]
destination_fqdns = rule.value.destination_fqdns
dynamic "protocols" {
for_each = { for protocol in rule.value.protocols : "${protocol.type}+${protocol.port}" => protocol }
content {
type = protocols.value.type
port = protocols.value.port
}
}
}
}
}
}
}
terraform.tfvars
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
ip-groups = [
{
name = "cloudflareDnsResolvers"
cidrs = ["1.1.1.0/29", "1.0.0.0/29"]
},
{
name = "googleDnsResolvers"
cidrs = ["8.8.8.8/32", "8.8.4.4/32"]
},
{
name = "localNetworkSpoke01Subnet01"
cidrs = ["10.69.10.0/24"]
}]
ruleCollectionGroups = [{
ruleCollectionGroupName = "ruleCollectionGroup01"
ruleCollectionGroupPriority = 120
NetworkRuleCollections = [{
ruleCollectionName = "networkRuleCollection01"
ruleCollectionPriority = 150
ruleCollectionAction = "Allow"
rules = [{
name = "rule01"
description = "description"
source_addresses = ["10.69.69.0/24"]
destination_addresses = ["66.66.66.0/32"]
protocols = ["ICMP"]
destination_ports = ["*"]
},
{
name = "rule02"
description = "description"
source_addresses = ["10.69.69.0/24"]
destination_addresses = ["65.34.23.4/32"]
protocols = ["TCP"]
destination_ports = ["443"]
}]
}]
ApplicationRuleCollections = [{
ruleCollectionName = "applicationRuleCollection01"
ruleCollectionPriority = 160
ruleCollectionAction = "Allow"
rules = [{
name = "rule01"
description = "description"
source_addresses = ["10.66.66.0/24"]
destination_fqdns = ["google.com"]
protocols = [{ type = "Https", port = 443 }]
},
{
name = "rule02"
description = "description"
source_addresses = ["10.66.66.0/24"]
destination_fqdns = ["facebook.com"]
protocols = [{ type = "Http", port = 80 }]
}]
}]
},
{
ruleCollectionGroupName = "ruleCollectionGroup02"
ruleCollectionGroupPriority = 120
NetworkRuleCollections = [{
ruleCollectionName = "networkRuleCollection02"
ruleCollectionPriority = 150
ruleCollectionAction = "Allow"
rules = [{
name = "rule01"
description = "description"
source_addresses = ["10.69.69.0/24"]
destination_ip_groups = ["cloudflareDnsResolvers", "googleDnsResolvers"]
protocols = ["TCP", "UDP"]
destination_ports = ["53"]
}]
}]
ApplicationRuleCollections = [{
ruleCollectionName = "applicationRuleCollection02"
ruleCollectionPriority = 160
ruleCollectionAction = "Allow"
rules = [
{
name = "rule01"
description = "description"
source_ip_groups = ["localNetworkSpoke01Subnet01"]
destination_fqdns = ["youtube.com"]
protocols = [{ type = "Http", port = 80 }]
}]
}]
}]
variables.tf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
variable "ip-groups" {
description = "Contains the Azure IP Groups that should be created."
type = list(object({
name = string
cidrs = list(string)
}))
}
variable "ruleCollectionGroups" {
description = "Variable that contains rule collections AND the rules within"
type = list(object({
ruleCollectionGroupName = string
ruleCollectionGroupPriority = number
NetworkRuleCollections = optional(list(object({
ruleCollectionName = string
ruleCollectionPriority = number
ruleCollectionAction = string
rules = list(object({
name = string
description = string
source_addresses = optional(list(string))
source_ip_groups = optional(list(any))
destination_addresses = optional(list(string))
destination_ip_groups = optional(list(any))
destination_fqdns = optional(list(string))
protocols = list(string)
destination_ports = list(any)
})) })))
ApplicationRuleCollections = optional(list(object({
ruleCollectionName = string
ruleCollectionPriority = number
ruleCollectionAction = string
rules = list(object({
name = string
description = string
source_addresses = optional(list(string))
source_ip_groups = optional(list(any))
destination_fqdns = list(string)
protocols = list(object({
port = any
type = string
}))
})) })))
}))
}
This post is licensed under CC BY 4.0 by the author.