Need help with dynamic blocks for firewall rules

I’m attempting to setup a pipeline to manage firewall rules on an Azure firewall. I have my core infrastructure setup via another pipeline, so this is only meant for firewall collection groups, rule collections, and rules.

I’d like to store the rules in csv files for ease of management and so that I can hand this over to someone else with relative ease.

The example I’ve been following is located here:

In the example, they use csvdecode to pull in the data and then a dynamic block to run through the setup of all the rules. They use the resource ‘azurerm_firewall_network_rule_collection’ but I’m using ‘azurerm_firewall_policy_rule_collection_group’ to the same effect and I’ve able to run this successfully. The main difference is the policy rule collection group requires a policy input and allows all rule types in the metadata blocks.

Now I want to expand on this by doing the following:

  • Have separate csv files for each rule type (network / app / dnat)
  • Include rule collection name, and rule collection priority in the csv files.
  • Include the rule collection group name, rule collection group priority in the csv files (eventually)

I’m tackling the above in order as it gets a bit trickier with each improvement. The aim is to have everything defined in the csv files.

I can simply add extra columns to the csv file and then include them in the section under “for ntwk_rule_iteration in local.ntwk_rules_data”. I believe I can also duplicate this and adjust the columns/variables for the app and dnat rules. But then how do I get the correct looping to work so that it will iterate for each rule in each file in the appropriate section of ‘azurerm_firewall_policy_rule_collection_group’?

My first step was to go from using a dynamic block for the rules, to now using it at the rule collection level, but that doesn’t loop through properly. This is what I’m working with:

(ignore the incorrect variables in the app_rules_data and dnat_rules_data sections for now)

locals {
ntwk_rules_data = csvdecode(file(“firewall_rules_network.csv”))
ntwk_rules = {
for ntwk_rule_iteration in local.ntwk_rules_data : “{ntwk_rule_iteration.collection_name}.{ntwk_rule_iteration.name}.{ntwk_rule_iteration.source_addresses}.{ntwk_rule_iteration.destination_addresses}.{ntwk_rule_iteration.protocols}.{ntwk_rule_iteration.destination_ports}.${ntwk_rule_iteration.priority}” => {
ntwk_rule_collection_name = ntwk_rule_iteration.collection_name
ntwk_rule_name = ntwk_rule_iteration.name
ntwk_rule_src_address = ntwk_rule_iteration.source_addresses
ntwk_rule_dst_address = ntwk_rule_iteration.destination_addresses
ntwk_rule_protocols = ntwk_rule_iteration.protocols
ntwk_rule_dst_ports = ntwk_rule_iteration.destination_ports
ntwk_rule_priority = ntwk_rule_iteration.priority
}
}

app_rules_data = csvdecode(file(“firewall_rules_app.csv”))
app_rules = {
for app_rule_iteration in local.app_rules_data : “{app_rule_iteration.collection_name}.{app_rule_iteration.name}.{app_rule_iteration.source_addresses}.{app_rule_iteration.destination_addresses}.{app_rule_iteration.protocols}.{app_rule_iteration.destination_ports}.${app_rule_iteration.priority}” => {
app_rule_collection_name = app_rule_iteration.collection_name
app_rule_name = app_rule_iteration.name
app_rule_src_address = app_rule_iteration.source_addresses
app_rule_dst_address = app_rule_iteration.destination_addresses
app_rule_protocols = app_rule_iteration.protocols
app_rule_dst_ports = app_rule_iteration.destination_ports
app_rule_priority = app_rule_iteration.priority
}
}

dnat_rules_data = csvdecode(file(“firewall_rules_dnat.csv”))
dnat_rules = {
for dnat_rule_iteration in local.dnat_rules_data : “{dnat_rule_iteration.collection_name}.{dnat_rule_iteration.name}.{dnat_rule_iteration.source_addresses}.{dnat_rule_iteration.destination_addresses}.{dnat_rule_iteration.protocols}.{dnat_rule_iteration.destination_ports}.${dnat_rule_iteration.priority}” => {
dnat_rule_collection_name = dnat_rule_iteration.collection_name
dnat_rule_name = dnat_rule_iteration.name
dnat_rule_src_address = dnat_rule_iteration.source_addresses
dnat_rule_dst_address = dnat_rule_iteration.destination_addresses
dnat_rule_protocols = dnat_rule_iteration.protocols
dnat_rule_dst_ports = dnat_rule_iteration.destination_ports
dnat_rule_priority = dnat_rule_iteration.priority
}
}
}

Creating the collection group and the rules within

resource “azurerm_firewall_policy_rule_collection_group” “firewall_rc” {
name = “Central_Firewall_Rules”
firewall_policy_id = data.azurerm_firewall_policy.central_firewall_policy.id
priority = “1000”

dynamic “network_rule_collection” {
for_each = local.ntwk_rules_data
content {
name = network_rule_collection.value.collection_name
priority = network_rule_collection.value.priority
action = “Allow”

  rule {
        name                  = network_rule_collection.value.name
        source_addresses      = [network_rule_collection.value.source_addresses]
        destination_ports     = [network_rule_collection.value.destination_ports]
        destination_addresses = [network_rule_collection.value.destination_addresses]
        protocols             = [network_rule_collection.value.protocols]
  }
}

}
}

Apologies for the syntax. Also some of the variables in the dynamic blocks may be wildly incorrect as I’ve been experimenting.