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
andfor_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 mapnsgrules
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.
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
Sorry Toppy, I missed this comment – could you define your full requirements?
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
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
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
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
Hi Janakiraman,
No problem – glad my blog post helped!
Thanks
Thomas
still gold!
Thanks!
Thank you Nico – glad it assisted you!
Thomas
Still gold!