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

1.8k views Asked by At

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.

0

There are 0 answers