How to add security group rule to multiple security groups - terraform

I'm creating one security group rule and want to attach it to multiple security groups. How can I do it? For example:
resource "aws_security_group" "test-sg-1" {
name = "Test SG 1"
description = "Test Security Group one"
vpc_id = aws_vpc.test_vpc.id
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group" "test-sg-2" {
name = "Test SG 2"
description = "Test Security Group two"
vpc_id = aws_vpc.test_vpc.id
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group_rule" "egress_all" {
from_port = 0
protocol = "-1"
security_group_id = [aws_security_group.test-sg-1.id, aws_security_group.test-sg-2.id]
to_port = 0
type = "egress"
cidr_blocks = ["0.0.0.0/0"]
}
I'm getting error if I try this above way of using a list.
│ Error: Incorrect attribute value type
│
│ on main.tf line 76, in resource "aws_security_group_rule" "egress_all":
│ 76: security_group_id = [aws_security_group.test-sg-1.id, aws_security_group.test-sg-2.id]
│ ├────────────────
│ │ aws_security_group.test-sg-1.id will be known only after apply
│ │ aws_security_group.test-sg-2.id will be known only after apply
│
│ Inappropriate value for attribute "security_group_id": string required.

In this case using the for_each meta-argument [1] might be a good idea to avoid code repetition. So this is what I would do:
locals {
sg_names = ["Test SG 1", "Test SG 2"]
}
resource "aws_security_group" "test_sg" {
for_each = toset(local.sg_names)
name = each.value
description = each.value
vpc_id = aws_vpc.test_vpc.id
lifecycle {
create_before_destroy = true
}
}
resource "aws_security_group_rule" "egress_all" {
for_each = aws_security_group.test_sg
from_port = 0
protocol = "-1"
security_group_id = each.value.id
to_port = 0
type = "egress"
cidr_blocks = ["0.0.0.0/0"]
}
Here the resource chaining is used. You can read more in [2].
[1] https://www.terraform.io/language/meta-arguments/for_each#basic-syntax
[2] https://www.terraform.io/language/meta-arguments/for_each#chaining-for_each-between-resources

Related

Problem referencing aws_nat_gateway within route table

The nat_gateway_id argument in the last resource below is busted. I've been staring at the sun for too long, though, and the issue isn't jumping out at me.
resource "aws_eip" "nat_gateway_ip" {
for_each = aws_subnet.public
vpc = true
}
}
# Create NAT gateways in public subnets
resource "aws_nat_gateway" "private" {
for_each = aws_subnet.public
allocation_id = aws_eip.nat_gateway_ip[each.key].id
subnet_id = each.value.id
}
# Create Route tables and default routes
resource "aws_route_table" "private" {
for_each = toset([ "test", "prod"])
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.private[each.key].id
}
}
The resulting error is:
nat_gateway_id = aws_nat_gateway.private[each.key].id
│ ├────────────────
│ │ aws_nat_gateway.private is object with 2 attributes
│ │ each.key is "test"
│
│ The given key does not identify an element in this collection value.

Using the resource ID from a for_each resource block

