Multiple Azure subnets, NSGs and security ruled

Hi guys.

I am trying to create multiple Azure subnets, with separate NSGs attached to them, and dedicated security rules. I am able to create a single rule, but I don’t know how to iterate through the list of strings that contains port needed.

Here is my code:

terraform {
required_providers {
azurerm = {
source = “hashicorp/azurerm”
version = “~>3.27.0”
}
}
}

provider “azurerm” {
features {

}
}

provider “random” {
}

variable “subnets” {
type = map(any)
default = {
“front” = { prefix = [“10.0.0.0/24”], ports = [“80”, “443”] },
“middle” = { prefix = [“10.0.1.0/24”], ports = [“8080”, “8081”, “9090”, “9091”] },
“back” = { prefix = [“10.0.2.0/24”], ports = [“1433”, “3306”, “5432”] },
“bastion” = { prefix = [“192.168.0.0/24”], ports = [“443”, “22”] }
}
}

resource “azurerm_resource_group” “rg” {
//for_each = var.subnets
name = “rgspo00000001”
location = “North Europe”
}

resource “azurerm_network_security_group” “nsg” {
depends_on = [
azurerm_resource_group.rg
]
for_each = var.subnets
name = “${each.key}-nsg”
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
}

resource “azurerm_network_security_rule” “nsg_rules_inbound” {
depends_on = [
azurerm_network_security_group.nsg
]
for_each = var.subnets
name = “Inbound-{each.value.ports[0]}" priority = 100 direction = "Inbound" access = "Allow" protocol = "Tcp" source_port_range = "*" destination_port_range = each.value.ports[0] source_address_prefix = "*" destination_address_prefix = "*" resource_group_name = azurerm_resource_group.rg.name network_security_group_name = "{each.key}-nsg”
}

So, for example for subnet:

“middle” = { prefix = [“10.0.1.0/24”], ports = [“8080”, “8081”, “9090”, “9091”] },

I want to open 8080, 8081, 9090 and 9091.

Appreciate any help.

Not sure, but maybe some of this could be use/inspiration:

//////////////////////////////////
// Variables
//////////////////////////////////

locals {
  prefix   = "Example"
  location = "NorwayEast"
  
  virtual_network_name = "demo"
  address_space        = ["192.168.69.0/24"]
  
  subnets = [{
    name   = "front"
    prefixes = [cidrsubnet(element(local.address_space,0), 3, 0)]
  }, {
    name = "middle"
    prefixes = [cidrsubnet(element(local.address_space,0), 3, 2)]
  }]

  rules = [{
    nsg                        = "front" // same name as the subnet, in this example
    name                       = "rule-1"
    priority                   = 999
    protocol                   = "Tcp"
    direction                  = "Inbound"
    access                     = "Allow"
    source_address_prefix      = "*"
    source_port_range          = "*"
    destination_port_range     = "8080"
    destination_address_prefix = "*"
  }, {
    nsg                        = "middle"
    name                       = "rule-1"
    priority                   = 999
    protocol                   = "Tcp"
    direction                  = "Inbound"
    access                     = "Allow"
    source_address_prefix      = "*"
    source_port_range          = "*"
    destination_port_range     = "8080"
    destination_address_prefix = "*"
  }, {
    nsg                        = "middle"
    name                       = "rule-2"
    priority                   = 998
    protocol                   = "Tcp"
    direction                  = "Inbound"
    access                     = "Allow"
    source_address_prefix      = "*"
    source_port_range          = "*"
    destination_port_range     = "8081"
    destination_address_prefix = "*"
  }]
}

//////////////////////////////////
// NSG Rules
//////////////////////////////////

resource "azurerm_network_security_rule" "MAIN" {
  for_each = {
    for rule in local.rules: join("-", [rule.nsg, rule.name]) => rule
  }

  name                       = each.value["name"]
  priority                   = each.value["priority"]
  protocol                   = each.value["protocol"]
  direction                  = each.value["direction"]
  access                     = each.value["access"]
  source_port_range          = each.value["source_port_range"]
  destination_port_range     = each.value["destination_port_range"]
  source_address_prefix      = each.value["source_address_prefix"]
  destination_address_prefix = each.value["destination_address_prefix"]

  network_security_group_name = azurerm_network_security_group.MAIN[each.value["nsg"]].name
  resource_group_name         = azurerm_resource_group.MAIN.name
}

//////////////////////////////////
// VNET >> Subnet >> NSG
//////////////////////////////////

resource "azurerm_subnet_network_security_group_association" "AKS_SYSTEM" {
  for_each = azurerm_network_security_group.MAIN
  
  subnet_id                 = azurerm_subnet.MAIN[each.key].id
  network_security_group_id = azurerm_network_security_group.MAIN[each.key].id
}

resource "azurerm_network_security_group" "MAIN" {
  for_each = azurerm_subnet.MAIN

  name                = each.key
  location            = azurerm_resource_group.MAIN.location
  resource_group_name = azurerm_resource_group.MAIN.name
}

resource "azurerm_subnet" "MAIN" {
  for_each = {
    for subnet in local.subnets: subnet.name => subnet.prefixes
  }
  
  name                 = each.key
  address_prefixes     = each.value
  virtual_network_name = azurerm_virtual_network.MAIN.name
  resource_group_name  = azurerm_resource_group.MAIN.name
}

resource "azurerm_virtual_network" "MAIN" {
  name                = local.virtual_network_name
  address_space       = local.address_space
  location            = azurerm_resource_group.MAIN.location
  resource_group_name = azurerm_resource_group.MAIN.name
}

//////////////////////////////////
// Base resources
//////////////////////////////////

resource "azurerm_resource_group" "MAIN" {
  name     = join("-", [random_id.RG.keepers.name, random_id.RG.hex])
  location = local.location
}

// RG needs to have unique name
resource "random_id" "RG" {
  byte_length = 4
  
  keepers = {
    name = local.prefix
  }
}

//////////////////////////////////
// Provider
//////////////////////////////////

provider "azurerm" {
  features {}
}
1 Like

Hi.

Thanks for the reply. But, doesn’t this code require copy/paste and modification for every new deployment? I would like the code to be more generic, so for each new deployment which requires different ports, I only change ports in defined variable.

Does this make sense?
Thanks.

You can make it as generic as you like. I just used locals here since i was just copying an example, an i wasn’t quite able to understand your goal :slight_smile:

I’d also recommend avoiding using ‘depends_on’, unless it’s strictly required (normally you want terraform to resolve your dependencies)

Just put the stuff you want to be generic into variables, and update the code to correspond.

If you want to just replace some parametes, you can use e.g. ‘optional’ in the variables (terraform 1.3.0+):

variable "something" {
  type = object({
    name = string
    port = number
    something = optional(string, "default-value")
    anotherthing = optional(number, 8000)
  })
}