Putting csvdecode in security group rule

I want to call csv function from variable.

Here is my main.tf file of security group

resource "aws_security_group" "names" {
  count = length(var.ams_prod_sg_list)
  name        = var.ams_prod_sg_list[count.index].sg_name
  vpc_id = module.vpc.vpc_id_sg
  tags = {
    Name = var.ams_prod_sg_list[count.index].sg_tags
  }
}

resource "aws_security_group_rule" "sg_rule" {
  count             = length(var.ams_prod_sg_list)
  security_group_id = "${aws_security_group.this.*.id}"
  type              = var.ams_prod_sg_list[count.index].sg_rules.type
  protocol          = var.ams_prod_sg_list[count.index].sg_rules.protocol
  from_port         = var.ams_prod_sg_list[count.index].sg_rules.from
  to_port           = var.ams_prod_sg_list[count.index].sg_rules.to
  cidr_blocks       = [var.ams_prod_sg_list[count.index].sg_rules.cidr_blocks]
  description       = var.ams_prod_sg_list[count.index].sg_rules.description

Here is the variable.tf file

locals {
  test = csvdecode(file("${path.module}/csv/test.csv"))
  test1 = csvdecode(file("${path.module}/csv/test1.csv"))
}

variable "ams_prod_sg_list" {
  description = "sg_name rules"
  type        = list(map(string))
  default = [
    {
      sg_name = "test"
      sg_rules = local.test
      sg_tags = "sg"
    },
    {
      sg_name = "test1"
      sg_rules = local.test1
      sg_tags = ""
    },
  ]
}

When I do terraform apply , it shows Variables may not be used here which means we cannot use local in variable. And also when I directly put sg_rules = csvdecode(file("${path.module}/csv/test.csv")) , it shows Functions may not be called here

Here is the test.csv file

type,protocol,from,to,cidr_blocks,description
ingress,-1,0,0,10.100.0.0/16,test
ingress,tcp,80,80,10.100.0.0/16,

Please help me solve this issue.

Hi @dipendra.chaudhary,

Input variables are Terraform’s equivalent of arguments to a function in a general-purpose language, so they get their values only from outside the module, and thus default values can only be constants to use in the case where no such value is provided.

However, you can get a similar effect by handling the “default” as a local value rather than directly as a value of the variable, and just declare the variable as optional with the generic default null:

variable "ams_prod_sg_list" {
  description = "sg_name rules"
  type        = list(map(string))
  default     = null
}

locals {
  default_ams_prod_sg_list = [
    {
      sg_name = "test"
      sg_rules = local.test
      sg_tags = "sg"
    },
    {
      sg_name = "test1"
      sg_rules = local.test1
      sg_tags = ""
    },
  ]

  ams_prod_sg_list = var.ams_prod_sg_list != null ? var.ams_prod_sg_list : local.default_ams_prod_sg_list
}

Elsewhere in your module then you can use local.ams_prod_sg_list instead of var.ams_prod_sg_list to get either the user’s provided value or the default.

hello, @apparentlymart, thanks for your response

Tried your method.

resource "aws_security_group" "this" {
  count = length(var.ams_prod_sg_list)
  name        = var.ams_prod_sg_list[count.index].sg_name
  //description = var.sg_name[count.index]
  vpc_id = module.vpc.vpc_id_sg
  
tags = {
    Name = var.ams_prod_sg_list[count.index].sg_tags
  }
}

resource "aws_security_group_rule" "Synoptek-Edge" {
  count             = length(var.ams_prod_sg_list)
  security_group_id = aws_security_group.this.*.id[count.index]
  type              = var.ams_prod_sg_list[count.index].sg_rule
  protocol          = var.ams_prod_sg_list[count.index].sg_rule
  from_port         = var.ams_prod_sg_list[count.index].sg_rule
  to_port           = var.ams_prod_sg_list[count.index].sg_rule
  cidr_blocks       = [var.ams_prod_sg_list[count.index].sg_rule]
  description       = var.ams_prod_sg_list[count.index].sg_rule
locals {
  test = "${csvdecode(file("${path.module}/csv/Synoptek_Edge.csv"))}"
  test1 = "${csvdecode(file("${path.module}/csv/EMRApps-SG.csv"))}"
}
variable "ams_prod_sg_list" {
  description = "sg_name rules"
  type        = list(map(string))
  default     = null
}

locals {
  default_ams_prod_sg_list = [
    {
      sg_name = "test"
      sg_rule = "${local.test}"
      sg_tags = "sg"
    },
    {
      sg_name = "test1"
      sg_rule = "${local.test1}"
      sg_tags = ""
    },
  ]

  ams_prod_sg_list = var.ams_prod_sg_list != null ? var.ams_prod_sg_list : local.default_ams_prod_sg_list
}

csv files

type,protocol,from,to,cidr_blocks,description
ingress,-1,0,0,10.100.0.0/16,
ingress,tcp,80,80,10.100.0.0/16,
type,protocol,from,to,cidr_blocks,description
ingress,tcp,80,80,10.100.0.0/16,

getting the following error

Error: Inconsistent conditional result types
│ 
│   on sg-variable.tf line 46, in locals:
│   46:   ams_prod_sg_list = var.ams_prod_sg_list != null ? var.ams_prod_sg_list : local.default_ams_prod_sg_list
│     ├────────────────
│     │ local.default_ams_prod_sg_list is tuple with 2 elements
│     │ var.ams_prod_sg_list is a list of map of string, known only after apply
│ 
│ The true and false result expressions must have consistent types. The given
│ expressions are list of map of string and tuple, respectively.

Hi @dipendra.chaudhary,

It seems like something here is making Terraform’s automatic type inference not succeed in unifying the two result types. I’m not sure what’s causing that, but when I get errors like this I usually debug them by changing the values to have explicit type conversions rather than relying on the automatic ones, which in this case would mean writing the local value like this:

  default_ams_prod_sg_list = tolist([
    tomap({
      sg_name = "test"
      sg_rule = local.test
      sg_tags = "sg"
    }),
    tomap({
      sg_name = "test1"
      sg_rule = local.test1
      sg_tags = ""
    }),
  ])

The extra tolist and tomap calls here will make explicit the type conversions that Terraform would normally do automatically in order to make the conditional expression have a consistent type. Given that the previous configuration failed I expect that this one will also return an error, but it will hopefully be one that makes it clearer where the problem is because Terraform will be able to better understand our intention rather than relying on automatic type inference.

hello @apparentlymart

used this

variable "ams_prod_sg_list" {
  description = "sg_name rules"
  type        = list(map(string))
  default     = null
}

locals {
  default_ams_prod_sg_list = tolist([
    tomap({
      sg_name = "test"
      sg_rule = "${local.test}"
      sg_tags = "sg"
    }),
    tomap({
      sg_name = "test1"
      sg_rule = "${local.test1}"
      sg_tags = ""
    }),
  ])

  ams_prod_sg_list = var.ams_prod_sg_list != null ? var.ams_prod_sg_list : local.default_ams_prod_sg_list
}

error

Error: Unsupported attribute
│ 
│   on security-group.tf line 91, in resource "aws_security_group_rule" "sg_rule":
│   91:   type              = var.ams_prod_sg_list[count.index].sg_rules.type
│     ├────────────────
│     │ count.index is a number, known only after apply
│     │ var.ams_prod_sg_list is a list of map of string, known only after apply
│ 
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│ 
│   on security-group.tf line 92, in resource "aws_security_group_rule" "sg_rule":
│   92:   protocol          = var.ams_prod_sg_list[count.index].sg_rules.protocol
│     ├────────────────
│     │ count.index is a number, known only after apply
│     │ var.ams_prod_sg_list is a list of map of string, known only after apply
│ 
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│ 
│   on security-group.tf line 93, in resource "aws_security_group_rule" "sg_rule":
│   93:   from_port         = var.ams_prod_sg_list[count.index].sg_rules.from
│     ├────────────────
│     │ count.index is a number, known only after apply
│     │ var.ams_prod_sg_list is a list of map of string, known only after apply
│ 
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│ 
│   on security-group.tf line 94, in resource "aws_security_group_rule" "sg_rule":
│   94:   to_port           = var.ams_prod_sg_list[count.index].sg_rules.to
│     ├────────────────
│     │ count.index is a number, known only after apply
│     │ var.ams_prod_sg_list is a list of map of string, known only after apply
│ 
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│ 
│   on security-group.tf line 95, in resource "aws_security_group_rule" "sg_rule":
│   95:   cidr_blocks       = [var.ams_prod_sg_list[count.index].sg_rules.cidr_blocks]
│     ├────────────────
│     │ count.index is a number, known only after apply
│     │ var.ams_prod_sg_list is a list of map of string, known only after apply
│ 
│ This value does not have any attributes.
╵
╷
│ Error: Unsupported attribute
│ 
│   on security-group.tf line 96, in resource "aws_security_group_rule" "sg_rule":
│   96:   description       = var.ams_prod_sg_list[count.index].sg_rules.description
│     ├────────────────
│     │ count.index is a number, known only after apply
│     │ var.ams_prod_sg_list is a list of map of string, known only after apply
│ 
│ This value does not have any attributes.
╵
╷
│ Error: Invalid function argument
│ 
│   on sg-variable.tf line 34, in locals:
│   34:     tomap({
│   35:       sg_name = "test"
│   36:       sg_rule = "${local.test}"
│   37:       sg_tags = "sg"
│   38:     }),
│     ├────────────────
│     │ local.test is list of object with 2 elements
│ 
│ Invalid value for "v" parameter: cannot convert object to map of any single
│ type.
╵
╷
│ Error: Invalid function argument
│ 
│   on sg-variable.tf line 39, in locals:
│   39:     tomap({
│   40:       sg_name = "test1"
│   41:       sg_rule = "${local.test1}"
│   42:       sg_tags = ""
│   43:     }),
│     ├────────────────
│     │ local.test1 is list of object with 1 element
│ 
│ Invalid value for "v" parameter: cannot convert object to map of any single
│ type.

Ahh, I hadn’t spotted that local.test in your first example was the result of csvdecode. This is therefore correct to fail, because local.test isn’t a valid value for a map of strings: as the error message says, it’s a list of objects.

My initial answer was about how to provide a default value for a variable where the default includes dynamic values from other objects in the module, so I didn’t look closely at the rest of what you showed. Unfortunately it’s hard to guess what your goals are here with everything just being named “test”… can you say a little more about what your intended result is here, so that I can perhaps suggest a different way to achieve it that would be valid in the Terraform language?

@apparentlymart local.test value is there sorry I forgot to mention

locals {
  test = "${csvdecode(file("${path.module}/csv/Synoptek_Edge.csv"))}"
  test1 = "${csvdecode(file("${path.module}/csv/EMRApps-SG.csv"))}"
}

variable "ams_prod_sg_list" {
  description = "sg_name rules"
  type        = list(map(string))
  default     = null
}

locals {
  default_ams_prod_sg_list = tolist([
    tomap({
      sg_name = "test"
      sg_rule = "${local.test}"
      sg_tags = "sg"
    }),
    tomap({
      sg_name = "test1"
      sg_rule = "${local.test1}"
      sg_tags = ""
    }),
  ])

  ams_prod_sg_list = var.ams_prod_sg_list != null ? var.ams_prod_sg_list : local.default_ams_prod_sg_list
}

I’ll explain What I want to achieve

I have 1 default security group and 24 custom security group that I have made by terraform code. Each security group have several rules. I have used the csv file to put the rules. I have used count in security group resource. But for security group rule, I have defined each rule for each security group and it happened to be 24. And also for the source security group id in security group rule, when I put aws_security_group..id, it doesnot get the id and says to put exact the id of the security group i.e.“sg-xxxx”. So I have put it in the security group rule. And also for the IPv6, since in the csv file it is already cidr_block ipv4, I cannot put there IPv6. The code is being so long.

security_group.tf

resource "aws_default_security_group" "this" {
  count = var.create_vpc && var.manage_default_security_group ? 1 : 0

  vpc_id = module.vpc.vpc_id_sg
}

resource "aws_security_group_rule" "default-sg-ingress" {
  source_security_group_id = aws_default_security_group.this[0].id
  security_group_id = aws_default_security_group.this[0].id
  type              = "ingress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}


resource "aws_security_group_rule" "default-sg-egress" {
  security_group_id = aws_default_security_group.this[0].id
  cidr_blocks = ["0.0.0.0/0"]
  type              = "egress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

resource "aws_security_group" "this" {
  count  = var.sg_count
  name   = element(local.sg_name, count.index)
  vpc_id = module.vpc.vpc_id_sg

  tags = {
    Name = "${element(local.sg_name_tag, count.index)}"
  }
}

resource "aws_security_group_rule" "Synoptek-Edge" {
  count             = length(local.security_group_rules)
  security_group_id = aws_security_group.this[0].id
  type              = local.security_group_rules[count.index].type
  protocol          = local.security_group_rules[count.index].protocol
  from_port         = local.security_group_rules[count.index].from
  to_port           = local.security_group_rules[count.index].to
  cidr_blocks       = [local.security_group_rules[count.index].cidr_blocks]
  description       = local.security_group_rules[count.index].description
}

resource "aws_security_group_rule" "EMRApps-SG" {
  count             = length(local.security_group_rules_1)
  security_group_id = aws_security_group.this[1].id
  type              = local.security_group_rules_1[count.index].type
  protocol          = local.security_group_rules_1[count.index].protocol
  from_port         = local.security_group_rules_1[count.index].from
  to_port           = local.security_group_rules_1[count.index].to
  cidr_blocks       = [local.security_group_rules_1[count.index].cidr_blocks]
  description       = local.security_group_rules_1[count.index].description
}

resource "aws_security_group_rule" "AD-SG" {
  count             = length(local.security_group_rules_2)
  security_group_id = aws_security_group.this[2].id
  type              = local.security_group_rules_2[count.index].type
  protocol          = local.security_group_rules_2[count.index].protocol
  from_port         = local.security_group_rules_2[count.index].from
  to_port           = local.security_group_rules_2[count.index].to
  cidr_blocks       = [local.security_group_rules_2[count.index].cidr_blocks]
  description       = local.security_group_rules_2[count.index].description
}

resource "aws_security_group_rule" "AccCntrl-SG" {
  count             = length(local.security_group_rules_3)
  security_group_id = aws_security_group.this[3].id
  type              = local.security_group_rules_3[count.index].type
  protocol          = local.security_group_rules_3[count.index].protocol
  from_port         = local.security_group_rules_3[count.index].from
  to_port           = local.security_group_rules_3[count.index].to
  cidr_blocks       = [local.security_group_rules_3[count.index].cidr_blocks]
  description       = local.security_group_rules_3[count.index].description
}

resource "aws_security_group_rule" "EMR-Database" {
  count             = length(local.security_group_rules_4)
  security_group_id = aws_security_group.this[4].id
  type              = local.security_group_rules_4[count.index].type
  protocol          = local.security_group_rules_4[count.index].protocol
  from_port         = local.security_group_rules_4[count.index].from
  to_port           = local.security_group_rules_4[count.index].to
  cidr_blocks       = [local.security_group_rules_4[count.index].cidr_blocks]
  description       = local.security_group_rules_4[count.index].description
}

resource "aws_security_group_rule" "Test-eCW-Access-SG" {
  count             = length(local.security_group_rules_5)
  security_group_id = aws_security_group.this[5].id
  type              = local.security_group_rules_5[count.index].type
  protocol          = local.security_group_rules_5[count.index].protocol
  from_port         = local.security_group_rules_5[count.index].from
  to_port           = local.security_group_rules_5[count.index].to
  cidr_blocks       = [local.security_group_rules_5[count.index].cidr_blocks]
  description       = local.security_group_rules_5[count.index].description
}

resource "aws_security_group_rule" "Datica-Interface-SG" {
  count             = length(local.security_group_rules_6)
  security_group_id = aws_security_group.this[6].id
  type              = local.security_group_rules_6[count.index].type
  protocol          = local.security_group_rules_6[count.index].protocol
  from_port         = local.security_group_rules_6[count.index].from
  to_port           = local.security_group_rules_6[count.index].to
  cidr_blocks       = [local.security_group_rules_6[count.index].cidr_blocks]
  description       = local.security_group_rules_6[count.index].description
}

resource "aws_security_group_rule" "Tolmar-Interface-SG" {
  count             = length(local.security_group_rules_7)
  security_group_id = aws_security_group.this[7].id
  type              = local.security_group_rules_7[count.index].type
  protocol          = local.security_group_rules_7[count.index].protocol
  from_port         = local.security_group_rules_7[count.index].from
  to_port           = local.security_group_rules_7[count.index].to
  cidr_blocks       = [local.security_group_rules_7[count.index].cidr_blocks]
  description       = local.security_group_rules_7[count.index].description
}

resource "aws_security_group_rule" "eBO-http-SG" {
  count             = length(local.security_group_rules_8)
  security_group_id = aws_security_group.this[8].id
  type              = local.security_group_rules_8[count.index].type
  protocol          = local.security_group_rules_8[count.index].protocol
  from_port         = local.security_group_rules_8[count.index].from
  to_port           = local.security_group_rules_8[count.index].to
  cidr_blocks       = [local.security_group_rules_8[count.index].cidr_blocks]
  description       = local.security_group_rules_8[count.index].description
}

resource "aws_security_group_rule" "DMZ-SFTP" {
  count             = length(local.security_group_rules_9)
  security_group_id = aws_security_group.this[9].id
  type              = local.security_group_rules_9[count.index].type
  protocol          = local.security_group_rules_9[count.index].protocol
  from_port         = local.security_group_rules_9[count.index].from
  to_port           = local.security_group_rules_9[count.index].to
  cidr_blocks       = [local.security_group_rules_9[count.index].cidr_blocks]
  description       = local.security_group_rules_9[count.index].description
}

resource "aws_security_group_rule" "Internal-web_SG" {
  count             = length(local.security_group_rules_10)
  security_group_id = aws_security_group.this[10].id
  type              = local.security_group_rules_10[count.index].type
  protocol          = local.security_group_rules_10[count.index].protocol
  from_port         = local.security_group_rules_10[count.index].from
  to_port           = local.security_group_rules_10[count.index].to
  cidr_blocks       = [local.security_group_rules_10[count.index].cidr_blocks]
  description       = local.security_group_rules_10[count.index].description
}

resource "aws_security_group_rule" "sg-app-server-elb" {
  count             = length(local.security_group_rules_11)
  security_group_id = aws_security_group.this[11].id
  type              = local.security_group_rules_11[count.index].type
  protocol          = local.security_group_rules_11[count.index].protocol
  from_port         = local.security_group_rules_11[count.index].from
  to_port           = local.security_group_rules_11[count.index].to
  cidr_blocks       = [local.security_group_rules_11[count.index].cidr_blocks]
  description       = local.security_group_rules_11[count.index].description
}

resource "aws_security_group_rule" "ams-temp-sg" {
  count             = length(local.security_group_rules_12)
  security_group_id = aws_security_group.this[12].id
  type              = local.security_group_rules_12[count.index].type
  protocol          = local.security_group_rules_12[count.index].protocol
  from_port         = local.security_group_rules_12[count.index].from
  to_port           = local.security_group_rules_12[count.index].to
  cidr_blocks       = [local.security_group_rules_12[count.index].cidr_blocks]
  description       = local.security_group_rules_12[count.index].description
}

resource "aws_security_group_rule" "sg-app-server-elb-instance" {
  count             = length(local.security_group_rules_13)
  security_group_id = aws_security_group.this[13].id
  type              = local.security_group_rules_13[count.index].type
  protocol          = local.security_group_rules_13[count.index].protocol
  from_port         = local.security_group_rules_13[count.index].from
  to_port           = local.security_group_rules_13[count.index].to
  cidr_blocks       = [local.security_group_rules_13[count.index].cidr_blocks]
  description       = local.security_group_rules_13[count.index].description
}

resource "aws_security_group_rule" "sh-enable-ssh-access" {
  count             = length(local.security_group_rules_14)
  security_group_id = aws_security_group.this[14].id
  type              = local.security_group_rules_14[count.index].type
  protocol          = local.security_group_rules_14[count.index].protocol
  from_port         = local.security_group_rules_14[count.index].from
  to_port           = local.security_group_rules_14[count.index].to
  cidr_blocks       = [local.security_group_rules_14[count.index].cidr_blocks]
  description       = local.security_group_rules_14[count.index].description
}

resource "aws_security_group_rule" "sg-reverse-proxy-dmz" {
  count             = length(local.security_group_rules_15)
  security_group_id = aws_security_group.this[15].id
  type              = local.security_group_rules_15[count.index].type
  protocol          = local.security_group_rules_15[count.index].protocol
  from_port         = local.security_group_rules_15[count.index].from
  to_port           = local.security_group_rules_15[count.index].to
  cidr_blocks       = [local.security_group_rules_15[count.index].cidr_blocks]
  description       = local.security_group_rules_15[count.index].description
}

resource "aws_security_group_rule" "sg-reverse-proxy-dmz-instances" {
  count             = length(local.security_group_rules_16)
  security_group_id = aws_security_group.this[16].id
  type              = local.security_group_rules_16[count.index].type
  protocol          = local.security_group_rules_16[count.index].protocol
  from_port         = local.security_group_rules_16[count.index].from
  to_port           = local.security_group_rules_16[count.index].to
  cidr_blocks       = [local.security_group_rules_16[count.index].cidr_blocks]
  description       = local.security_group_rules_16[count.index].description
}

resource "aws_security_group_rule" "sg-ssh-access-from-management-vpc" {
  count             = length(local.security_group_rules_17)
  security_group_id = aws_security_group.this[17].id
  type              = local.security_group_rules_17[count.index].type
  protocol          = local.security_group_rules_17[count.index].protocol
  from_port         = local.security_group_rules_17[count.index].from
  to_port           = local.security_group_rules_17[count.index].to
  cidr_blocks       = [local.security_group_rules_17[count.index].cidr_blocks]
  description       = local.security_group_rules_17[count.index].description
}

resource "aws_security_group_rule" "sg-web-access-ports-from-production" {
  count             = length(local.security_group_rules_18)
  security_group_id = aws_security_group.this[18].id
  type              = local.security_group_rules_18[count.index].type
  protocol          = local.security_group_rules_18[count.index].protocol
  from_port         = local.security_group_rules_18[count.index].from
  to_port           = local.security_group_rules_18[count.index].to
  cidr_blocks       = [local.security_group_rules_18[count.index].cidr_blocks]
  description       = local.security_group_rules_18[count.index].description
}

resource "aws_security_group_rule" "AMS-EphemAC-SG" {
  count             = length(local.security_group_rules_19)
  security_group_id = aws_security_group.this[19].id
  type              = local.security_group_rules_19[count.index].type
  protocol          = local.security_group_rules_19[count.index].protocol
  from_port         = local.security_group_rules_19[count.index].from
  to_port           = local.security_group_rules_19[count.index].to
  cidr_blocks       = [local.security_group_rules_19[count.index].cidr_blocks]
  description       = local.security_group_rules_19[count.index].description
}

resource "aws_security_group_rule" "sg-database-access" {
  count             = length(local.security_group_rules_20)
  security_group_id = aws_security_group.this[20].id
  type              = local.security_group_rules_20[count.index].type
  protocol          = local.security_group_rules_20[count.index].protocol
  from_port         = local.security_group_rules_20[count.index].from
  to_port           = local.security_group_rules_20[count.index].to
  cidr_blocks       = [local.security_group_rules_20[count.index].cidr_blocks]
  description       = local.security_group_rules_20[count.index].description
}

resource "aws_security_group_rule" "d-906701493f_controllers" {
  count             = length(local.security_group_rules_21)
  security_group_id = aws_security_group.this[21].id
  type              = local.security_group_rules_21[count.index].type
  protocol          = local.security_group_rules_21[count.index].protocol
  from_port         = local.security_group_rules_21[count.index].from
  to_port           = local.security_group_rules_21[count.index].to
  cidr_blocks       = [local.security_group_rules_21[count.index].cidr_blocks]
  description       = local.security_group_rules_21[count.index].description
}

resource "aws_security_group_rule" "launch-wizard-5" {
  count             = length(local.security_group_rules_22)
  security_group_id = aws_security_group.this[22].id
  type              = local.security_group_rules_22[count.index].type
  protocol          = local.security_group_rules_22[count.index].protocol
  from_port         = local.security_group_rules_22[count.index].from
  to_port           = local.security_group_rules_22[count.index].to
  cidr_blocks       = [local.security_group_rules_22[count.index].cidr_blocks]
  description       = local.security_group_rules_22[count.index].description
}

resource "aws_security_group_rule" "EMRApps-SG_rule_source" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  description              = "Synoptek Edge"
  source_security_group_id = aws_security_group.this[0].id
  security_group_id        = aws_security_group.this[1].id
}

resource "aws_security_group_rule" "EMRApps-SG_rule_source_1" {

  type                     = "ingress"
  from_port                = "8080"
  to_port                  = "8080"
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.this[11].id
  security_group_id        = aws_security_group.this[1].id
}

resource "aws_security_group_rule" "EMR-Database_rule_source" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  description              = "Synoptek Edge"
  source_security_group_id = aws_security_group.this[0].id
  security_group_id        = aws_security_group.this[4].id
}

resource "aws_security_group_rule" "Internal-web_SG_rule_source" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  description              = "Synoptek Edge"
  source_security_group_id = aws_security_group.this[0].id
  security_group_id        = aws_security_group.this[10].id
}

resource "aws_security_group_rule" "Internal-web_SG_rule_source_1" {

  type                     = "ingress"
  from_port                = "8080"
  to_port                  = "8080"
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.this[11].id
  security_group_id        = aws_security_group.this[10].id
}

resource "aws_security_group_rule" "EMR-Database_ip_v6" {

  type              = "ingress"
  from_port         = "80"
  to_port           = "80"
  protocol          = "tcp"
  ipv6_cidr_blocks  = ["::/0"]
  security_group_id = aws_security_group.this[4].id
}

resource "aws_security_group_rule" "sg-app-server-elb_ip_v6" {

  type              = "ingress"
  from_port         = "80"
  to_port           = "80"
  protocol          = "tcp"
  ipv6_cidr_blocks  = ["::/0"]
  security_group_id = aws_security_group.this[11].id
}

resource "aws_security_group_rule" "sg-app-server-elb_rule_source" {

  type                     = "egress"
  from_port                = "8080"
  to_port                  = "8080"
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.this[1].id
  security_group_id        = aws_security_group.this[11].id
}

resource "aws_security_group_rule" "sg-app-server-elb-instance_rule_source" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  description              = "Synoptek Edge"
  source_security_group_id = aws_security_group.this[0].id
  security_group_id        = aws_security_group.this[13].id
}

