azurerm_network_security_rule with variable source_address_prefix - azure

I'm deploying some firewall rules on Azure with Terraform and would like to keep the "source_address_prefix" in a variable, given that the list contains more than 20 IPs and they can change. Since I have around 5 rules, it's not ideal to add the IPs in each block and would rather use a variable
Tried the following variations of variable:
source_address_prefix = ["${var.whitelist_ips}"]
source_address_prefix = "${var.whitelist_ips}"
variables.tf
variable "whitelist_ips" {
type = "list"
default = ["199.83.128.0/21","198.143.32.0/19", "149.126.72.0/21","103.28.248.0/22", "45.64.64.0/22", "185.11.124.0/22", "192.230.64.0/18", "107.154.0.0/16", "45.60.0.0/16", "45.223.0.0/16", "2a02:e980::/29"]
}
main.tf
resource "azurerm_network_security_rule" "https" {
name = "Whitelist-HTTPS"
priority = 101
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "443"
destination_port_range = "*"
source_address_prefix = ["${var.whitelist_ips}"]
destination_address_prefix = "${azurerm_public_ip.ingress.ip_address}"
resource_group_name = "test"
network_security_group_name = "test"
depends_on = [azurerm_resource_group.aks]
}
Getting the following errors:
Error: Incorrect attribute value type
on main.tf line 35, in resource "azurerm_network_security_rule" "http":
35: source_address_prefix = ["${var.whitelist_ips}"]
Inappropriate value for attribute "source_address_prefix": string required.

Begone with that weird 0.11 syntax, with explicit depends_on, also the source port seemed wrong:
resource azurerm_network_security_rule this {
name = "Whitelist-HTTPS"
resource_group_name = azurerm_resource_group.this.name
network_security_group_name = azurerm_network_security_group.this.name
priority = 101
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "443"
source_address_prefixes = var.whitelist_ips
destination_address_prefix = azurerm_public_ip.ingress.ip_address
}
variable whitelist_ips {
description = "A list of IP CIDR ranges to allow as clients. Do not use Azure tags like `Internet`."
default = ["199.83.128.0/21", "198.143.32.0/19", "2a02:e980::/29"]
type = list(string)
}

Should have paid attention to the docs. The actual block is "source_address_prefixes" and not "source_address_prefix".

Related

Using Conditional Expressions in Terraform

I heard that terraform doesn't support if else condition. I would like to know what is the alternative for if/else condition in that case when using terraform. I have the below piece of code which by default sets up the below 2 Rules on all the Network Security Group (i.e say around 20+ NSG Groups).
Now, for a specific Network Security Group (i.e nsg-restricted-jdmsg-chn), I wanted to adjust this default rule slightly.. I wanted to change the rdp port from 3389 to 33091 and for the second rule, I wanted to change the ssh port from 22 to 26.
How can we accomplish this condition rule in terraform?
resource "azurerm_network_security_rule" "testrules" {
for_each = local.nsgrules
resource_group_name = var.jdgrp.rg.name
location = var.jdgrp.rg.location
name = nsg-restricted-${var.cfg.name}"
security_rule {
name = rdp
direction = "Inbound"
access = "Allow"
priority = "100"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = 3389
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "192.168.2.0/24"
}
security_rule {
name = ssh
direction = Inbound
access = "Allow"
priority = "101"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "22"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "192.168.2.0/24"
}
}
Additional Info
The value for name = nsg-restricted-${var.cfg.name}" is getting it from the below code.
jd-tech.tf
module "jd-tech" {
for_each = {
"${local.jd-tech_rst.name" = local.jd-tech_rst
"${local.jd-tech_dct.name" = local.jd-tech_dct
"${local.jd-tech_rpr.name" = local.jd-tech_rpr
"${local.jd-tech_amb.name" = local.jd-tech_amb
...
...
}
source = "./jd-tech"
...
...
cfg = each.value
...
}
The easiest way to do this would be by using the conditional expresion (you probably know it as ternary operator from other programming languages) [1]. To achieve what you want, you just need to do this for RDP:
destination_port_range = local.jd-tech_chn.name == "nsg-restricted-jdmsg-chn" ? 33091 : 3389
And for SSH:
destination_port_range = local.jd-tech_chn.name == "nsg-restricted-jdmsg-chn" ? 26 : 22
You would have to define either a local variable or a regular one. In my answer it is a regular variable.
[1] https://www.terraform.io/language/expressions/conditionals#conditional-expressions

Terraform azurerm provider count and csvdecode