I've created a Terraform template that creates 2 route tables and 2 subnets using the for_each command. I am trying to associate the route tables to the two subnets, however I am struggling to do so because I don't know how to obtain the ID for the route tables and subnets as the details are not in a variable, and I'm not sure how to get that information and use it. Please may someone provide assistance?
Thank you
Main Template
# SUBNETS DEPLOYMENT
resource "azurerm_subnet" "subnets" {
depends_on = [azurerm_virtual_network.vnet]
for_each = var.subnets
resource_group_name = var.rg.name
virtual_network_name = var.vnet.config.name
name = each.value.subnet_name
address_prefixes = each.value.address_prefixes
}
# ROUTE TABLE DEPLOYMENT
resource "azurerm_route_table" "rt" {
depends_on = [azurerm_virtual_network.vnet]
for_each = var.rt
name = each.value.route_table_name
resource_group_name = var.rg.name
location = var.rg.location
disable_bgp_route_propagation = true
route = [ {
address_prefix = each.value.address_prefix
name = each.value.route_name
next_hop_in_ip_address = each.value.next_hop_ip
next_hop_type = each.value.next_hop_type
} ]
}
# ROUTE TABLE ASSOICATION
resource "azurerm_subnet_route_table_association" "rt_assoication" {
subnet_id = azurerm_subnet.subnets.id
route_table_id = azurerm_route_table.rt.id
}
Variables
# SUBNET VARIBALES
variable "subnets" {
description = "subnet names and address prefixes"
type = map(any)
default = {
subnet1 = {
subnet_name = "snet-001"
address_prefixes = ["172.17.208.0/28"]
}
subnet2 = {
subnet_name = "snet-002"
address_prefixes = ["172.17.208.32/27"]
}
}
}
# ROUTE TABLES VARIABLES
variable "rt" {
description = "variable for route tables."
type = map(any)
default = {
rt1 = {
route_table_name = "rt1"
address_prefix = "0.0.0.0/0"
route_name = "udr-azure-firewall"
next_hop_ip = "10.0.0.0"
next_hop_type = "VirtualAppliance"
}
rt2 = {
route_table_name = "rt2"
address_prefix = "0.0.0.0/0"
route_name = "udr-azure-firewall"
next_hop_ip = "10.0.0.0"
next_hop_type = "VirtualAppliance"
}
}
}
The error I get when I run terraform plan is:
│ Error: Missing resource instance key
│
│ on modules\vnet\main.tf line 74, in resource "azurerm_subnet_route_table_association" "rt_assoication":
│ 74: subnet_id = azurerm_subnet.subnets.id
│
│ Because azurerm_subnet.subnets has "for_each" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ azurerm_subnet.subnets[each.key]
╵
╷
│ Error: Missing resource instance key
│
│ on modules\vnet\main.tf line 75, in resource "azurerm_subnet_route_table_association" "rt_assoication":
│ 75: route_table_id = azurerm_route_table.rt.id
│
│ Because azurerm_route_table.rt has "for_each" set, its attributes must be accessed on specific instances.
│
│ For example, to correlate with indices of a referring resource, use:
│ azurerm_route_table.rt[each.key]
Looks like you are almost there, update the following in the subnet-route table association block, it should work:
# ROUTE TABLE ASSOICATION
resource "azurerm_subnet_route_table_association" "rt_assoication" {
subnet_id = azurerm_subnet.subnets[each.key].id
route_table_id = azurerm_route_table.rt[each.key].id
}

Need to pass values for nsg rules in terraform resource block from a map type variable

I am trying a create a resource block for azurerm_network_security_rule such that it can be mapped with subnet variable being used in resource block as well so that subnet association with nsg would be easy.
for eg. variable is like
variable "subnets" {
description = "A list of subnets inside the VPC"
type = map(any)
default = {
subnet = {
name = "test"
address_prefix = ["x.x.x.x/x"]
attached_nat = "false"
inbound_rules = [
# [name, priority, direction, access, protocol, destination_port_range, source_address_prefix, destination_address_prefix]
["http", "100", "Inbound", "Allow", "Tcp", "80", "*", "0.0.0.0/0"],
]
outbound_rules = [
# [name, priority, direction, access, protocol, destination_port_range, source_address_prefix, destination_address_prefix]
["ntp_out", "103", "Outbound", "Allow", "Udp", "123", "", "0.0.0.0/0"],
]
}
}
}
Getting error
**Error: Invalid for_each argument
│
│ on main.tf line 120, in resource "azurerm_network_security_rule" "subnet":
│ 120: for_each = concat([for k, v in var.subnets: {"inbound_rules"=lookup(v,"inbound_rules",[])}], [for k, v in var.subnets: {"outbound_rules"=lookup(v,"outbound_rules",[])}])
│ ├────────────────
│ │ var.subnets is map of object with 1 element
│
│ The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or set of strings, and you have provided a value of type │ tuple.╵**
my resource block is like -
resource "azurerm_network_security_group" "subnet" {
for_each = var.subnets
name = format("%s-${each.value.name}")
location = var.location
resource_group_name = var.resource_group_name
}
resource "azurerm_network_security_rule" "subnet" {
for_each = concat([for k, v in var.subnets: {"inbound_rules"=lookup(v,"inbound_rules",[])}], [for k, v in var.subnets: {"outbound_rules"=lookup(v,"outbound_rules",[])}])
name = each.value[0]
priority = each.value[1]
direction = each.value[2]
access = each.value[3]
protocol = each.value[4]
source_port_range = "*"
destination_port_range = each.value[5]
source_address_prefix = each.value[6]
destination_address_prefix = each.value[7]
description = "${each.value[2]}_Port_${each.value[5]}"
resource_group_name = var.resource_group_name
network_security_group_name = azurerm_network_security_group.subnet[each.key].name
}
If someone can help figuring out the loop to put in for_each to achieve it.

Terraform - Automatically create SGs for CloudFront IPs

I am trying to automatically create SGs for CloudFront IPs so I can associate them my ALB.
This article has a very good insight on how to achieve it, but unfortunately it didn't work on my environment.
This is the code:
data "aws_ip_ranges" "cloudfront" {
regions = ["global"]
services = ["cloudfront"]
}
locals {
chunks_v4 = chunklist(data.aws_ip_ranges.cloudfront.cidr_blocks, 60)
}
resource "aws_security_group" "cloudfront" {
count = length(local.chunks_v4)
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = [local.chunks_v4[count.index]]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
}
and that is the error message:
╷
│ Error: Incorrect attribute value type
│
│ on main.tf line 34, in resource "aws_security_group" "cloudfront":
│ 34: cidr_blocks = [local.chunks_v4[count.index]]
│ ├────────────────
│ │ count.index is a number, known only after apply
│ │ local.chunks_v4 is a list of list of dynamic, known only after apply
│
│ Inappropriate value for attribute "cidr_blocks": element 0: string required.
╵
Shouldn't it be something like:
local.chunks_v4[count.index][0 to 59???]
How can I achieve it by using Terraform?
Edit: Since there is a hard limit of 60 CIDR blocks, we need to split it into chunks, thanks for the heads up #Marcin!
locals {
chunks_v4 = chunklist(data.aws_ip_ranges.cloudfront.cidr_blocks, 60)
}
data "aws_ip_ranges" "cloudfront" {
regions = ["global"]
services = ["cloudfront"]
}
resource "aws_security_group" "cloudfront" {
count = length(local.chunks_v4)
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = local.chunks_v4[count.index]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
lifecycle {
create_before_destroy = true
}
}

Terraform doesn't like my variables that contain lists of strings in my NACL block, can't figure out why

I wanted to make a rule that used a range of cidr blocks to cut down on the number of rules. I can't seem to get terraform to accept the variables or data output as strings
code:
data "aws_ip_ranges" "az_s3" {
regions = ["region-1"]
services = ["s3"]
}
variable "wan_range" {
description = "WAN cidr ranges"
type = list(string)
default = ["10.0.0.0/8", "172.16.0.0/16", "192.168.0.0/24"]
}
resource "aws_network_acl" "NACL_1" {
vpc_id = aws_vpc.sec_vpc.id
subnet_ids = [aws_subnet.private_subnet.id]
count = length(var.sd_wan_range)
egress = [
{
protocol = "tcp"
rule_no = 100
action = "allow"
cidr_block = data.aws_ip_ranges.az_s3.cidr_blocks
from_port = 80
to_port = 80
icmp_code = 0
icmp_type = 0
ipv6_cidr_block = null
},
{
protocol = "tcp"
rule_no = 200
action = "allow"
cidr_block = var.wan_range[count.index]
from_port = 32768
to_port = 65535
icmp_code = 0
icmp_type = 0
ipv6_cidr_block = null
}
]
Here is the error from plan:
├────────────────
│ │ count.index is 0
│ │ data.aws_ip_ranges.az_s3.cidr_blocks is list of string with 6 elements
│ │ var.wan_range is list of string with 3 elements
│
│ Inappropriate value for attribute "egress": element 2: attribute "cidr_block": string required.
You need to index on data.aws_ip_ranges.az_s3.cidr_blocks as well. The cidr_blocks attribute is a list.
e.g.
cidr_block = data.aws_ip_ranges.az_s3.cidr_blocks[count.index]

Resources