resource "aws_security_group_rule" "sg-reverse-proxy-dmz_rule_source" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  description              = "Synoptek Edge"
  source_security_group_id = aws_security_group.this[0].id
  security_group_id        = aws_security_group.this[15].id
}

resource "aws_security_group_rule" "sg-database-access_rule_source_1" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  description              = "Synoptek Edge"
  source_security_group_id = aws_security_group.this[0].id
  security_group_id        = aws_security_group.this[20].id
}

resource "aws_security_group_rule" "sg-database-access_rule_source_2" {

  type                     = "ingress"
  from_port                = "3306"
  to_port                  = "3306"
  protocol                 = "tcp"
  source_security_group_id = aws_security_group.this[13].id
  security_group_id        = aws_security_group.this[20].id
}

resource "aws_security_group_rule" "DMZ-SFTP_rule_source" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  description              = "Synoptek Edge"
  source_security_group_id = aws_security_group.this[0].id
  security_group_id        = aws_security_group.this[9].id
}

resource "aws_security_group_rule" "d-906701493f_controllers_rule_source" {

  type                     = "ingress"
  from_port                = 0
  to_port                  = 0
  protocol                 = -1
  source_security_group_id = aws_security_group.this[21].id
  security_group_id        = aws_security_group.this[21].id
}