I'm trying to populate NSG rules from a CSV file.
CSV file:
name,priority,direction,access,protocol,source_port_range,destination_port_range,destination_port_ranges,source_address_prefix,destination_address_prefix,resource_group_name,network_security_group_name
allowindatasubnet,600,inbound,allow,*,*,*,,192.168.3.0/24,*,resourcegroup1,networksecgroup1
allowinremote,700,inbound,allow,*,*,,"3389,22",192.168.1.128/27,*,resourcegroup1,networksecgroup1
denyinall,1000,inbound,deny,*,*,*,,*,*,resourcegroup1,networksecgroup1
tf file:
locals {
network_security_group_rules = csvdecode(file("/csvfile.csv"))
}
resource "azurerm_network_security_rule" "network_security_rule_WL1" {
count = length(local.network_security_group_rules)
name = local.network_security_group_rules[count.index].name
priority = local.network_security_group_rules[count.index].priority
direction = local.network_security_group_rules[count.index].direction
access = local.network_security_group_rules[count.index].access
protocol = local.network_security_group_rules[count.index].protocol
source_port_range = local.network_security_group_rules[count.index].source_port_range
destination_port_range = local.network_security_group_rules[count.index].destination_port_range
destination_port_ranges = [local.network_security_group_rules[count.index].destination_port_ranges]
source_address_prefix = local.network_security_group_rules[count.index].source_address_prefixyes
destination_address_prefix = local.network_security_group_rules[count.index].destination_address_prefix
resource_group_name = local.network_security_group_rules[count.index].resource_group_name
network_security_group_name = local.network_security_group_rules[count.index].network_security_group_name
}
This works fine without the destination_port_ranges attribute within the nsg rule resource block, but when I add it in I get an error:
Error: "destination_port_ranges": conflicts with destination_port_range
I understand that I need to use one argument or the other, but can anyone help me with the syntax or suggest changes that I can make allowing me to keep the same CSV format?
Also is my config correct for specifying a list of ports for the destination_port_ranges argument?
UPDATE:
I tried the following which was suggested by a friend but this threw up the same exception.
destination_port_range = local.network_security_group_rules[count.index].destination_port_range != "" ? local.network_security_group_rules[count.index].destination_port_range : null
destination_port_ranges = local.network_security_group_rules[count.index].destination_port_ranges != "" ? split(",", local.network_security_group_rules[count.index].destination_port_ranges) : null
Thanks!
As you said that you just need one argument, not both. As I see, all your destination port is a list or the character * and it means a range. Let's see the description of the argument destination_port_ranges and destination_port_range:
destination_port_range - (Optional) Destination Port or Range. Integer
or range between 0 and 65535 or * to match any. This is required if
destination_port_ranges is not specified.
destination_port_ranges - (Optional) List of destination ports or port
ranges. This is required if destination_port_range is not specified.
You use a list of destination ports or port ranges, so you just need to set the argument destination_port_ranges in the csv file for the network security rules.
Update:
You can use a module for the rules, the module is used to decide use which attribute for each rule:
./main.tf
locals {
network_security_group_rules = csvdecode(file("/csvfile.csv"))
}
module "rules" {
source = "./modules/rules"
count = length(local.network_security_group_rules)
rule = local.network_security_group_rules[count.index]
}
./modules/rules/main.tf
variable "rule" {}
resource "azurerm_network_security_rule" "network_security_rule_WL1" {
count = rule.destination_port_range == null ? 0 : 1
name = local.network_security_group_rules[count.index].name
priority = local.network_security_group_rules[count.index].priority
direction = local.network_security_group_rules[count.index].direction
access = local.network_security_group_rules[count.index].access
protocol = local.network_security_group_rules[count.index].protocol
source_port_range = local.network_security_group_rules[count.index].source_port_range
destination_port_range = local.network_security_group_rules[count.index].destination_port_range
source_address_prefix = local.network_security_group_rules[count.index].source_address_prefixyes
destination_address_prefix = local.network_security_group_rules[count.index].destination_address_prefix
resource_group_name = local.network_security_group_rules[count.index].resource_group_name
network_security_group_name = local.network_security_group_rules[count.index].network_security_group_name
}
resource "azurerm_network_security_rule" "network_security_rule_WL1" {
count = rule.destination_port_ranges == null ? 0 : 1
name = local.network_security_group_rules[count.index].name
priority = local.network_security_group_rules[count.index].priority
direction = local.network_security_group_rules[count.index].direction
access = local.network_security_group_rules[count.index].access
protocol = local.network_security_group_rules[count.index].protocol
source_port_range = local.network_security_group_rules[count.index].source_port_range
destination_port_ranges = [local.network_security_group_rules[count.index].destination_port_ranges]
source_address_prefix = local.network_security_group_rules[count.index].source_address_prefixyes
destination_address_prefix = local.network_security_group_rules[count.index].destination_address_prefix
resource_group_name = local.network_security_group_rules[count.index].resource_group_name
network_security_group_name = local.network_security_group_rules[count.index].network_security_group_name
}
In this way, you can't create a rule with both two attributes not null, I mean each rule only can have one of the two attributes.

