'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,



Solution 1:[1]

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"
        },
    ]
}

Solution 2:[2]

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"}
    }
}

Solution 3:[3]

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"
  }
}

Solution 4:[4]

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

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Mohamed Jawad
Solution 2
Solution 3 Bharat
Solution 4 BMW