burim:terraform:nsg
Table of Contents
Terraform Network Security Rule Loop Explained
This documentation explains the complex for_each loop used to create network security rules in our Azure Network Manager module.
Loop Structure
resource "azurerm_network_security_rule" "this" {
for_each = {
for rule in flatten([
for subnet_key, subnet in var.subnets : [
for rule_key, rule_value in subnet.security_rules : {
subnet_key = subnet_key
rule_key = rule_key
rule = rule_value
}
] if subnet.subnet_purpose == "Default"
]) : "${rule.subnet_key}_${rule.rule_key}" => rule
}
# ... resource attributes ...
}
This loop performs the following operations:
- Iterates over each subnet in
var.subnets - Filters to only include subnets with
subnet_purpose == “Default” - For each qualified subnet, iterates over its
security_rules - Creates a flattened list of objects with
subnet_key,rule_key, andruleproperties - Uses
“${subnet_key}_${rule_key}”as the resource map key
Example Input
subnets = {
web_subnet = {
counter_subnet = "001"
prefix_len = 24
subnet_purpose = "Default"
security_rules = {
allow_http = {
counter_netsecrule = "001"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "Internet"
destination_address_prefix = "*"
description = "Allow HTTP from Internet"
},
allow_https = {
counter_netsecrule = "002"
priority = 110
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefix = "Internet"
destination_address_prefix = "*"
description = "Allow HTTPS from Internet"
}
}
},
bastion_subnet = {
counter_subnet = "002"
prefix_len = 27
subnet_purpose = "AzureBastionSubnet" # Not Default, will be filtered out
security_rules = {
allow_ssh = {
counter_netsecrule = "001"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "Internet"
destination_address_prefix = "*"
description = "Allow SSH from Internet"
}
}
},
db_subnet = {
counter_subnet = "003"
prefix_len = 24
subnet_purpose = "Default"
security_rules = {
allow_sql = {
counter_netsecrule = "001"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "1433"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "*"
description = "Allow SQL from VNet"
}
}
}
}
Step-by-Step Loop Execution
Step 1: First level of iteration
Loop through var.subnets:
web_subnet(subnet_purpose = “Default”) - INCLUDEDbastion_subnet(subnet_purpose = “AzureBastionSubnet”) - EXCLUDED due to filterdb_subnet(subnet_purpose = “Default”) - INCLUDED
Step 2: Second level of iteration
For each included subnet, loop through its security rules:
- For
web_subnet:allow_httpandallow_https - For
db_subnet:allow_sql
Step 3: Create flattened list
The flatten() function creates this list:
[
{
subnet_key = "web_subnet"
rule_key = "allow_http"
rule = { ... rule properties ... }
},
{
subnet_key = "web_subnet"
rule_key = "allow_https"
rule = { ... rule properties ... }
},
{
subnet_key = "db_subnet"
rule_key = "allow_sql"
rule = { ... rule properties ... }
]
Step 4: Create for_each map
The outer for expression creates this map:
{
"web_subnet_allow_http" = { subnet_key = "web_subnet", rule_key = "allow_http", rule = {...} }
"web_subnet_allow_https" = { subnet_key = "web_subnet", rule_key = "allow_https", rule = {...} }
"db_subnet_allow_sql" = { subnet_key = "db_subnet", rule_key = "allow_sql", rule = {...} }
}
Step 5: Create resources
The loop creates three NSG rules:
azurerm_network_security_rule.this[“web_subnet_allow_http”]azurerm_network_security_rule.this[“web_subnet_allow_https”]azurerm_network_security_rule.this[“db_subnet_allow_sql”]
Example Resource Output
For the first rule, the resource would be:
resource "azurerm_network_security_rule" "this" {
# Key: "web_subnet_allow_http"
name = "rule-app01-dev-weu-001" # Assuming these variables are set
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "Internet"
destination_address_prefix = "*"
description = "Allow HTTP from Internet"
resource_group_name = "my-resource-group" # From var.resource_group_name
network_security_group_name = "nsg-app01-dev-weu-001" # References the NSG created for web_subnet
}
Key Benefits of This Approach
- Filtered Creation: Only creates rules for subnets with
subnet_purpose == “Default”. - Association: Links each rule to its parent NSG using
network_security_group_name. - Unique Keys: Uses
${subnet_key}_${rule_key}to ensure uniquely named resources. - Optional Attributes: Uses
try()to handle optional destination address fields. - Independent Management: Each rule can be added, modified, or removed without affecting other rules.
- No Rule Recreation: When adding a new rule, existing rules remain untouched in Terraform.
This approach separates NSG rules from NSGs, which makes managing individual rules much easier and avoids the problem of recreating all rules when any single rule changes.
burim/terraform/nsg.txt · Last modified: 2025/08/28 15:53 by burim
