This guide looks at Optimising Azure NSG Rules Creation with Terraform. We’ll look at how to use the try function with for_each for Terraform NSG Rule Configuration and all arguments for azurerm_network_security_rule, providing a Step-by-Step Terraform NSG Guide for optimising Terraform Azure NSG Rules.
A follow on from my popular blog post: Network Security Group Rule Creation using Terraform
Why Use the try Function in Terraform for NSG Rules?
The try function in Terraform is essential for managing various possible configurations by allowing you to specify multiple arguments and use the first non-erroring value. This is particularly useful for handling optional arguments in Terraform NSG Rule Configuration.
In the context of Network Security Group (NSG) rule creation, it is common to encounter multiple optional values, such as source_port_range, source_port_ranges, destination_address_prefix, and destination_address_prefixes. By leveraging the try function, you can effectively manage these cases where values might not always be provided, making your configurations more robust and adaptable.
Code for Optimising Azure NSG Rules creation with Terraform
To demonstrate Advanced Azure NSG Rule Creation with Terraform, let’s start by setting up a Resource Group and NSG that will be used in our ruleset:
resource "azurerm_resource_group" "nsgrg" {
name = "tamops-nsg-rg"
location = "uksouth"
}
resource "azurerm_network_security_group" "nsg" {
name = "tamopsnsg"
location = azurerm_resource_group.nsgrg.location
resource_group_name = azurerm_resource_group.nsgrg.name
}
In this example, we create an Azure Resource Group and Network Security Group (NSG). These resources will serve as the foundation for our NSG ruleset.
How to Use for_each and try for Dynamic NSG Rule Creation
With the for_each and try functions, we can create dynamic and flexible NSG rules. Here’s how you can configure the azurerm_network_security_rule resource:
resource "azurerm_network_security_rule" "advanced_rules" {
for_each = local.nsgrules
name = each.key
direction = each.value.direction
access = each.value.access
priority = each.value.priority
protocol = each.value.protocol
resource_group_name = azurerm_resource_group.nsgrg.name
network_security_group_name = azurerm_network_security_group.nsg.name
source_port_range = try(each.value.source_port_range, null)
source_port_ranges = try(each.value.source_port_ranges, null)
destination_port_range = try(each.value.destination_port_range, null)
destination_port_ranges = try(each.value.destination_port_ranges, null)
source_address_prefix = try(each.value.source_address_prefix, null)
source_address_prefixes = try(each.value.source_address_prefixes, null)
destination_address_prefix = try(each.value.destination_address_prefix, null)
destination_address_prefixes = try(each.value.destination_address_prefixes, null)
source_application_security_group_ids = try(each.value.source_application_security_group_ids, null)
destination_application_security_group_ids = try(each.value.destination_application_security_group_ids, null)
}
Breaking it down:
- Network Security rule creation: A
for_eachmap will be passed that will contain all the necessary values needed to create the NSG ruleset - Using the
tryfunction: This is really where the magic happens, using this function throughout will determine which optional arguments to use
Defining NSG Rules with Maps and Key-Value Pairs in Terraform
We can create a map with a number of key-value pairs to define our NSG ruleset, allowing us to create both inbound and outbound rules together:
locals {
nsgrules = {
ssh-all = {
name = "ssh-all"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_address_prefix = "1.2.3.4/32"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "*"
}
rdp-bastion = {
name = "rdp-bastion"
priority = 101
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefixes = ["1.2.3.4/32", "5.6.7.8/32"]
destination_address_prefixes = ["192.168.10.10/32", "192.168.10.11/32"]
}
http-https = {
name = "http-https"
priority = 102
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_ranges = [80,443]
source_address_prefixes = ["1.2.3.4/32", "5.6.7.8/32"]
destination_address_prefix = "AzureLoadBalancer"
}
dns-tcp = {
name = "dns-tcp"
priority = 100
direction = "Outbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = "*"
destination_address_prefix = "*"
}
dns-udp = {
name = "dns-tcp"
priority = 101
direction = "Outbound"
access = "Allow"
protocol = "Udp"
source_port_range = "*"
destination_port_range = "53"
source_address_prefix = "*"
destination_address_prefix = "*"
}
}
}
- Notice the use of different optional arguments. For instance, some rules use
destination_port_range, while others usedestination_port_ranges. This approach makes your configuration dynamic and reusable for various rule cases.
Reviewing Inbound and Outbound Security Rules in Azure
As above shows both inbound and outbound rules for the same NSG, lets review the Azure portal after the Terraform has been successfully ran
Azure NSG Inbound Security Rules

- Notice the variation of rules in
Port,SourceandDestination?
Azure NSG Outbound Security Rules

- Similarly, you will see the outbound rules reflecting the same flexibility and coverage.
By using for_each and try, we can create a flexible, powerful, and reusable set of NSG rules. This method handles multiple optional values efficiently, ensuring that our infrastructure as code is both robust and adaptable.
GitHub repository containing Terraform from this blog post
This is helpful. Thanks for sharing!
Thank you, glad you found it useful!