locals.tf

sg_name = ["Synoptek Edge", "AMS-EMRApps-SG", ......so on]

sg_name_tag = ["Synoptek Edge", "AMS-EMRApps-SG", ....so on]

security_group_rules = csvdecode(file("${path.module}/csv/Synoptek_Edge.csv"))

  security_group_rules_1 = csvdecode(file("${path.module}/csv/EMRApps-SG.csv"))

  security_group_rules_2 = csvdecode(file("${path.module}/csv/AD-SG.csv"))

  security_group_rules_3 = csvdecode(file("${path.module}/csv/AccCntrl-SG.csv"))

  security_group_rules_4 = csvdecode(file("${path.module}/csv/EMR-Database.csv"))

  security_group_rules_5 = csvdecode(file("${path.module}/csv/Test-eCW-Access-SG.csv"))

  security_group_rules_6 = csvdecode(file("${path.module}/csv/Datica-Interface-SG.csv"))

  security_group_rules_7 = csvdecode(file("${path.module}/csv/Tolmar-Interface-SG.csv"))

  security_group_rules_8 = csvdecode(file("${path.module}/csv/eBO-http-SG.csv"))

  security_group_rules_9 = csvdecode(file("${path.module}/csv/DMZ-SFTP.csv"))

  security_group_rules_10 = csvdecode(file("${path.module}/csv/Internal-web_SG.csv"))

  security_group_rules_11 = csvdecode(file("${path.module}/csv/sg-app-server-elb.csv"))


  security_group_rules_12 = csvdecode(file("${path.module}/csv/ams-temp-sg.csv"))

  security_group_rules_13 = csvdecode(file("${path.module}/csv/sg-app-server-elb-instance.csv"))

  security_group_rules_14 = csvdecode(file("${path.module}/csv/sg-enable-ssh-access.csv"))

  security_group_rules_15 = csvdecode(file("${path.module}/csv/sg-reserve-proxy-dmz.csv"))

  security_group_rules_16 = csvdecode(file("${path.module}/csv/sg-reserve-proxy-dmz-instances.csv"))

  security_group_rules_17 = csvdecode(file("${path.module}/csv/sg-ssh-access-from-management-vpc.csv"))

  security_group_rules_18 = csvdecode(file("${path.module}/csv/sg-web-access-ports-from-production.csv"))

  security_group_rules_19 = csvdecode(file("${path.module}/csv/AMS-EphemAC-SG.csv"))

  security_group_rules_20 = csvdecode(file("${path.module}/csv/sg-database-access.csv"))

  security_group_rules_21 = csvdecode(file("${path.module}/csv/d-906701493f_controllers.csv"))

  security_group_rules_22 = csvdecode(file("${path.module}/csv/launch-wizard-5.csv"))
}

