0

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.

Jack Pettersson
  • 1,606
  • 4
  • 17
  • 28
  • 2
    You are using attributes as blocks for the rules. Dynamic blocks are not applicable. What's wrong with the merges? – Marcin Oct 01 '21 at 10:14
  • In the vacuum of this example it's decent, but I have to import over 100 security groups with 30+ rules each. It's quite cumbersome to clean up and copy paste the state after import and then having to add the merges methods. Would be a lot easier to simply have the values i want in a list and input that. The way i did it seems very "hacky". But maybe I should just do it like this anyway. – Jack Pettersson Oct 01 '21 at 10:22
  • I am not sure how the `import` complicates this config. If dynamic blocks are not working for you, then have you considered a separate resource for all of the rules: https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule? – Matthew Schuchard Oct 01 '21 at 12:29
  • I'm not sure I 100% understand your problematic. But I was able to get hereroclit setup, combing CIDR & SG running using the open source module [here](https://registry.terraform.io/modules/terraform-aws-modules/security-group/aws/latest). Maybe it'll help you, using it or reading its code – Rémy Jan 11 '22 at 14:09

0 Answers0