jawad846
jawad846

Reputation: 772

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,

Upvotes: 11

Views: 33841

Answers (5)

Alain O'Dea
Alain O'Dea

Reputation: 21696

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

Upvotes: 2

Martin L. Brink
Martin L. Brink

Reputation: 507

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

Upvotes: 7

BMW
BMW

Reputation: 45263

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

Upvotes: 2

Bharat
Bharat

Reputation: 227

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

Upvotes: 3

jawad846
jawad846

Reputation: 772

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

Upvotes: 10

Related Questions