I have list of csv files. One of them is mentioned below. The others are same as it

Synoptek-Edge.csv

type,protocol,from,to,cidr_blocks,description
ingress,-1,0,0,10.100.0.0/16,
ingress,tcp,3389,3389,10.100.10.81/32,AWS
ingress,tcp,3389,3389,104.32.122.85/32,Synoptek
ingress,tcp,3389,3389,204.227.0.127/32,Synoptek
ingress,tcp,3389,3389,204.227.0.254/32,Synoptek
ingress,icmp,-1,-1,10.100.0.0/16,
egress,-1,0,0,0.0.0.0/0,

Now when I do terraform apply, it works fine. But the code is too long and its unmanaged.

What I want to achieve that is minimize the lines of code. In the code mentioned above, there is 24 aws_security_group_rule resources. But I want to make it only one and put it in count and make the variable look as mentioned below:

Synoptek_Edge_Sg = csvdecode(file("${path.module}/csv/Synoptek_Edge.csv"))
AnotherSG = csvdecode(file("${path.module}/csv/antoherSG.csv"))
antoherSG2 = csvdecode(file("${path.module}/csv/antoherSG2.csv"))

variable "Ams_Prod_Sg_List" {
  description = "sg_name rules"
  type        = list(map(string))
  default = [
    {
      sg_name = "Synoptek Edge"
      sg_rules = Synoptek_Edge_Sg
      sg_tags = [""]
    },
    {
      sg_name = "AnotherSG"
      sg_rules = AnotherSG
      sg_tags = [""]
    },
    {
      sg_name = "AnotherSG2"
      antoherSG2 = antoherSG2
      sg_tags = [""]
    },
  ]
}

