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.