Network Security Group Rule Creation using Terraform

In this blog post I am going to create a set of Network Security Group rules in Terraform using the resource azurerm_network_security_rule and rather than copying this resource multiple times I will show how you can iterate over the same resource multiple times using for_each meta-argument in Terraform.

By default, a resource block configures one real infrastructure object. However, sometimes you want to manage several similar objects, such as a fixed pool of compute instances. Terraform has two ways to do this: count and for_each.

The for_each meta-argument accepts a map or a set of strings, and creates an instance for each item in that map or set. Each instance has a distinct infrastructure object associated with it (as described above in Resource Behavior), and each is separately created, updated, or destroyed when the configuration is applied.

Read further here

terraform.io

I decided to use a map so I can pass potentially separate settings to each Network Security Group (NSG) rule while using the key each time to create the rule.

azurerm_network_security_rule from Terraform

Lets look at azurerm_network_security_rule from terraform.io

resource "azurerm_resource_group" "example" {
  name     = "acceptanceTestResourceGroup1"
  location = "West US"
}

resource "azurerm_network_security_group" "example" {
  name                = "acceptanceTestSecurityGroup1"
  location            = azurerm_resource_group.example.location
  resource_group_name = azurerm_resource_group.example.name
}

resource "azurerm_network_security_rule" "example" {
  name                        = "test123"
  priority                    = 100
  direction                   = "Outbound"
  access                      = "Allow"
  protocol                    = "Tcp"
  source_port_range           = "*"
  destination_port_range      = "*"
  source_address_prefix       = "*"
  destination_address_prefix  = "*"
  resource_group_name         = azurerm_resource_group.example.name
  network_security_group_name = azurerm_network_security_group.example.name
}

For this blog post, I will keep the attributes/arguments as above. As previously mentioned; I could create multiple resources of azurerm_network_security_rule to deploy multiple NSG rules but over time your Terraform file would become quite congested depending on the size of environment you will be deploying.

Configuration Time

Inside locals.tf I will create a map nsgrules that will have 3 rules rdp, sql & http with the required attributes/arguments configured for each

locals.tf

locals { 
nsgrules = {
  
    rdp = {
      name                       = "rdp"
      priority                   = 100
      direction                  = "Inbound"
      access                     = "Allow"
      protocol                   = "Tcp"
      source_port_range          = "*"
      destination_port_range    = "3389"
      source_address_prefix      = "VirtualNetwork"
      destination_address_prefix = "*"
    }

    sql = {
      name                       = "sql"
      priority                   = 101
      direction                  = "Inbound"
      access                     = "Allow"
      protocol                   = "Tcp"
      source_port_range          = "*"
      destination_port_range     = "1433"
      source_address_prefix      = "SqlManagement"
      destination_address_prefix = "192.168.2.0/24"
    }

    http = {
      name                       = "http"
      priority                   = 201
      direction                  = "Inbound"
      access                     = "Allow"
      protocol                   = "Tcp"
      source_port_range          = "*"
      destination_port_range     = "80"
      source_address_prefix      = "*"
      destination_address_prefix = "192.168.2.0/24"
    }
  }

}

Now back to my Terraform file, in this case main.tf

  • Notice the reference of for_each used (will loop over the map nsgrules created in locals.tf)
  • Name has to be unique, using the key for each rule
  • resource_group_name & network_security_group_name is static and needs to be created prior, will include this in the final snippet of the code
resource "azurerm_network_security_rule" "testrules" {
  for_each                    = local.nsgrules 
  name                        = each.key
  direction                   = each.value.direction
  access                      = each.value.access
  priority                    = each.value.priority
  protocol                    = each.value.protocol
  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
  resource_group_name         = azurerm_resource_group.tamopsrg.name
  network_security_group_name = azurerm_network_security_group.tamopsnsg.name
}

Full code in main.tf

resource "azurerm_resource_group" "tamopsrg" {
  name     = "tamopsrg"
  location = "eastus2"
}

resource "azurerm_network_security_group" "tamopsnsg" {
  name                = "tamopstest"
  location            = "eastus2"
  resource_group_name = azurerm_resource_group.tamopsrg.name

}