How can I achieve this? If this is not possible, please let me know another approach to do so but I want to minimize the lines of code and make it as module to use in future.

Thanks for the extra details, @dipendra.chaudhary.

You mentioned at the end of your comment that your goal is to make this a reusable module for declaring security groups and their rules, so I want to start from that requirement and work backwards to discuss ways to achieve that. A Terraform module typically takes the data it will use directly as input variables, rather than indirectly via files on disk, and so I would typically start by defining what data this module will need as input variables:

variable "vpc_id" {
  type = string
}

variable "default_security_group" {
  type = object({
    manage_ingress = bool
    manage_egress  = bool
  })

  # This variable is optional. Setting it
  # means that the caller wants the module
  # to manage the VPC's default security
  # group.
  default = null
}

variable "security_groups" {
  type = map(object({
    name  = string
    tags  = map(string)

    # AWS allows both rules that are matched by
    # source network address and rules matched
    # by source security group. They each need
    # different handling by our module so we'll
    # use two different attributes.
    cidr_rules = map(object({
      type        = string
      protocol    = string
      from_port   = number
      to_port     = number
      cidr_blocks = set(string)
      description = string
    }))
    source_group_rules = map(object({
      type             = string
      protocol         = string
      from_port        = number
      to_port          = number
      source_group_key = string
      description      = string
    }))
  }))
  default = {}
}

Above I’ve defined three variables, the third of which is the main one that will take all of the data related to the security groups and their rules. It’s a two-level hierarchical structure because from your example it seemed like you wanted to have different rules for each group.

Let’s first write out the resource blocks for the default security group, since that isn’t changed much from what you had in your example. I made some changes here you didn’t actually call for just because I thought it useful to show the pattern of deciding whether to activate a particular feature based on whether a variable is null, which is pretty common in shared modules where the optional feature also needs some customizable parameters:

resource "aws_default_security_group" "this" {
  # The [*] operator converts a non-list value
  # that might be null into a list of either
  # zero or one elements.
  count = length(var.default_security_group[*])

  vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "default_ingress" {
  # We'll filter out a non-null object if it
  # doesn't also specify to manage ingress.
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_ingress
  ])

  security_group_id        = aws_default_security_group.this[count.index]
  source_security_group_id = aws_default_security_group.this[count.index]

  type              = "ingress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

resource "aws_security_group_rule" "default_egress" {
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_egress
  ])

  security_group_id        = aws_default_security_group.this[count.index]
  cidr_blocks              = ["0.0.0.0/0"]

  type              = "egress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

With those defined we can now move on to the part which I think you are most interested in: defining the additional security groups and their associated rules. I designed the input variables above to use maps rather than lists because that allows us to use resource for_each to define the multiple objects in a way that will allow you to add and remove security groups and rules in future without disturbing other groups/rules. (There’s some more detail on this decision in When to use for_each instead of count.)

To meet the requirements for for_each we need a map that has one element for each instance we want to declare. var.security_groups is already of a suitable shape for the security groups themselves, but in order to declare the security group rules systematically with for_each we will need a collection with one element per rule across all security groups, and so we can use the flatten function in a local value to derive such a structure:

locals {
  cidr_rules = flatten([
    for group_key, group in var.security_groups : [
      for rule_key, rule in group.cidr_rules : {
        rule_key  = rule_key
        group_key = group_key
        rule      = rule
        group     = group
      }
    ]
  ])
  source_group_rules = flatten([
    for group_key, group in var.security_groups : [
      for rule_key, rule in group.source_group_rules : {
        rule_key  = rule_key
        group_key = group_key
        rule      = rule
        group     = group
      }
    ]
  ])
}

Each of these is a list of objects with rule, rule_key, group, and group_key attributes that describe all of the rules across all of the groups. We need to include the keys in there as well as the groups because the other requirement for for_each is that we can construct a unique key for every element, which in this case will be a combination of the group and rule keys, as we’ll see in a moment.

Now we can declare all of the groups and all of the rules as just three resources with for_each set:

resource "aws_security_group" "additional" {
  for_each = var.security_groups

  vpc_id = var.vpc_id
  name   = each.value.count
  tags   = each.value.tags
}

