Network_security_group_id with for each function

HI guys
I opening this topic to get your help to help me progress on terraform coding.
I try to create multiple ressources using for each functions but I don’t know how to get network_security_group_id from azure resources create with for each.

## I Creating a network security group for all my hub subnets
resource and I want do the association  
"azurerm_network_security_group" "hub_network_security_group" {
  for_each            = var.hub_network_security_group
  name                = each.value
  location            = azurerm_resource_group.hub_resource_group.location
  resource_group_name = azurerm_resource_group.hub_resource_group.name
}

## Creating a security rule to deny inbound hub traffic 
resource "azurerm_network_security_rule" "deny_internet_inbound" {
  for_each = var.hub_network_security_group
  name                       = "Deny-internet-Outbound"
  priority                   = "4096" #beetween 100 - 4096
  direction                  = "outbound"
  access                     = "Deny"
  protocol                   = "*"
  source_port_range          = "*"
  destination_port_range     = "*"
  source_address_prefix      = "VirtualNetwork"
  destination_address_prefix = "Internet"
  resource_group_name         = azurerm_resource_group.hub_resource_group.name
  network_security_group_name = each.value
}

# Associating the network security group with subnets hosting session hosts

resource "azurerm_subnet_network_security_group_association" "hub_deny_inbound_traffic" {
  for_each                  = azurerm_subnet.hub_subnets
  subnet_id                 = each.value.id
  network_security_group_id =azurerm_network_security_group.hub_network_security_group.id    

\ thing my syntax is not good and I dont know what I need to use to get the network_security_group.hub_network_security_group id at this step.
could you help me please.
I guessing I can use a locals value but I asking before change the way I declare my ressources.

Thx

the error message I received

Error: Incorrect attribute value type

  on security.tf line 31, in resource "azurerm_subnet_network_security_group_association" "hub_deny_inbound_traffic":
  31:   network_security_group_id = azurerm_network_security_group.hub_network_security_group[*].id
    |----------------
    | azurerm_network_security_group.hub_network_security_group is object with 2 attributes

Inappropriate value for attribute "network_security_group_id": string
required.

The for_each meta-argument creates maps of resources.
Also 1 subnet can have only 1 nsg associated with it.
In your error text I see
network_security_group_id = azurerm_network_security_group.hub_network_security_group[*].id which is incorrect.
It should look like :
network_security_group_id = azurerm_network_security_group.hub_network_security_group["TheActualKeyNameGoesHere"].id

So using a lookup against the map will work.

Incase you have generated multiple networksecurity groups using for_each = var.hub_network_security_group , the hub_network_security_group variable should store a mapping between the subnets and the nsgs, this way you can do lookups.

Example :

nsgs = {
    nsg1 = {
      name   = "nsg1",
      subnet = "snet1"
    }
  }
  subnets = {
    snet1 = {
      name             = "snet1"
      nsg              = "nsg1"
      address_prefixes = ["10.0.0.0/26"]
    }
resource "azurerm_subnet" "subnet" {
  for_each             = local.subnets
  name                 = each.value.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  resource_group_name  = azurerm_resource_group.resourcegroup.name
  address_prefixes     = each.value.address_prefixes
}

resource "azurerm_network_security_group" "nsg" {
  for_each            = local.nsgs
  name                = each.value.name
  location            = local.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
}

resource "azurerm_subnet_network_security_group_association" "nsg-assosiation" {
  for_each                  = azurerm_subnet.subnet
  subnet_id                 = each.value.id
  network_security_group_id = azurerm_network_security_group.nsg[lookup(local.subnets, each.value.name, null).nsg].id
}

I have ran a plan and apply against the sample code and it works as intended. good luck!

HI thank for your answer I try but still not working did you try with multiple subnet ?
because with one it’s work for me but with two he won’t work
I change my nsg variables to locals.tf for all net resources create but the nsg association is still an issue for me. I used the way you advice me but for now no solution for me Grrrr

the error message :slight_smile:

Error: Invalid index

This value is null, so it does not have any attributes.


Error: Attempt to get attribute from null value

  on security.tf line 31, in resource "azurerm_subnet_network_security_group_association" "nsg-assosiation":
  31:   network_security_group_id = azurerm_network_security_group.core_net_nsgs[lookup(local.subnets, each.value.name, null).nsg].id
    |--**--------------**
**    | each.value.name is "snet-prd-weu-core-Shared-Infra-01"**
**    | local.subnets is object with 4 attributes**

This value is null, so it does not have any attributes.

what I did wrong ?

sample of the code :

locals {
location = "westeurope"

subnets = {
    snet-prd-weu-core-SharedInfra-01 = {
      name             = "snet-prd-weu-core-Shared-Infra-01"
      nsg              = "nsg-prd-weu-sub-shared-infra-01"
      adress_prefix    = ["10.98.225.0/24"] 

    }
    snet-prd-weu-core-ad-01 = {
      name             = "snet-prd-weu-core-ad-01"
      nsg              = "nsg-prd-weu-core-ad-01"
      adress_prefix    = ["10.98.239.160/27"]
    }

# Creating a network security group for all hub subnet
resource "azurerm_network_security_group" "core_net_nsgs" {
  for_each            = local.subnets
  name                = each.value.nsg
  location            = local.location
  resource_group_name = azurerm_resource_group.hub_resource_group.name
}

## Creating a security rule to deny inbound hub traffic 
resource "azurerm_network_security_rule" "deny_internet_inbound" {
  for_each = azurerm_network_security_group.core_net_nsgs
  name                       = "Deny-internet-Outbound"
  priority                   = "4096" #beetween 100 - 4096
  direction                  = "outbound"
  access                     = "Deny"
  protocol                   = "*"
  source_port_range          = "*"
  destination_port_range     = "*"
  source_address_prefix      = "VirtualNetwork"
  destination_address_prefix = "Internet"
  resource_group_name         = azurerm_resource_group.hub_resource_group.name
  network_security_group_name = each.value.name
}

#Associating the network security group with subnets core vnet

resource "azurerm_subnet_network_security_group_association" "nsg-assosiation" {
  for_each                  = azurerm_subnet.hub_subnets
  subnet_id                 = each.value.id
  network_security_group_id = azurerm_network_security_group.core_net_nsgs[lookup(local.subnets, each.value.name, null).nsg].id
}

Yes it works with multiple subnets and nsgs.
Here’s the locals ( I am using locals instead of vars, but the concept is still the same )

locals {
  location      = "australiaeast"
  resourcegroup = "rg-mssqlvm"
  nsgs = {
    nsg1 = {
      name   = "nsg1",
      subnet = "snet1"
    },
    nsg2 = {
      name   = "nsg2",
      subnet = "snet2"
    }
  }
  subnets = {
    snet1 = {
      name             = "snet1"
      nsg              = "nsg1"
      address_prefixes = ["10.0.0.0/26"]
    },
    snet2 = {
      name             = "snet2"
      nsg              = "nsg2"
      address_prefixes = ["10.0.0.64/26"]
    }

  }
}

the code which does nsg , subnet creation, and nsg-subnet association

resource "azurerm_subnet" "subnet" {
  for_each             = local.subnets
  name                 = each.value.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  resource_group_name  = azurerm_resource_group.resourcegroup.name
  address_prefixes     = each.value.address_prefixes
}

resource "azurerm_network_security_group" "nsg" {
  for_each            = local.nsgs
  name                = each.value.name
  location            = local.location
  resource_group_name = azurerm_resource_group.resourcegroup.name
}

resource "azurerm_subnet_network_security_group_association" "nsg-assosiation" {
  for_each                  = azurerm_subnet.subnet
  subnet_id                 = each.value.id
  network_security_group_id = azurerm_network_security_group.nsg[lookup(local.subnets, each.value.name, null).nsg].id
}

And this is the terraform plan output


An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # azurerm_network_security_group.nsg["nsg1"] will be created
  + resource "azurerm_network_security_group" "nsg" {
      + id                  = (known after apply)
      + location            = "australiaeast"
      + name                = "nsg1"
      + resource_group_name = "rg-mssqlvm"
      + security_rule       = (known after apply)
    }

  # azurerm_network_security_group.nsg["nsg2"] will be created
  + resource "azurerm_network_security_group" "nsg" {
      + id                  = (known after apply)
      + location            = "australiaeast"
      + name                = "nsg2"
      + resource_group_name = "rg-mssqlvm"
      + security_rule       = (known after apply)
    }

  # azurerm_resource_group.resourcegroup will be created
  + resource "azurerm_resource_group" "resourcegroup" {
      + id       = (known after apply)
      + location = "australiaeast"
      + name     = "rg-mssqlvm"
    }

  # azurerm_subnet.subnet["snet1"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefix                                 = (known after apply)
      + address_prefixes                               = [
          + "10.0.0.0/26",
        ]
      + enforce_private_link_endpoint_network_policies = false
      + enforce_private_link_service_network_policies  = false
      + id                                             = (known after apply)
      + name                                           = "snet1"
      + resource_group_name                            = "rg-mssqlvm"
      + virtual_network_name                           = "vnet-mssql"
    }

  # azurerm_subnet.subnet["snet2"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefix                                 = (known after apply)
      + address_prefixes                               = [
          + "10.0.0.64/26",
        ]
      + enforce_private_link_endpoint_network_policies = false
      + enforce_private_link_service_network_policies  = false
      + id                                             = (known after apply)
      + name                                           = "snet2"
      + resource_group_name                            = "rg-mssqlvm"
      + virtual_network_name                           = "vnet-mssql"
    }

  # azurerm_subnet_network_security_group_association.nsg-assosiation["snet1"] will be created
  + resource "azurerm_subnet_network_security_group_association" "nsg-assosiation" {
      + id                        = (known after apply)
      + network_security_group_id = (known after apply)
      + subnet_id                 = (known after apply)
    }

  # azurerm_subnet_network_security_group_association.nsg-assosiation["snet2"] will be created
  + resource "azurerm_subnet_network_security_group_association" "nsg-assosiation" {
      + id                        = (known after apply)
      + network_security_group_id = (known after apply)
      + subnet_id                 = (known after apply)
    }

  # azurerm_virtual_network.vnet will be created
  + resource "azurerm_virtual_network" "vnet" {
      + address_space         = [
          + "10.0.0.0/24",
        ]
      + guid                  = (known after apply)
      + id                    = (known after apply)
      + location              = "australiaeast"
      + name                  = "vnet-mssql"
      + resource_group_name   = "rg-mssqlvm"
      + subnet                = (known after apply)
      + vm_protection_enabled = false
    }

Plan: 8 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.


indeed one of my keys value in local nsgs does not match one of a key name in my subnets.
stupid mistake.Appreciate it

Hi, I’m looking at expanding this excellent code to include vms with a name, size and subnet.

Here is what I have in locals:

  vnet = {
    vnet = {
      name          = "vnet"
      address_space = ["10.0.0.0/16"]
    }
  }
  nsgs = {
    nsg01 = {
      name             = "nsg01"
      subnet           = "snet01"
    },
    nsg02 = {
      name             = "nsg02"
      subnet           = "snet02"
    }
  }
  subnets = {
    snet01 = {
      name             = "snet01"
      nsg              = "nsg01"
      address_prefixes = ["10.0.1.0/24"]
    },
    snet02 = {
      name             = "snet02"
      nsg              = "nsg02"
      address_prefixes = ["10.0.2.0/24"]
    }
  }
  vms = {
    vm01 = {
      name             = "vm01",
      size             = "Standard_DS1_v2"
      subnet           = "snet01"
    }
    vm02 = {
      name             = "vm02"
      size             = "Standard_D2s_v3"
      subnet           = "snet02"
    }
  }

My subnet and network interface resource:

resource "azurerm_subnet" "subnet" {
  depends_on           = [azurerm_virtual_network.vnet]
  for_each             = local.subnets
  name                 = each.value.name
  virtual_network_name = local.vnet.vnet.name
  resource_group_name  = local.resourcegroup
  address_prefixes     = each.value.address_prefixes
}

resource "azurerm_network_interface" "nic" {
  depends_on                    = [azurerm_resource_group.rg]
  for_each                      = local.vms
  name                          = "${each.key}-nic"
  location                      = local.location
  resource_group_name           = local.resourcegroup
  enable_ip_forwarding          = "false"
  enable_accelerated_networking = "false"

  ip_configuration {
    name                          = "ipconfig"
    subnet_id                     = azurerm_subnet.subnet[lookup(local.subnets, each.value.name, null).subnet].id
    private_ip_address_allocation = "Dynamic"
    primary                       = "true"
  }
}

However I’m seeing:

│ Error: Attempt to get attribute from null value
│
│   on main.tf line 50, in resource "azurerm_network_interface" "nic":
│   50:     subnet_id                     = azurerm_subnet.subnet[lookup(local.subnets, each.value.name, null).subnet].id
│     ├────────────────
│     │ each.value.name is "vm02"
│     │ local.subnets is object with 2 attributes
│
│ This value is null, so it does not have any attributes.
╵
╷
│ Error: Attempt to get attribute from null value
│
│   on main.tf line 50, in resource "azurerm_network_interface" "nic":
│   50:     subnet_id                     = azurerm_subnet.subnet[lookup(local.subnets, each.value.name, null).subnet].id
│     ├────────────────
│     │ each.value.name is "vm01"
│     │ local.subnets is object with 2 attributes
│
│ This value is null, so it does not have any attributes.

What would be the correct approach to lookup the subnet_id under ip_configuration?

Hi @mgibson85,

I think the problem here is that the logic to treat a missing element as null is too “early” and so later operations are failing by trying to work with the null value.

It seems like what you need here is to set subnet_id to null if local.subnets doesn’t contain an entry for each.value.name. If so, here’s a different way to write that:

  subnet_id = can(local.subnets[each.value.name]) ? azurerm_subnet.subnet[local.subnets[each.value.name]].id : null

Specifically the idea here is to deal with the null case in the outermost expression, so that the rest will not be evaluated at all if the precondition isn’t met, and thus you can avoid problems related to trying to perform operations on a null value.

Thank you, I didn’t consider the can function.
Terraform plan executes successfully however terraform apply gives the error:

╷
│ Error: expanding `ip_configuration`: A Subnet ID must be specified for an IPv4 Network Interface.
│
│   with azurerm_network_interface.nic["vm02"],
│   on main.tf line 39, in resource "azurerm_network_interface" "nic":
│   39: resource "azurerm_network_interface" "nic" {
│
╵
╷
│ Error: expanding `ip_configuration`: A Subnet ID must be specified for an IPv4 Network Interface.
│
│   with azurerm_network_interface.nic["vm01"],
│   on main.tf line 39, in resource "azurerm_network_interface" "nic":
│   39: resource "azurerm_network_interface" "nic" {

What are your thoughts?

These new errors seem to be comign from the Azure provider itself, rather than from Terraform, and so unfortunately this is now outside of my scope of direct knowledge.

It seems like the provider is telling you that subnet_id is actually a required argument and so it isn’t valid to set it to null. If that’s true, I guess you’ll need to find some other suitable fallback value to use instead of null so that the configuration can be valid even if there isn’t an entry in local.subnets, or to make sure that your local.subnets value is comprehensive for all possible keys and therefore the can fallback to null wouldn’t be necessary anymore.

Many thanks for the support and explanation.
I’ll look into another way of achieving the desired outcome.