resource "azurerm_network_security_rule" "testrules" {
  for_each                    = local.nsgrules 
  name                        = each.key
  direction                   = each.value.direction
  access                      = each.value.access
  priority                    = each.value.priority
  protocol                    = each.value.protocol
  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
  resource_group_name         = azurerm_resource_group.tamopsrg.name
  network_security_group_name = azurerm_network_security_group.tamopsnsg.name
}

Time to deploy

Deploying the above Terraform code will create:-

  • Resource Group:- tamopsrg
  • Network Security Group:- tamopstest
  • Network Security Group Ruleset from locals.tf

Reviewing NSG tamopstest you can see the ruleset from locals.tf has been applied successfully

Successfully deployed! – Hopefully an insight into how you can lyou can iterate over the same resource multiple times using for_each meta-argument in Terraform.

10 comments

  1. Hi Thomas.
    Thanks for this wonderful piece.

    Is there a way to have multiple nsg-rules declared in locals.tf (probably) and only use a few (maybe 3 out of 7 rules) rather than all; for a specific azurerm_network_security_group

    Thanks

  2. Hello Thomas,

    I got close to 50 ports which should have outbound rules and another 30 ports for inbound rules, is there a way to simply the code instead of providing all the ports in local file?

    I am wondering if we can have only one per direction and adding the ports in that variable, but not sure if that is possible. Can you please suggest?

    Regards,
    Janakiraman

    1. Hi Janakiram,

      Thanks for the comment. Sure you could split the below into two resources?

      Notice how I am adding direction = “inbound” & direction = “outbound?” . The for_each has been changed to include both local.nsgrulesinbound & local.nsgrulesoutbound

      resource “azurerm_network_security_rule” “inbound” {
      for_each = local.nsgrulesinbound
      name = each.key
      direction = “inbound”
      access = each.value.access
      priority = each.value.priority
      protocol = each.value.protocol
      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
      resource_group_name = azurerm_resource_group.tamopsrg.name
      network_security_group_name = azurerm_network_security_group.tamopsnsg.name
      }

      resource “azurerm_network_security_rule” “outbound” {
      for_each = local.nsgrulesoutbound
      name = each.key
      direction = “outbound”
      access = each.value.access
      priority = each.value.priority
      protocol = each.value.protocol
      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
      resource_group_name = azurerm_resource_group.tamopsrg.name
      network_security_group_name = azurerm_network_security_group.tamopsnsg.name
      }

      Let me know how you proceed

      Thanks

      Thomas

      1. Hello Thomas,

        Thanks for the quick update.
        I assume still in the locals.tf file, we have to provide with the all the details for my 100 ports. Is my understanding is correct? Is there a better way to handle it? Variable is difficult to manage and i am afraid it could lead to manual errors.

        locals {
        nsgrules = {

        rdp = {
        name = “rdp”
        priority = 100
        direction = “Inbound”
        access = “Allow”
        protocol = “Tcp”
        source_port_range = “*”
        destination_port_range = “3389”
        source_address_prefix = “VirtualNetwork”
        destination_address_prefix = “*”
        }

        …….

        Regards,
        Janakiraman

  3. Hello Thomas,

    The below code worked for me, almost similar to your suggestion. In my local variable, i have provided only the ports, priority and name alone. Thanks again for your support.

    # NSG rule to open ports for Web dispatcher
    resource “azurerm_network_security_rule” “webrule” {
    count = length(local.nsg-ports.webin)
    name = local.nsg-ports.webin[count.index].name
    priority = local.nsg-ports.webin[count.index].priority
    direction = “Inbound”
    access = “Allow”
    protocol = “Tcp”
    source_port_range = “*”
    destination_port_range = “*”
    source_address_prefix = “*”
    destination_address_prefix = “*”
    resource_group_name = “AUTOMATION_TESTRG”
    network_security_group_name = azurerm_network_security_group.web.name
    }

    # NSG rule to open ports for Web dispatcher
    resource “azurerm_network_security_rule” “webruleout” {
    count = length(local.nsg-port.web)
    name = local.nsg-port.web[count.index].name
    priority = local.nsg-port.web[count.index].priority
    direction = “Outbound”
    access = “Allow”
    protocol = “Tcp”
    source_port_range = “*”
    destination_port_range = “*”
    source_address_prefix = “*”
    destination_address_prefix = “*”
    resource_group_name = “AUTOMATION_TESTRG”
    network_security_group_name = azurerm_network_security_group.web.name
    }

    Regards,
    Janakiraman

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s