User Tools

Site Tools


burim:terraform:nsg

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, and rule properties
  • 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”) - INCLUDED
  • bastion_subnet (subnet_purpose = “AzureBastionSubnet”) - EXCLUDED due to filter
  • db_subnet (subnet_purpose = “Default”) - INCLUDED

Step 2: Second level of iteration

For each included subnet, loop through its security rules:

  • For web_subnet: allow_http and allow_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

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki