Creating AWS Security Groups in a Terraform Nested Loop - amazon-web-services

Using Terraform version 12, I am attempting to create some AWS security group rules.
I need to create x number of rules which may have different from and to ports.
These rules need to be create for each security group returned from the sg_groups data lookup.
So I have a nested list/map.
I think this is doable using Terraform's for_each and for loop function but I struggling to get my head around how to make this work.
Can anybody help me getting looping syntax correct.
Note: The format of the sg_rules map is not set in stone and can be formatted in any way that works best.
variable "sg_rules" = {
type = map
default = {
80 = {
protocol = "tcp"
from_port = 80
to_port = 80
},
443 = {
protocol = "tcp"
from_port = 443
to_port = 443
},
service_ports = {
protocol = "tcp"
from_port = 60000
to_port = 60500
}
}
}
data "aws_security_groups" "sg_groups" {
filter {
name = "group-name"
values = var.sg_names
}
}

Here is an example in which I have created the list of AWS Security Groups using nested for_each.
One for_each for creating a list of AWS Security Groups and another for creating dynamic Ingress and Egress Rules.
main.tf
resource "aws_security_group" "security-groups" {
# Dynamic number of Security Groups
for_each = var.security_group_configurations.security_groups
name = each.value.name
description = each.value.description
vpc_id = var.vpc_id
# Dynamic number of Ingress Rules
dynamic ingress {
for_each = var.security_group_configurations.security_groups[each.key].ingress_rules
content {
description = ingress.value.description
from_port = ingress.value.from_port
to_port = ingress.value.to_port
protocol = ingress.value.protocol
cidr_blocks = lookup(ingress.value, "cidr_blocks", [])
ipv6_cidr_blocks = lookup(ingress.value, "ipv6_cidr_blocks", [])
security_group_id = lookup(ingress.value, "security_group_id", "")
}
}
# Dynamic number of Egress Rules
dynamic egress {
for_each = var.security_group_configurations.security_groups[each.key].egress_rules
content {
description = egress.value.description #Here the 'egress' is the dynamic egress block
from_port = egress.value.from_port
to_port = egress.value.to_port
protocol = egress.value.protocol
cidr_blocks = lookup(ingress.value, "cidr_blocks", [])
ipv6_cidr_blocks = lookup(ingress.value, "ipv6_cidr_blocks", [])
security_group_id = lookup(ingress.value, "security_group_id", "")
}
}
tags = each.value.tags
}
variables.tf
variable "security_group_configurations" {
description = "All Security_Groups related configuration"
}
variable "vpc_id" {
description = "Vpc Id for security_groups"
}
example.tfvars
vpc_id = "vpc-0be6071d37dsd985b"
security_group_configurations = {
security_groups = {
#--------------------->> Security Group 0 <-------------------------------------------
"0" = {
name = "security_group_0"
description = "security_group_0"
# Ingress Rules
ingress_rules = {
# Ingress Rule 0
0 = {
description = "TLS from Public Internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
},
# Ingress Rule 1
1 = {
description = "SSH from Public Internet"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
},
}
# Egress Rules
egress_rules = {
# Egress Rule 0
0 = {
description = "Allow all traffic to outside"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
},
# Egress Rule 1
##############
}
# Tags for Security Group 0
tags = {
Name = "security_group_0"
}
},
#--------------------->> Security Group 1 <-------------------------------------------
"1" = {
name = "security_group_1"
description = "security_group_1"
# Ingress Rules
ingress_rules = {
# Ingress Rule 0
0 = {
description = "TLS from Public Internet"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
},
# Ingress Rule 1
1 = {
description = "SSH from Public Internet"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
},
}
# Egress Rules
egress_rules = {
# Egress Rule 0
0 = {
description = "Allow all traffic to outside"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
},
# Egress Rule 1
####
####
}
# Tags for Security Group 1
tags = {
Name = "security_group_1"
}
}
#--------------------->> Security Group 2 <-------------------------------------------
#######
#######
}
}

In typical fashion as soon as I ask for help I end up figuring it out myself, or at least one way of doing it. Here is what I came up with, if anyone has any better ideas please feel free to say
variable "sg_rules" = {
type = map
default = [
{
protocol = "tcp"
from_port = 80
to_port = 80
},
{
protocol = "tcp"
from_port = 443
to_port = 443
},
{
protocol = "tcp"
from_port = 60000
to_port = 60500
}
}
}
locals {
gw_to_mx_rules = [
for pair in setproduct(data.aws_security_groups.sg_groups.ids, var.sg_rules) : {
sg = pair[0]
type = pair[1].type
protocol = pair[1].protocol
from_port = pair[1].from_port
to_port = pair[1].to_port
}
]
}
data "aws_security_groups" "sg_groups" {
filter {
name = "group-name"
values = var.sg_names
}
}
resource "aws_security_group_rule" "gw_to_mx" {
for_each = { for rule in local.gw_to_mx_rules : "${rule.type}.${rule.from_port}.${rule.to_port}.${rule.protocol}.${rule.sg}" => rule }
description = "MX Communication"
type = each.value.type
from_port = each.value.from_port
to_port = each.value.to_port
protocol = each.value.protocol
source_security_group_id = each.value.sg
security_group_id = aws_security_group.waf_instance_sg.id
}

Related

Referencing Security Group in AWS via Terraform using Dynamic Block

I have a security group resource in the module called "networking":
resource "aws_security_group" "dev_sg" {
for_each = var.security_groups
name = each.value.name
description = each.value.description
vpc_id = aws_vpc.dev_vpc.id
dynamic "ingress" {
for_each = each.value.ingress
#iterator = port
content {
from_port = ingress.value.from
to_port = ingress.value.to
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Also, outside of the module, in the root module i have locals.tf file which is this:
locals {
security_groups = {
public = {
name = "public_sg"
description = "Security Group for Public Access"
ingress = {
ssh = {
from = 22
to = 22
protocol = "tcp"
cidr_blocks = [var.access_ip]
}
http = {
from = 80
to = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
}
}
And here is the module definition:
module "networking" {
source = "./networking"
vpc_cidr = local.vpc_cidr
security_groups = local.security_groups
public_sn_count = 2
private_sn_count = 3
}
Now, my question is, how can I reference a security group ID instead of cidr_block inside locals.tf file? I have no clue how to implement this?
For example:
cidr_blocks = ["192.168.8.0/21", "${var.security_group_id}"]
You need to use security_groups in aws_security_group ingress or egress
resource "aws_security_group" "dev_sg" {
for_each = var.security_groups
name = each.value.name
description = each.value.description
vpc_id = aws_vpc.dev_vpc.id
dynamic "ingress" {
for_each = each.value.ingress
#iterator = port
content {
from_port = ingress.value.from
to_port = ingress.value.to
protocol = ingress.value.protocol
cidr_blocks = ingress.value.cidr_blocks
security_groups = lookup(ingress.value, "security_groups", null)
}
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
locals {
security_groups = {
public = {
name = "public_sg"
description = "Security Group for Public Access"
ingress = {
ssh = {
from = 22
to = 22
protocol = "tcp"
cidr_blocks = [var.access_ip]
security_groups = [var.security_group_id]
}
http = {
from = 80
to = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"],
security_groups = [var.security_group_id]
}
}
}
}
module "networking" {
source = "./networking"
vpc_cidr = local.vpc_cidr
security_groups = local.security_groups
public_sn_count = 2
private_sn_count = 3
}

Terraform for loop to generate security groups with different ports and protocols

I hope can someone can help? I'm a beginner in my terraform journey and from what i have discovered I need to use combination of dynamic function with flatten in order to achieve my reqs. My requirements are:
create 4x different AWS security groups(diff description and name)
each security group have same ingress and egress settings
each ingress and egress should be created as a separate rule
each rule has different port numbers
each rule has different protocol to be used(e.g. TCP and UDP)
Jus to give a background, I need to create AWS FSx service(with managed AD) that will allow following set up access to and from FSx service: https://docs.aws.amazon.com/fsx/latest/WindowsGuide/limit-access-security-groups.html
Initially the security was done based on 1x security group for 4x different CIDRs with multiple rules but I ended up reaching AWS quota(60) for amount of rules per ingress/egress, so to not extend it best way recommendation from AWS support was to split each CIDR traffic as a separate security group.
So I tried to modify the code from link below but this does not seem to work: https://www.terraform.io/language/functions/flatten
variables.tfvars
security_config = {
ports = [{
tcp_port = ["135", "389", "445", "636", "3268", "3269", "5985", "9389", "49152 - 65535"]
tcp_udp_port = ["53", "88", "123", "389", "464"]
udp_port = ["123"]
protocol = ["tcp", "udp"]
cidr_block = ["10.1.0.0/28", "10.2.0.0/28", "10.3.0.0/28", "10.4.0.0/28"]
}
]
}
locals.tf
locals {
security_rules = flatten([
for port_key, port in var.ports : [
for protocol_key, protocol in port.protocols : {
from_port = port_key
to_port = port_key
protocol = protocol_key
cidr_block = security_rules.cidr_block
}
]
])
}
main.tf
resource "aws_security_group" "fsx_flatten" {
for_each = {
for port in local.security_rules : "${port.port_key}.${port.protocol_key}" => port
}
vpc_id = each.value.vpc_id
name = each.value.name
description = each.value.description
}
I would like to have something similar output to:
security_config = {
security_groups = [{
name = "sg_1"
description = "security group 1 - primary site"
ingress = {
from_port = 53
to_port = 53
protocol = "tcp"
cidr_block = ["10.1.0.0/28"]
}
ingress = {
from_port = 53
to_port = 53
protocol = "udp"
cidr_block = ["10.1.0.0/28"]
}
ingress = {
from_port = 88
to_port = 88
protocol = "tcp"
cidr_block = ["10.1.0.0/28"]
}
}
ingress = {
from_port = 88
to_port = 88
protocol = "udp"
cidr_block = ["10.1.0.0/28"]
}
ingress = {
from_port = 123
to_port = 123
protocol = "udp"
cidr_block = ["10.1.0.0/28"]
}
ingress = {
from_port = 135
to_port = 135
protocol = "tcp"
cidr_block = ["10.1.0.0/28"]
}
egress = {
from_port = 53
to_port = 53
protocol = "tcp"
cidr_block = ["10.1.0.0/28"]
}
}
egress = {
from_port = 53
to_port = 53
protocol = "udp"
cidr_block = ["10.1.0.0/28"]
}
egress = {
from_port = 88
to_port = 88
protocol = "tcp"
cidr_block = ["10.1.0.0/28"]
}
}
egress = {
from_port = 88
to_port = 88
protocol = "udp"
cidr_block = ["10.1.0.0/28"]
}
egress = {
from_port = 123
to_port = 123
protocol = "udp"
cidr_block = ["10.1.0.0/28"]
}
egress = {
from_port = 135
to_port = 135
protocol = "tcp"
cidr_block = ["10.1.0.0/28"]
}
},
{
name = "sg_2"
description = "security group 2 - secondary site"
ingress = {
from_port = 53
to_port = 53
protocol = "tcp"
cidr_block = ["10.2.0.0/28"]
}
ingress = {
from_port = 53
to_port = 53
protocol = "udp"
cidr_block = ["10.2.0.0/28"]
}
ingress = {
from_port = 88
to_port = 88
protocol = "tcp"
cidr_block = ["10.2.0.0/28"]
}
}
ingress = {
from_port = 88
to_port = 88
protocol = "udp"
cidr_block = ["10.2.0.0/28"]
}
ingress = {
from_port = 123
to_port = 123
protocol = "udp"
cidr_block = ["10.2.0.0/28"]
}
ingress = {
from_port = 135
to_port = 135
protocol = "tcp"
cidr_block = ["10.2.0.0/28"]
}
egress = {
from_port = 53
to_port = 53
protocol = "tcp"
cidr_block = ["10.2.0.0/28"]
}
}
egress = {
from_port = 53
to_port = 53
protocol = "udp"
cidr_block = ["10.2.0.0/28"]
}
egress = {
from_port = 88
to_port = 88
protocol = "tcp"
cidr_block = ["10.2.0.0/28"]
}
}
egress = {
from_port = 88
to_port = 88
protocol = "udp"
cidr_block = ["10.2.0.0/28"]
}
egress = {
from_port = 123
to_port = 123
protocol = "udp"
cidr_block = ["10.2.0.0/28"]
}
egress = {
from_port = 135
to_port = 135
protocol = "tcp"
cidr_block = ["10.2.0.0/28"]
}
]
}
hope this helps to solve your problem
main.tf
resource "aws_security_group" "main" {
...
dynamic "ingress" {
for_each = var.ingress_roles
content {
description = ingress.value["description"]
from_port = ingress.value["from_port"]
to_port = ingress.value["to_port"]
protocol = ingress.value["protocol"]
cidr_blocks = tolist(ingress.value["cidr_blocks"])
ipv6_cidr_blocks = tolist(ingress.value["ipv6_cidr_blocks"])
}
}
dynamic "egress" {
for_each = var.egress_roles
content {
from_port = egress.value["from_port"]
to_port = egress.value["to_port"]
protocol = egress.value["protocol"]
cidr_blocks = tolist(egress.value["cidr_blocks"])
ipv6_cidr_blocks = tolist(egress.value["ipv6_cidr_blocks"])
}
}
...
}
variables.tf
...
ingress_security_group=[{
description = "http from VPC"
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
},
{
description = "TLS from VPC"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
]
egress_security_group=[{
from_port = 0
to_port = 0
protocol = "all"
cidr_blocks = ["0.0.0.0/0"]
ipv6_cidr_blocks = ["::/0"]
}
]
...
My purpose is to use dynamic blocks expression to iterate over ingress and egress objects whenever I add a rule
Terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# module.ec2.aws_security_group.main will be created
+ resource "aws_security_group" "main" {
...
+ ingress = [
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = "TLS from VPC"
+ from_port = 443
+ ipv6_cidr_blocks = [
+ "::/0",
]
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 443
},
+ {
+ cidr_blocks = [
+ "0.0.0.0/0",
]
+ description = "http from VPC"
+ from_port = 80
+ ipv6_cidr_blocks = [
+ "::/0",
]
+ prefix_list_ids = []
+ protocol = "tcp"
+ security_groups = []
+ self = false
+ to_port = 80
},
]
...
}
Plan: 1 to add, 0 to change, 0 to destroy.
Reference:
https://www.terraform.io/language/expressions/dynamic-blocks
https://blog.gruntwork.io/terraform-tips-tricks-loops-if-statements-and-gotchas-f739bbae55f9

Terraform: create resource(aws_security_group) successfully but it takes ingress/egress rules from all given security groups

My code will create security groups as well as ingress/egress as we give the list of security groups and rules in the dev.tfvars file
The code ran successfully but created security groups takes ingress/egress rules from all given security groups.
./security.tf
resource "aws_security_group" "sg" {
count = length(var.vpc_config.security_groups)
name = var.vpc_config.security_groups[count.index].name
description = var.vpc_config.security_groups[count.index].description
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = var.vpc_config.security_groups
content {
from_port = ingress.value.ingress.from_port
to_port = ingress.value.ingress.to_port
protocol = ingress.value.ingress.protocol
cidr_blocks = ingress.value.ingress.cidr_block
}
}
dynamic "egress" {
for_each = var.vpc_config.security_groups
content {
from_port = egress.value.egress.from_port
to_port = egress.value.egress.to_port
protocol = egress.value.egress.protocol
cidr_blocks = egress.value.egress.cidr_block
}
}
tags = {
Name = var.vpc_config.security_groups[count.index].name
Environment = var.vpc_config.environment
}
}
./dev.tfvars
vpc_config = {
security_groups = [ {
name = "sg_1"
description = "security group 1"
ingress = {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_block = ["0.0.0.0/0"]
}
egress = {
from_port = 0
to_port = 0
protocol = "-1"
cidr_block = ["0.0.0.0/0"]
}
},
{
name = "sg_2"
description = "security group 2"
ingress = {
from_port = 21
to_port = 21
protocol = "tcp"
cidr_block = ["0.0.0.0/0"]
}
egress = {
from_port = 443
to_port = 443
protocol = "http"
cidr_block = ["0.0.0.0/0"]
}
}
]
}
It will create two security groups with one ingress and one egress each but it creates two security groups with two ingress and two egress each.
If your goal is to create a 2 security groups, each having a certain ingress and egress rules explicitly defined, you do not want to have dynamic blocks. With dynamic blocks, you will create an inner loop, which is not what you would want.
I recommend using only one for_each at the resource level and no dynamic blocks:
resource "aws_security_group" "sg" {
for_each = {
for sg in var.vpc_config.security_groups : sg.name => sg
}
name = each.value.name
description = each.value.description
vpc_id = var.vpc_id
ingress {
from_port = each.value.ingress.from_port
to_port = each.value.ingress.to_port
protocol = each.value.ingress.protocol
cidr_blocks = each.value.ingress.cidr_block
}
egress {
from_port = each.value.egress.from_port
to_port = each.value.egress.to_port
protocol = each.value.egress.protocol
cidr_blocks = each.value.egress.cidr_block
}
tags = {
Name = each.value.name
Environment = var.vpc_config.environment
}
}
If you want to use count, you can do it as follows:
resource "aws_security_group" "sg" {
count = length(var.vpc_config.security_groups)
name = var.vpc_config.security_groups[count.index].name
description = var.vpc_config.security_groups[count.index].description
vpc_id = var.vpc_id
ingress {
from_port = var.vpc_config.security_groups[count.index].ingress.from_port
to_port = var.vpc_config.security_groups[count.index].ingress.to_port
protocol = var.vpc_config.security_groups[count.index].ingress.protocol
cidr_blocks = var.vpc_config.security_groups[count.index].ingress.cidr_block
}
egress {
from_port = var.vpc_config.security_groups[count.index].egress.from_port
to_port = var.vpc_config.security_groups[count.index].egress.to_port
protocol = var.vpc_config.security_groups[count.index].egress.protocol
cidr_blocks = var.vpc_config.security_groups[count.index].egress.cidr_block
}
tags = {
Name = var.vpc_config.security_groups[count.index].name
Environment = var.vpc_config.environment
}
}

Terraform aws_security_group: How do you pass ingress and egress blocks from variables?

My main goal is to remove hardcoded ingress and egress configuration blocks from our aws_security_group resources in our terraforms modules. I instead want to pass in one ingress and one egress input variable containing all the ingress and egress rules.
Current aws_security_group creation:
# main.tf
resource "aws_security_group" "sg" {
name = "Example security group"
egress {
from_port = 123
to_port = 123
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 53
to_port = 53
protocol = "tcp"
security_groups = [local.some_sg_id]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["10.0.0.0/8"]
}
vpc_id = var.vpc_id
}
What i want to do:
# main.tf
resource "aws_security_group" "sg" {
name = "Example security group"
egress = var.sg_egress
ingress = var.sg_ingress
vpc_id = var.vpc_id
}
The issue is that the ingress and egress blocks have optional parameters.
I.E on one ingress statement i have specified "cidr_blocks" and on one "security_groups".
This makes it hard to create a variable statement for these blocks.
I have managed to get it to work using this:
# terragrunt.hcl
# Note: we use terragrunt, but in the example below think of this as terraform.tfvars
locals {
some_sg_id = "sg-123abc456"
sg_defaults = {
"security_groups" = []
"cidr_blocks" = []
"ipv6_cidr_blocks" = []
"prefix_list_ids" = []
"self" = false
"description" = ""
}
}
inputs = {
sg_egress [
merge(local.sg_defaults, {
from_port = 123
to_port = 123
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}),
merge(local.sg_defaults, {
from_port = 53
to_port = 53
protocol = "tcp"
security_groups = [local.some_sg_id]
})
]
sg_ingress [
merge(local.sg_defaults, {
from_port = 123
to_port = 123
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
})
]
}
# variables.tf
variable "sg_ingress" {
type = list(object({
cidr_blocks = list(string)
description = string
from_port = number
ipv6_cidr_blocks = list(string)
prefix_list_ids = list(string)
protocol = string
security_groups = list(string)
self = bool
to_port = number
}))
default = []
}
variable "sg_egress" {
type = list(object({
cidr_blocks = list(string)
description = string
from_port = number
ipv6_cidr_blocks = list(string)
prefix_list_ids = list(string)
protocol = string
security_groups = list(string)
self = bool
to_port = number
}))
default = []
}
Here i create default (empty) values for the optional attributes and then merge them with the values in the input variable. This creates input variables that have all attributes filled in, with empty values if none were specified. This way i can just create a variable statement with all values specified, but it's not a very pretty solution...
Dynamic blocks can probobly be used to accomplish this but so far my smooth brain have not been able to make it work with those..
I have seen this similar StackOverflow question, but it does not go over using optional attributes.

Create multiple rules in AWS security Group

I tried to create an AWS security group with multiple inbound rules, Normally we need to multiple ingresses in the sg for multiple inbound rules. Instead of creating multiple ingress rules separately, I tried to create a list of ingress and so that I can easily reuse the module for different applications.
PFB,
module/sg/sg.tf >>
resource "aws_security_group" "ec2_security_groups" {
name = var.name_security_groups
vpc_id = var.vpc_id
}
module/sg/rules.tf >>
resource "aws_security_group_rule" "ingress_rules" {
count = lenght(var.ingress_rules)
type = "ingress"
from_port = var.ingress_rules[count.index][0]
to_port = var.ingress_rules[count.index][1]
protocol = var.ingress_rules[count.index][2]
cidr_blocks = var.ingress_rules[count.index][3]
description = var.ingress_rules[count.index][4]
security_group_id = aws_security_group.ec2_security_groups.id
}
module/sg/variable.tf >>
variable "vpc_id" {
}
variable "name_security_groups" {
}
variable "ingress_rules" {
type = list(string)
}
In the application folder,
application/dev/sg.tf >>
module "sg_test" {
source = "../modules/sg"
vpc_id = "vpc-xxxxxxxxx"
name_security_groups = "sg_test"
ingress_rules = var.sg_ingress_rules
}
application/dev/variable.tf >>
variable "sg_ingress_rules" {
type = list(string)
default = {
[22, 22, "tcp", "1.2.3.4/32", "test"]
[23, 23, "tcp", "1.2.3.4/32", "test"]
}
}
Error:
Error: Missing attribute value
on test-sgs.tf line 21, in variable "sg_ingress_rules":
20:
21:
22:
Expected an attribute value, introduced by an equals sign ("=").
Please help to correct this or if there is any other method please suggest.
Regards,
Thanks#apparentlymart, who helped to solve this in Terraform discussion
The Security rule:-
resource "aws_security_group_rule" "ingress_rules" {
count = length(var.ingress_rules)
type = "ingress"
from_port = var.ingress_rules[count.index].from_port
to_port = var.ingress_rules[count.index].to_port
protocol = var.ingress_rules[count.index].protocol
cidr_blocks = [var.ingress_rules[count.index].cidr_block]
description = var.ingress_rules[count.index].description
security_group_id = aws_security_group.ec2_security_groups.id
}
And the variable:
variable "sg_ingress_rules" {
type = list(object({
from_port = number
to_port = number
protocol = string
cidr_block = string
description = string
}))
default = [
{
from_port = 22
to_port = 22
protocol = "tcp"
cidr_block = "1.2.3.4/32"
description = "test"
},
{
from_port = 23
to_port = 23
protocol = "tcp"
cidr_block = "1.2.3.4/32"
description = "test"
},
]
}
We can do this in a simple way:-
locals {
ports_in = [
443,
80
]
ports_out = [
0
]
}
resource "aws_security_group" "internet_facing_alb" {
name = "internetfacing-loadbalancer-sg"
description = "Security group attached to internet facing loadbalancer"
vpc_id = var.vpc_id
dynamic "ingress" {
for_each = toset(local.ports_in)
content {
description = "Web Traffic from internet"
from_port = ingress.value
to_port = ingress.value
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
}
dynamic "egress" {
for_each = toset(local.ports_out)
content {
description = "Web Traffic to internet"
from_port = egress.value
to_port = egress.value
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
tags = {
Name = "internetfacing-loadbalancer-sg"
}
}
If you want this to work literally with indexed fields, make it a list(list(string)) and change the default oyter syntax from braces (used for maps) to brackets (used for lists):
variable "sg_ingress_rules" {
type = list(list(string))
default = [
[22, 22, "tcp", "1.2.3.4/32", "test"]
[23, 23, "tcp", "1.2.3.4/32", "test"]
]
}
That is a confusing data structure and will be difficult to work with, so I recommend this instead:
variable "sg_ingress_rules" {
type = map(map(any))
default = {
thing1 = {from=22, to=22, proto="tcp", cidr="1.2.3.4/32", desc=test"]
thing2 = {from=23, to=23, proto="tcp", cidr="1.2.3.4/32", desc="test"}
}
}
You can use better names than the terrible ones I've chosen and then refer to them in your resource:
resource "aws_security_group_rule" "ingress_rules" {
for_each = var.ingress_rules
type = "ingress"
from_port = each.value.from
to_port = each.value.to
protocol = each.value.proto
cidr_blocks = each.value.cidr
description = each.value.desc
security_group_id = aws_security_group.ec2_security_groups.id
}
You'll get multiple named copies of the aws_security_group_rule which better survives insertions and deletions from the ingress_rules variable and will save you headaches. Otherwise you'll get superfluous destroys and creates of rules and sometimes conflicts due to the indexed resources a count creates.
If you are feeling like having some better guardrails on people setting the ingress_rules value you can use object to require and restrict to a particular set of fields with certain types as follows:
variable "sg_ingress_rules" {
type = map(object(
{
from = number
to = number
proto = string
cidr = string
desc = string
}
))
default = {
thing1 = {from=22, to=22, proto="tcp", cidr="1.2.3.4/32", desc=test"]
thing2 = {from=23, to=23, proto="tcp", cidr="1.2.3.4/32", desc="test"}
}
}
There is a new way to manage multiple ingress rules, with a new terraform resource, named aws_security_group_rule
it is better than the other ways, using Attributes as Blocks
Sample for reference
resource "aws_security_group_rule" "example" {
type = "ingress"
from_port = 0
to_port = 65535
protocol = "tcp"
cidr_blocks = [aws_vpc.example.cidr_block]
ipv6_cidr_blocks = [aws_vpc.example.ipv6_cidr_block]
security_group_id = "sg-123456"
}
Ref: aws_security_group_rule
The easiest way to implement multiple rules in a security group looks a bit like the following example:
resource "aws_security_group" "sg_my_security_group" {
name = "sg_my_security_group"
description = "Implements some security"
vpc_id = var.your_vpc_id
ingress {
from_port = 3889
to_port = 3889
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 3889
to_port = 3889
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
tags = {
Name = "does_something_important"
}
}