Passing the outputs from a module when the module is created using a for_each

This links to my the previous question, click the link for the rest of my code and how it all fits together: Use output value from module that has a for_each set
Whilst the answer was helpful in solving the issue and allowing me to run the pipeline, I think there is an error because of the way the VM is generated using the for_each on the module. This results in the incorrect value being passed to the network_security_rule. Below is an example of the error:
Error: Error Creating/Updating Network Security Rule "nsr-sbox-http80" (NSG "module.fico_app_vm.linux_vm_nsg" / Resource Group "rg-sbox-app"): network.SecurityRulesClient#CreateOrUpdate: Failure sending request: StatusCode=404 -- Original Error: Code="ResourceNotFound" Message="The Resource 'Microsoft.Network/networkSecurityGroups/module.fico_app_vm.linux_vm_nsg' under resource group 'rg-sbox-app' was not found. For more details please go to https://aka.ms/ARMResourceNotFoundFix"
on main.tf line 58, in resource "azurerm_network_security_rule" "fico-app-sr-80":
58: resource "azurerm_network_security_rule" "fico-app-sr-80" {
outputs.tf
output "linux_vm_ips" {
value = azurerm_network_interface.dwp_network_interface.private_ip_address
}
output "linux_vm_nsg" {
value = azurerm_network_security_group.dwp_network_security_group.name
}
At first I thought it was because the NSG isn't being created, but I checked the console and it does create it. The issue is the NSG is created in the module for each VM. The VM's are created by looping over the variable in tfvars file. How do I pass the NSG name created in the module to the security rule which is outside of the module?:
resource "azurerm_network_security_rule" "fico-app-sr-80" {
name = "nsr-${var.environment}-${var.directorate}-${var.business_unit}-${var.vm_identifier}${var.instance_number}-http80"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_range = "80"
source_address_prefixes = ["module.fico_web_vm.linux_vm_ips"]
destination_address_prefix = "VirtualNetwork"
resource_group_name = azurerm_resource_group.rg_fico_app.name
network_security_group_name = "module.fico_app_vm.linux_vm_nsg"
}
# Network Security Group
resource "azurerm_network_security_group" "network_security_group" {
name = "nsg-${var.environment}-${var.directorate}-${var.business_unit}-${var.vm_identifier}-${var.vm_name}"
resource_group_name = var.resource_group
location = var.location
}
Something to note as well, is that the var.vm_name iterates through the key of each map and this makes up part of the name of the NSG.
module in main.tf:
module "fico_app_vm" {
for_each = var.app_servers
source = "../modules/compute/linux_vm"
source_image_id = var.app_image_id
location = var.location
vm_name = each.key
vm_identifier = "${var.vm_identifier}${var.instance_number}"
vm = each.value
disks = each.value["disks"]
resource_group = azurerm_resource_group.rg_fico_app.name
directorate = var.directorate
business_unit = var.business_unit
environment = var.environment
network_rg_identifier = var.network_rg_identifier
subnet_name = "sub-${var.environment}-${var.directorate}-${var.business_unit}-be01"
diag_storage_account_name = var.diag_storage_account_name
ansible_storage_account_name = var.ansible_storage_account_name
ansible_storage_account_key = var.ansible_storage_account_key
log_analytics_workspace_name = var.log_analytics_workspace_name
backup_policy_name = var.backup_policy_name
enable_management_locks = true
}
tfvars:
app_servers ={
app-1 = {
size = "Standard_E2s_v3"
admin_username = "xxx"
public_key = "xxx"
disks = [32, 32]
zone_vm = "1"
zone_disk = ["1"]
}
}
I think it's the problem that you read the previous answer not carefully. The answer shows you need to change the security group name like this:
network_security_group_name = module.fico_app_vm.linux_vm_nsg
There are no double-quotes. Double-quotes means it's a string. But you need to use the module attribute, not a string with a value "module.fico_app_vm.linux_vm_nsg". The error also shows it directly.
I realised what my error was. I needed to change this:
network_security_group_name = "module.fico_app_vm.linux_vm_nsg"
to this
network_security_group_name = module.fico_app_vm[each.key].linux_vm_nsg
But I also needed to reference the value in the source address prefixes. So to reference the key I created another variable and changed this:
source_address_prefixes = ["module.fico_web_vm.linux_vm_ips"]
to this:
source_address_prefix = module.fico_web_vm[each.value.web_server].linux_vm_ip
I also had to change to source_address_prefix and thats why the type error was occurring!
The security rule now looks like this:
resource "azurerm_network_security_rule" "fico-app-sr-80" {
for_each = var.app_servers
name = "nsr-${var.environment}-${var.directorate}-${var.business_unit}-${var.vm_identifier}${var.instance_number}-http80"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "*"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = module.fico_web_vm[each.value.web_server].linux_vm_ip
destination_address_prefix = "VirtualNetwork"
resource_group_name = azurerm_resource_group.rg_dwp_fico_app.name
network_security_group_name = module.fico_app_vm[each.key].linux_vm_nsg
And variable looks like this:
app_servers = {
app-1 = {
size = "Standard_E2s_v3"
admin_username = "azureuser"
public_key = xxxx
disks = [32, 32]
zone_vm = "1"
zone_disk = ["1"]
web_server = "web-1"
}
}

What is the equivalent argument field for source, source service tag and destination field in NSG security rules azure using teraform deployment

In the Source and Destination columns, VirtualNetwork, AzureLoadBalancer, and Internet are service tags, rather than IP addresses. How do i create this using terraform?
I am trying to create NSG on azure using terraform. While creating nsg security rules fields like source, source service tags and destination field is mandatory for creating nsg. How do i create this field using terraform ?
You could see source_address_prefix or destination_address_prefix in the Argument Reference. Tags such as ‘VirtualNetwork’, ‘AzureLoadBalancer’ and ‘Internet’ can also be used.
For example, you just need to add the specific service tag in the source_address_prefix and destination_address_prefix field.
# VirtualNetwork
security_rule {
name = "RDP"
priority = 300
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "VirtualNetwork"
destination_address_prefix = "VirtualNetwork"
}
#AppService
security_rule {
name = "SQL"
priority = 310
direction = "Inbound"
access = "Allow"
protocol = "Tcp"
source_port_range = "*"
destination_port_range = "80"
source_address_prefix = "AppService"
destination_address_prefix = "*"
}
result

Terraform Azure network security

I'm trying to configure a network security rule for a network security group in Azure via Terraform with multiple source addresses.
Based on the documentation
https://www.terraform.io/docs/providers/azurerm/r/network_security_rule.html
However, I'm not able to get this to work nor can I find any examples for it:
https://www.terraform.io/docs/providers/azurerm/r/network_security_rule.html#source_address_prefixes
I get the Error:
Error: azurerm_network_security_rule.test0: "source_address_prefix": required field is not set
Error: azurerm_network_security_rule.test0: : invalid or unknown key: source_address_prefixes
Here is my sample:
resource "azurerm_network_security_rule" "test0" {
name = "RDP"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "TCP"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefixes = "{200.160.200.30,200.160.200.60}"
destination_address_prefix = "VirtualNetwork"
network_security_group_name= "${azurerm_network_security_group.test.name}"
resource_group_name = "${azurerm_resource_group.test.name}"
}
Please let me know.
Thanks!
source_address_prefixes needs list of source address prefixes.
Modify it as below:
source_address_prefixes = ["200.160.200.30","200.160.200.60"]
There also a mistake in azurerm_network_security_group.test.name, the correct type is azurerm_network_security_group.test0.name. The following tf file works for me.
resource "azurerm_resource_group" "test0" {
name = "shuinsg"
location = "West US"
}
resource "azurerm_network_security_group" "test0" {
name = "shuinsgtest"
location = "${azurerm_resource_group.test0.location}"
resource_group_name = "${azurerm_resource_group.test0.name}"
}
resource "azurerm_network_security_rule" "test0" {
name = "RDP"
priority = 100
direction = "Inbound"
access = "Allow"
protocol = "TCP"
source_port_range = "*"
destination_port_range = "3389"
source_address_prefixes = ["200.160.200.30","200.160.200.60"]
destination_address_prefix = "VirtualNetwork"
network_security_group_name= "${azurerm_network_security_group.test0.name}"
resource_group_name = "${azurerm_resource_group.test0.name}"
}
Here is my test result.
An "address_prefix" is a string values representing a CIDR e.g. 10.0.0.0/24. So in your case source_address_prefix = "200.160.200.30/32" and destination_address_prefix = "${azurerm_virtual_network.test.address_space.0}" depending on what you want to refer to.

Resources