resource "aws_security_group_rule" "additional_cidr" {
  for_each = {
    for o in local.cidr_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id = aws_security_group.additional[each.value.group_key]
  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  cidr_blocks       = each.value.cidr_blocks
  description       = each.value.description
}

resource "aws_security_group_rule" "additional_source_group" {
  for_each = {
    for o in local.source_group_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id        = aws_security_group.additional[each.value.group_key]
  source_security_group_id = aws_security_group.additional[each.value.rule.source_group_key]

  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  description       = each.value.description
}

So with all of this together what we’ve achieved is a module which can take a hierarchical description of various security groups and their rules, where some rules possibly refer to other security groups, and produce the flat set of security group and rule declarations to produce that result with all of the needed interconnections.

This module doesn’t manage a VPC and it doesn’t do anything with CSV files. It could do those things, but I suggest keeping each module focused on a particular goal and then connecting multiple modules together in order to construct a larger system. In this case, perhaps you’d have a separate module that declares a VPC, but for the sake of keeping things simple to wrap this up I’m going to just assume that the root module will directly declare a VPC and pass it in to the vpc_id variable I declared above.

One way to use this module now would be to eschew the CSV files altogether and to just write the definitions out directly inside the module block:

resource "aws_vpc" "example" {
  # ...
}

module "security" {
  source = "./modules/vpc-security"

  vpc_id = aws_vpc.example.id
  default_security_group = {
    manage_ingress = true
    manage_egress  = true
  }
  security_groups = {
    "Synoptek-Edge" = {
      name = "Synoptek Edge"
      tags = { Name = "Synoptek Edge" }
      cidr_rules = {
        "internal" = {
          type        = "ingress"
          protocol    = -1
          from_port   = 0
          to_port     = 0
          cidr_blocks = ["10.100.0.0/16"]
          description = ""
        },
        "AWS" = {
          type        = "ingress"
          protocol    = "tcp"
          from_port   = 3389
          to_port     = 3389
          cidr_blocks = ["10.100.10.81/32"]
          description = "AWS"
        },
        # (and so on)
      }
      source_group_rules = {}
    }
  }
}

However, you can still use your CSV strategy with this module, by having the calling module dynamically construct the security_groups data structure based on the CSV files it finds on disk:

locals {
  rule_files = fileset("${path.module}/security-groups", "*.csv")
  rule_file_names = {
    # Trim the .csv suffix
    for fn in local.rule_files : fn => substr(fn, 0, length(fn) - 4)
  }
  rule_file_contents = {
    for fn in local.rule_files : fn => csvdecode(file("${path.module}/security-groups/${fn}"))
  }
  rule_file_cidr = {
    for fn, raws in local.rule_file_contents : fn => [
      for raw in raws : raw
      if try(raw.cidr_blocks, "") != ""
    ]
  }
  rule_file_srcgrp = {
    for fn, raws in local.rule_file_contents : fn => [
      for raw in raws : raw
      if try(raw.source_group, "") != ""
    ]
  }
  groups = {
    for fn, raw in local.rule_file_contents : local.rule_file_names[fn] => {
      name = local.rule_file_names[fn]
      tags = { Name = local.rule_file_names[fn] }
      cidr_rules = {
        for raw in local.rule_file_cidr[fn] : raw.name => {
          type        = raw.type
          protocol    = raw.protocol
          from_port   = tonumber(raw.from)
          to_port     = tonumber(raw.to)
          cidr_blocks = split(" ", raw.cidr_blocks)
          description = try(raw.description, raw.name)
        }
      }
      source_group_rules = {
        for raw in local.rule_file_srcgrp[fn] : raw.name => {
          type             = raw.type
          protocol         = raw.protocol
          from_port        = tonumber(raw.from)
          to_port          = tonumber(raw.to)
          source_group_key = raw.source_group
          description      = try(raw.description, raw.name)
        }
      }
    }
  }
}

The above will search a subdirectory of the module directory for files named with a .csv suffix and create a security group object for each one, with a nested rule for each row in the CSV file that has either cidr_blocks or source_group set. I made a few assumptions here that I want to be explicit about:

  • This assumes that all of your CSV files would have an additional column name which is a unique key for each rule within a particular file. It’ll use that as the key in one of the two rule maps.
  • I assumed that a particular rule is either CIDR-based or source-group-based, never both at the same time. If it’s source-group-based then I assumed you’d indicate that by writing the unique name of the other group in that field, which would be the same as the filename defining the other group but without the .csv suffix.

I’ve also just written all of this code directly into the comment box and not tested it against real Terraform, so I expect I’ve made some syntax errors and other mistakes along the way. Hopefully any error messages Terraform returns will give you a hint about how to fix my errors. If not, feel free to post another comment with the full text of the error and I’ll try to explain what mistake it’s reporting.

I hope this helps show some different patterns that’ll be useful when building your module! Of course, you don’t have to take exactly what I showed here for your final module, but I tried to show a few different common techniques here which come up in many reusable Terraform module designs, so you can take those patterns to use in future modules too.

hi, @apparentlymart. thanks for your answer

I used the first method which is without using csv. I have made these directories and folders

[terraform-community]$ ls
main.tf  modules
[terraform-community]$ cd modules/
[modules]$ ls
vpc-security
[modules]$ cd vpc-security/
[vpc-security]$ ls
locals.tf  main.tf  variables.tf

Now inside vpc-security, main.tf

resource "aws_default_security_group" "this" {
  # The [*] operator converts a non-list value
  # that might be null into a list of either
  # zero or one elements.
  count = length(var.default_security_group[*])

  vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "default_ingress" {
  # We'll filter out a non-null object if it
  # doesn't also specify to manage ingress.
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_ingress
  ])

  security_group_id        = aws_default_security_group.this[count.index]
  source_security_group_id = aws_default_security_group.this[count.index]

  type              = "ingress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

resource "aws_security_group_rule" "default_egress" {
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_egress
  ])

  security_group_id        = aws_default_security_group.this[count.index]
  cidr_blocks              = ["0.0.0.0/0"]

  type              = "egress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

resource "aws_security_group" "additional" {
  for_each = var.security_groups

  vpc_id = var.vpc_id
  name   = each.value.count
  tags   = each.value.tags
}

resource "aws_security_group_rule" "additional_cidr" {
  for_each = {
    for o in local.cidr_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id = aws_security_group.additional[each.value.group_key]
  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  cidr_blocks       = each.value.cidr_blocks
  description       = each.value.description
}

resource "aws_security_group_rule" "additional_source_group" {
  for_each = {
    for o in local.source_group_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id        = aws_security_group.additional[each.value.group_key]
  source_security_group_id = aws_security_group.additional[each.value.rule.source_group_key]

  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.from_port
  to_port           = each.value.to_port
  description       = each.value.description
}

inside vpc-security, variables.tf

variable "vpc_id" {
  type = string
}

variable "default_security_group" {
  type = object({
    manage_ingress = bool
    manage_egress  = bool
  })

  # This variable is optional. Setting it
  # means that the caller wants the module
  # to manage the VPC's default security
  # group.
  default = null
}

variable "security_groups" {
  type = map(object({
    name  = string
    tags  = map(string)

    # AWS allows both rules that are matched by
    # source network address and rules matched
    # by source security group. They each need
    # different handling by our module so we'll
    # use two different attributes.
    cidr_rules = map(object({
      type        = string
      protocol    = string
      from_port   = number
      to_port     = number
      cidr_blocks = set(string)
      description = string
    }))
    source_group_rules = map(object({
      type             = string
      protocol         = string
      from_port        = number
      to_port          = number
      source_group_key = string
      description      = string
    }))
  }))
  default = {}
}

inside vpc-security, locals.tf

locals {
  cidr_rules = flatten([
    for group_key, group in var.security_groups : [
      for rule_key, rule in group.cidr_rules : {
        rule_key  = rule_key
        group_key = group_key
        rule      = rule
        group     = group
      }
    ]
  ])
  source_group_rules = flatten([
    for group_key, group in var.security_groups : [
      for rule_key, rule in group.source_group_rules : {
        rule_key  = rule_key
        group_key = group_key
        rule      = rule
        group     = group
      }
    ]
  ])
}

outside the module main.tf

resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

module "security" {
  source = "./modules/vpc-security"

  vpc_id = aws_vpc.example.id
  default_security_group = {
    manage_ingress = true
    manage_egress  = true
  }
  security_groups = {
    "Synoptek-Edge" = {
      name = "Synoptek Edge"
      tags = { Name = "Synoptek Edge" }
      cidr_rules = {
        "internal" = {
          type        = "ingress"
          protocol    = -1
          from_port   = 0
          to_port     = 0
          cidr_blocks = ["10.100.0.0/16"]
          description = ""
        },
        "AWS" = {
          type        = "ingress"
          protocol    = "tcp"
          from_port   = 3389
          to_port     = 3389
          cidr_blocks = ["10.100.10.81/32"]
          description = "AWS"
        },
        # (and so on)
      }
      source_group_rules = {}
    }
  }
}

I’m getting this error

provider.aws.region
  The region where AWS operations will take place. Examples
  are us-east-1, us-west-2, etc.

  Enter a value: us-west-2 

╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 18, in resource "aws_security_group_rule" "default_ingress":
│   18:   security_group_id        = aws_default_security_group.this[count.index]
│     ├────────────────
│     │ aws_default_security_group.this is tuple with 1 element
│     │ count.index is 0
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 19, in resource "aws_security_group_rule" "default_ingress":
│   19:   source_security_group_id = aws_default_security_group.this[count.index]
│     ├────────────────
│     │ aws_default_security_group.this is tuple with 1 element
│     │ count.index is 0
│ 
│ Inappropriate value for attribute "source_security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 33, in resource "aws_security_group_rule" "default_egress":
│   33:   security_group_id        = aws_default_security_group.this[count.index]
│     ├────────────────
│     │ aws_default_security_group.this is tuple with 1 element
│     │ count.index is 0
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 46, in resource "aws_security_group" "additional":
│   46:   name   = each.value.count
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "count".

@apparentlymart

I have commented the default security group codes as I just want the additional security group for now and test on it. I have also edited some codes line. I was having following error so I have put rule after the each.value

provider.aws.region
  The region where AWS operations will take place. Examples
  are us-east-1, us-west-2, etc.

  Enter a value: us-west-2

╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 1 attribute "Synoptek-Edge"
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 1 attribute "Synoptek-Edge"
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 60, in resource "aws_security_group_rule" "additional_cidr":
│   60:   from_port         = each.value.from_port
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "from_port".
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 60, in resource "aws_security_group_rule" "additional_cidr":
│   60:   from_port         = each.value.from_port
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "from_port".
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 61, in resource "aws_security_group_rule" "additional_cidr":
│   61:   to_port           = each.value.to_port
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "to_port".
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 61, in resource "aws_security_group_rule" "additional_cidr":
│   61:   to_port           = each.value.to_port
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "to_port".
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 62, in resource "aws_security_group_rule" "additional_cidr":
│   62:   cidr_blocks       = each.value.cidr_blocks
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "cidr_blocks".
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 62, in resource "aws_security_group_rule" "additional_cidr":
│   62:   cidr_blocks       = each.value.cidr_blocks
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "cidr_blocks".
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 63, in resource "aws_security_group_rule" "additional_cidr":
│   63:   description       = each.value.description
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "description".
╵
╷
│ Error: Unsupported attribute
│ 
│   on modules/vpc-security/main.tf line 63, in resource "aws_security_group_rule" "additional_cidr":
│   63:   description       = each.value.description
│     ├────────────────
│     │ each.value is object with 4 attributes
│ 
│ This object does not have an attribute named "description".
  protocol          = each.value.rule.protocol
  from_port         = each.value.rule.from_port
  to_port           = each.value.rule.to_port
  cidr_blocks       = each.value.rule.cidr_blocks

Please find the all codes below

/*
resource "aws_default_security_group" "this" {
  # The [*] operator converts a non-list value
  # that might be null into a list of either
  # zero or one elements.
  count = length(var.default_security_group[*])

  vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "default_ingress" {
  # We'll filter out a non-null object if it
  # doesn't also specify to manage ingress.
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_ingress
  ])

  security_group_id        = aws_default_security_group.this[count.index]
  source_security_group_id = aws_default_security_group.this[count.index]

  type              = "ingress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

resource "aws_security_group_rule" "default_egress" {
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_egress
  ])

  security_group_id        = aws_default_security_group.this[count.index]
  cidr_blocks              = ["0.0.0.0/0"]

  type              = "egress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}
*/

resource "aws_security_group" "additional" {
  for_each = var.security_groups

  vpc_id = var.vpc_id
  name   = each.value.name
  tags   = each.value.tags
}

resource "aws_security_group_rule" "additional_cidr" {
  for_each = {
    for o in local.cidr_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id = "${aws_security_group.additional[each.value.group_key]}"
  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.rule.from_port
  to_port           = each.value.rule.to_port
  cidr_blocks       = each.value.rule.cidr_blocks
  description       = each.value.rule.description
}

resource "aws_security_group_rule" "additional_source_group" {
  for_each = {
    for o in local.source_group_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id        = "${aws_security_group.additional[each.value.group_key]}"
  source_security_group_id = aws_security_group.additional[each.value.rule.source_group_key]

  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.rule.from_port
  to_port           = each.value.rule.to_port
  description       = each.value.rule.description
}

error message

terraform plan
provider.aws.region
  The region where AWS operations will take place. Examples
  are us-east-1, us-west-2, etc.

  Enter a value: us-west-2

╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = "${aws_security_group.additional[each.value.group_key]}"
│     ├────────────────
│     │ aws_security_group.additional is object with 1 attribute "Synoptek-Edge"
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = "${aws_security_group.additional[each.value.group_key]}"
│     ├────────────────
│     │ aws_security_group.additional is object with 1 attribute "Synoptek-Edge"
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.

@apparentlymart
I have also tested the second approach.

[security-groups]$ ls
nextsg.csv  Synoptek-Edge.csv

Synoptek-Edge.csv I have added additional column name at first as you told before.

name,type,protocol,from,to,cidr_blocks,description
a,ingress,-1,0,0,10.100.0.0/16,
b,ingress,tcp,3389,3389,10.100.10.81/32,AWS-SYN-JUMP02
c,ingress,tcp,3389,3389,104.32.122.85/32,Synoptek
d,ingress,tcp,3389,3389,204.227.0.127/32,Synoptek
e,ingress,tcp,3389,3389,204.227.0.254/32,Synoptek
f,ingress,icmp,-1,-1,10.100.0.0/16,
g,egress,-1,0,0,0.0.0.0/0,
locals {
  rule_files = fileset("${path.module}/security-groups", "*.csv")
  rule_file_names = {
    # Trim the .csv suffix
    for fn in local.rule_files : fn => substr(fn, 0, length(fn) - 4)
  }
  rule_file_contents = {
    for fn in local.rule_files : fn => csvdecode(file("${path.module}/security-groups/${fn}"))
  }
  rule_file_cidr = {
    for fn, raws in local.rule_file_contents : fn => [
      for raw in raws : raw
      if try(raw.cidr_blocks, "") != ""
    ]
  }
  rule_file_srcgrp = {
    for fn, raws in local.rule_file_contents : fn => [
      for raw in raws : raw
      if try(raw.source_group, "") != ""
    ]
  }
  groups = {
    for fn, raw in local.rule_file_contents : local.rule_file_names[fn] => {
      name = local.rule_file_names[fn]
      tags = { Name = local.rule_file_names[fn] }
      cidr_rules = {
        for raw in local.rule_file_cidr[fn] : raw.name => {
          type        = raw.type
          protocol    = raw.protocol
          from_port   = tonumber(raw.from)
          to_port     = tonumber(raw.to)
          cidr_blocks = split(" ", raw.cidr_blocks)
          description = try(raw.description, raw.name)
        }
      }
      source_group_rules = {
        for raw in local.rule_file_srcgrp[fn] : raw.name => {
          type             = raw.type
          protocol         = raw.protocol
          from_port        = tonumber(raw.from)
          to_port          = tonumber(raw.to)
          source_group_key = raw.source_group
          description      = try(raw.description, raw.name)
        }
      }
    }
  }
}
resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}
provider "aws" {
  region = "us-west-2"
}
module "security" {
  source = "./modules/vpc-security"
  vpc_id = aws_vpc.example.id
  security_groups = local.groups
}

error message

Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "nextsg"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "nextsg"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "nextsg"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "nextsg"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "nextsg"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "nextsg"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "Synoptek-Edge"
│ 
│ Inappropriate value for attribute "security_group_id": string required.
╵
╷
│ Error: Incorrect attribute value type
│ 
│   on modules/vpc-security/main.tf line 57, in resource "aws_security_group_rule" "additional_cidr":
│   57:   security_group_id = aws_security_group.additional[each.value.group_key]
│     ├────────────────
│     │ aws_security_group.additional is object with 2 attributes
│     │ each.value.group_key is "nextsg"
│ 
│ Inappropriate value for attribute "security_group_id": string required.

modules/vpc-security/main.tf

44 resource "aws_security_group" "additional" {
 45   for_each = var.security_groups
 46 
 47   vpc_id = var.vpc_id
 48   name   = each.value.name
 49   tags   = each.value.tags
 50 }
 51 
 52 resource "aws_security_group_rule" "additional_cidr" {
 53   for_each = {
 54     for o in local.cidr_rules : "${o.group_key}:${o.rule_key}" => o
 55   }
 56 
 57   security_group_id = aws_security_group.additional[each.value.group_key]
 58   type              = each.value.rule.type
 59   protocol          = each.value.rule.protocol
 60   from_port         = each.value.rule.from_port
 61   to_port           = each.value.rule.to_port
 62   cidr_blocks       = each.value.rule.cidr_blocks
 63   description       = each.value.rule.description
 64 }
 65 
 66 resource "aws_security_group_rule" "additional_source_group" {
 67   for_each = {
 68     for o in local.source_group_rules : "${o.group_key}:${o.rule_key}" => o
 69   }
 70 
 71   security_group_id        = "${aws_security_group.additional[each.value.group_key]}"
 72   source_security_group_id = aws_security_group.additional[each.value.rule.source_group_key]
 73 
 74   type              = each.value.rule.type
 75   protocol          = each.value.rule.protocol
 76   from_port         = each.value.rule.from_port
 77   to_port           = each.value.rule.to_port
 78   description       = each.value.rule.description
 79 }

I want this approach. This looks nice to me. But facing error

For this default security group situation, it looks like I just forgot the .id attribute on the end of this:

  security_group_id        = aws_default_security_group.this[count.index].id

We need the id of the security group here, not the entire object representing a security group.

…and indeed, looks like I made the same mistake in all of the other rules too. This is, of course, the result of copy and paste! I think you add .id to the end of all of these in a way similar to what I showed in my last comment then it should get past this particular set of errors.

Hi, @apparentlymart, Thanks for the answer. I tried and it worked.
I want to put ipv6_cidr_blocks. You can see in main.tf, variable.tf and locals.tf I have put ipv6_cidr_blocks section. I there is some error in that. please look into it

I have edited the code however, it is giving me error

inside modules/vpc-security/main.tf

resource "aws_default_security_group" "this" {
  # The [*] operator converts a non-list value
  # that might be null into a list of either
  # zero or one elements.
  count = length(var.default_security_group[*])

  vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "default_ingress" {
  # We'll filter out a non-null object if it
  # doesn't also specify to manage ingress.
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_ingress
  ])

  security_group_id        = aws_default_security_group.this[count.index].id
  source_security_group_id = aws_default_security_group.this[count.index].id

  type              = "ingress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

resource "aws_security_group_rule" "default_egress" {
  count = length([
    for o in var.default_security_group[*] : o
    if o.manage_egress
  ])

  security_group_id        = aws_default_security_group.this[count.index].id
  cidr_blocks              = ["0.0.0.0/0"]

  type              = "egress"
  protocol          = -1
  from_port         = 0
  to_port           = 0
}

resource "aws_security_group" "additional" {
  for_each = var.security_groups

  vpc_id = var.vpc_id
  name   = each.value.name
  tags   = each.value.tags
}

resource "aws_security_group_rule" "additional_cidr" {
  for_each = {
    for o in local.cidr_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id = aws_security_group.additional[each.value.group_key].id
  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.rule.from_port
  to_port           = each.value.rule.to_port
  cidr_blocks       = each.value.rule.cidr_blocks
  description       = each.value.rule.description
}

resource "aws_security_group_rule" "additional_cidr_v6" {
  for_each = {
    for o in local.ipv6_cidr_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id = aws_security_group.additional[each.value.group_key].id
  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.rule.from_port
  to_port           = each.value.rule.to_port
  ipv6_cidr_blocks  = each.value.rule.ipv6_cidr_blocks
  description       = each.value.rule.description
}


resource "aws_security_group_rule" "additional_source_group" {
  for_each = {
    for o in local.source_group_rules : "${o.group_key}:${o.rule_key}" => o
  }

  security_group_id        = aws_security_group.additional[each.value.group_key].id
  source_security_group_id = aws_security_group.additional[each.value.rule.source_group_key].id

  type              = each.value.rule.type
  protocol          = each.value.rule.protocol
  from_port         = each.value.rule.from_port
  to_port           = each.value.rule.to_port
  description       = each.value.rule.description
}

inside modules/vpc-security/variables.tf

variable "vpc_id" {
  type = string
}

variable "default_security_group" {
  type = object({
    manage_ingress = bool
    manage_egress  = bool
  })

  # This variable is optional. Setting it
  # means that the caller wants the module
  # to manage the VPC's default security
  # group.
  default = null
}

variable "security_groups" {
  type = map(object({
    name  = string
    tags  = map(string)

    # AWS allows both rules that are matched by
    # source network address and rules matched
    # by source security group. They each need
    # different handling by our module so we'll
    # use two different attributes.
    cidr_rules = map(object({
      type        = string
      protocol    = string
      from_port   = number
      to_port     = number
      cidr_blocks = set(string)
      description = string
    }))
    ipv6_cidr_rules = map(object({
      type        = string
      protocol    = string
      from_port   = number
      to_port     = number
      ipv6_cidr_blocks = set(string)
      description = string
    }))
    source_group_rules = map(object({
      type             = string
      protocol         = string
      from_port        = number
      to_port          = number
      source_group_key = string
      description      = string
    }))
  }))
  default = {}
}

inside modules/vpc-security/locals.tf

locals {
  cidr_rules = flatten([
    for group_key, group in var.security_groups : [
      for rule_key, rule in group.cidr_rules : {
        rule_key  = rule_key
        group_key = group_key
        rule      = rule
        group     = group
      }
    ]
  ])
  ipv6_cidr_rules = flatten([
    for group_key, group in var.security_groups : [
      for rule_key, rule in group.ipv6_cidr_rules : {
        rule_key  = rule_key
        group_key = group_key
        rule      = rule
        group     = group
      }
    ]
  ])
  source_group_rules = flatten([
    for group_key, group in var.security_groups : [
      for rule_key, rule in group.source_group_rules : {
        rule_key  = rule_key
        group_key = group_key
        rule      = rule
        group     = group
      }
    ]
  ])
}

local.tf outside the module directory

locals {
  rule_files = fileset("${path.module}/security-groups", "*.csv")
  rule_file_names = {
    # Trim the .csv suffix
    for fn in local.rule_files : fn => substr(fn, 0, length(fn) - 4)
  }
  rule_file_contents = {
    for fn in local.rule_files : fn => csvdecode(file("${path.module}/security-groups/${fn}"))
  }
  rule_file_cidr = {
    for fn, raws in local.rule_file_contents : fn => [
      for raw in raws : raw
      if try(raw.cidr_blocks, "") != ""
    ]
  }
  rule_file_ipv6_cidr = {
    for fn, raws in local.rule_file_contents : fn => [
      for raw in raws : raw
      if try(raw.ipv6_cidr_blocks, "") != ""
    ]
  }

  rule_file_srcgrp = {
    for fn, raws in local.rule_file_contents : fn => [
      for raw in raws : raw
      if try(raw.source_group, "") != ""
    ]
  }
  groups = {
    for fn, raw in local.rule_file_contents : local.rule_file_names[fn] => {
      name = local.rule_file_names[fn]
      tags = { Name = local.rule_file_names[fn] }
      cidr_rules = {
        for raw in local.rule_file_cidr[fn] : raw.name => {
          type        = raw.type
          protocol    = raw.protocol
          from_port   = tonumber(raw.from)
          to_port     = tonumber(raw.to)
          cidr_blocks = split(" ", raw.cidr_blocks)
          description = try(raw.description, raw.name)
        }
      }
      ipv6_cidr_rules = {
        for raw in local.rule_file_ipv6_cidr[fn] : raw.name => {
          type        = raw.type
          protocol    = raw.protocol
          from_port   = tonumber(raw.from)
          to_port     = tonumber(raw.to)
          cidr_blocks = split(" ", raw.ipv6_cidr_blocks)
          description = try(raw.description, raw.name)
        }
      }
      source_group_rules = {
        for raw in local.rule_file_srcgrp[fn] : raw.name => {
          type             = raw.type
          protocol         = raw.protocol
          from_port        = tonumber(raw.from)
          to_port          = tonumber(raw.to)
          source_group_key = raw.source_group
          description      = try(raw.description, raw.name)
        }
      }
    }
  }
}

main.tf outisde module folder

resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

provider "aws" {
  region = "us-west-2"
}

module "security" {
  source = "./modules/vpc-security"

  vpc_id = aws_vpc.example.id

  security_groups = local.groups

  default_security_group = {
    manage_ingress = true
    manage_egress  = true
  }
}

csv file

name,type,protocol,from,to,cidr_blocks,ipv6_cidr_blocks,source_group,description
a,ingress,tcp,443,443,0.0.0.0/0,::/0,,AWS-SYN-JUMP02
b,ingress,tcp,3389,3389,10.100.10.81/32,,,AWS-SYN-JUMP02
c,ingress,tcp,80,80,,,Synoptek-Edge,AWS-SYN-JUMP02
g,egress,-1,0,0,0.0.0.0/0,,,

error message

Error: Invalid value for module argument
│ 
│   on main.tf line 14, in module "security":
│   14:   security_groups = local.groups
│ 
│ The given value is not suitable for child module variable "security_groups"
│ defined at modules/vpc-security/variables.tf:18,1-27: element "nextsg":
│ attribute "ipv6_cidr_rules": element "a": attribute "ipv6_cidr_blocks" is
│ required.

I want to the sg rule of cidr_blocks, ipv6_cidr_blocks and source security group id


as shown in the image

its fine now

edited the code of locals.tf outside module folder. I put ipv6_cidr_blocks instead of cidr_blocks

ipv6_cidr_blocks = split(" ", raw.ipv6_cidr_blocks)

Hi, @apparentlymart

Got another issue

The code is taking the tag:Name and Security Group Name from the csv file name. But I have some csv files that name starts with “sg-” and when I do apply, it says that the security group name should not start with “sg-”. Now what I want to do is that I want to replace those Security Group Name from sg- to different name. How can I do so?

I have used replace function

but how can I use it for multiple names?

groups = {
    for fn, raw in local.rule_file_contents : local.rule_file_names[fn] => {
      name = replace(local.rule_file_names[fn], "/sg-a/", "test-a")

There are many csv files whose name starts with sg- like sg-a, sg-b, sg-c. I the code I have only put for sg-a. How can I do for all.

I want to change sg-a as test-a, sg-b as test-b and so on

Hi @dipendra.chaudhary,

If you want to systematically always replace and sg- prefix with a test- prefix then I think you can match that with a regular expression pattern like this:

replace(input, "/^sg-/", "test-")

The ^ symbol here represents “start of string”, so this regular expression will only match the characters sg- at the start of the given string, and if it finds them it’ll replace them with the string given in the third argument, “test-”. If you use that with the input string set to local.rule_file_names[fn] as in your example then I think it should work.

You can read more about the regular expression pattern syntax as part of the documentation of the regex function.

thanks @apparentlymart

lets say if I have 3 sgs with csv file name (of course the tag name will be taken from csv file name) sg-1.csv, sg-2.csv and sg-3.csv. And I want to make the security group name test-1, test-2 and test-3 respectively. How can I do that?