azureterraformterraform-provider-azureazure-virtual-networkazure-nsg

Terraform Azure include NSG with subnet deployment


I'm hitting a bit of difficulty on Azure trying to create subnets with conditional NSG associations through Terraform.

I have a single vnet with some subnets, some of the subnets should have nsgs attached/assigned, others should not.

Module code looks like this:

(NOTE: here I'm trying to include a reference that an NSG should be associated with a subnet if one has been supplied by the calling module):

Subnet module

subnets.tf

resource "azurerm_subnet" "subnet" {
  resource_group_name               = var.resource_group_name
  virtual_network_name              = var.virtual_network_name
  name                              = var.name
  address_prefixes                  = var.address_prefixes
  private_endpoint_network_policies = var.private_endpoint_network_policies

  dynamic "delegation" {
    for_each = var.delegation
    content {
      name = delegation.value["delegation_name"]
      service_delegation {
        name    = delegation.value["service_delegation_name"]
        actions = delegation.value["service_delegation_actions"]
      }
    }
  }
}

# If the subnet has a NSG specified associate it
resource "azurerm_subnet_network_security_group_association" "subnet_nsg_link" {
  count                     = var.network_security_group == null ? 0 : 1
  subnet_id                 = azurerm_subnet.subnet.id
  network_security_group_id = var.network_security_group
}

vars for subnets:

variables.tf

variable "name" {
  type        = string
  description = "Name for subnet resource"
}

variable "resource_group_name" {
  type        = string
  description = "Resource group the subnet resource to fall under"
}

variable "virtual_network_name" {
  type        = string
  description = "Name of the virtual network this subnet will be connected with"
}

variable "address_prefixes" {
  type        = list(string)
  description = "List of ip address"
}

variable "private_endpoint_network_policies" {
  type        = string
  description = "Setting for network policy to be allowed for security group"
  default     = null
}

variable "network_security_group" {
  type        = string
  description = "Network security group id to connect with"
  default     = null
}

variable "delegation" {
  type = list(object({
    delegation_name            = string,
    service_delegation_name    = string,
    service_delegation_actions = list(string)
  }))
  description = "(Optional) The delegations for the subnet."
  default     = []
}

NSG module:

nsg.tf

resource "azurerm_network_security_group" "this" {
  name                = var.name
  location            = var.location
  resource_group_name = var.resource_group_name

  security_rule {
    name                       = "test123"
    priority                   = 100
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "*"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

vars for nsg:

variables.tf

variable "name" {
  type        = string
  description = "Name for the network security group"
}

variable "location" {
  type        = string
  description = "Azure region that the network security group resource will be created under"
}

variable "resource_group_name" {
  type        = string
  description = "Resource group the network security group will be created under"
}

I'm trying this to call the module:

main.tf

# Resource group
resource "azurerm_resource_group" "network" {
  name     = "rg-temp"
  location = "uksouth"
}

# Default NSG (empty)
module "default" {
  source = "./modules/azure-network-security-group"

  name                = "nsg-default"
  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name
}

# VNETs
# Internal vnet
resource "azurerm_virtual_network" "vnet" {
  name                = "my-uksouth-int-vn01"
  location            = azurerm_resource_group.network.location
  resource_group_name = azurerm_resource_group.network.name
  address_space       = "10.10.10.0/24"

}

module "firewall_subnet" {
  source = "./modules/azure-subnet"

  resource_group_name  = azurerm_resource_group.network.name
  virtual_network_name = azurerm_virtual_network.vnet
  name                 = "AzureFirewallSubnet"
  address_prefixes     = "10.10.10.0/27"
}

module "vpn_subnet" {
  source = "./modules/azure-subnet"

  resource_group_name    = azurerm_resource_group.network.name
  virtual_network_name   = azurerm_virtual_network.vnet
  name                   = "vpnout"
  address_prefixes       = "10.10.10.32/27"
  network_security_group = module.default.id
}

The first two Azure specific subnets should not have any NSG associated with them, but the x2 VPN ones should.

I was hoping the logic of the azurerm_subnet_network_security_group_association in the subnets module would be "if the subnet 'network_security_group' is null skip this bit otherwise associate the NSG ID"?

But when I run the code I get this error:

╷
│ Error: Invalid count argument
│
│   on modules/azure-subnet/main.tf line 22, in resource "azurerm_subnet_network_security_group_association" "sublink":
│   22:   count                     = var.network_security_group == null ? 0 : 1
│
│ The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use
│ the -target argument to first apply only the resources that the count depends on.
╵

This doesn't seem to be a syntax issue, but an ordering condition problem? I'm not quite sure how to proceed?

Anyone have any ideas?

Thanks.


Solution

  • The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created.

    The message seems clear - you cannot set count with a dynamic value such as the resource id of another resource that is being managed by Terraform.

    Consider adding a new boolean variable to manage the association between the subnet and NSG:

    # other variables here
    
    # new variable
    variable "associate_network_security_group" {
      type        = bool
      description = "Associate subnet to a NSG?"
      default     = true
    }
    
    # existing variable
    variable "network_security_group" {
      type        = string
      description = "Network security group id to connect with"
      default     = null
    }
    

    Using the variable:

    module "firewall_subnet" {
      source = "./modules/azure-subnet"
    
      resource_group_name  = azurerm_resource_group.network.name
      virtual_network_name = azurerm_virtual_network.vnet
      name                 = "AzureFirewallSubnet"
      address_prefixes     = "10.10.10.0/27"
    
      // use new variable here
      associate_network_security_group = false 
    }
    
    module "vpn_subnet" {
      source = "./modules/azure-subnet"
    
      resource_group_name    = azurerm_resource_group.network.name
      virtual_network_name   = azurerm_virtual_network.vnet
      name                   = "vpnout"
      address_prefixes       = "10.10.10.32/27"
    
      network_security_group = module.default.id
      // No need to set associate_network_security_group (true be default)
    }
    

    Subnet - NSG association:

    resource "azurerm_subnet_network_security_group_association" "subnet_nsg_link" {
      count = var.associate_network_security_group ? 0 : 1
    
      subnet_id                 = azurerm_subnet.subnet.id
      network_security_group_id = var.network_security_group
    }