Search code examples
amazon-web-servicesterraformterraform-provider-awsamazon-eks

How to create multiple AWS security group rules by reading multiple yaml files using terraform?


I want to create multiple aws security group rules by reading multiple yaml files located under a different directory using terraform but I am not sure how exactly can I achieve it.

Below is the folder structure:

-> env
    |
    -> prod
        |
        -> external
            |
            -> IXCC
                |
                -> values.yaml
            |
            -> IXCB
                |
                -> values.yaml

my aws_security_group_rules.tf file look like below:

module "security_group_rules" {
    source                  = "../kem-infra-modules/iam-security-group-rules"
    for_each                = fileset("${path.module}/env/prod/external/*/", "values.yaml")

    # Assuming you have output variables from your security_group module
    security_group_id       = module.security_group.security_group_id

    # Use the contents of the YAML file to populate the security group rules
    from_port               = yamldecode(file("${path.module}/env/prod/external/${dirname(each.value)}/values.yaml"))["fromPort"]
    description             = yamldecode(file("${path.module}/env/prod/external/${dirname(each.value)}/values.yaml"))["definition"]
    protocol                = upper(yamldecode(file("${path.module}/env/prod/external/${dirname(each.value)}/values.yaml"))["protocol"])
    to_port                 = yamldecode(file("${path.module}/env/prod/external/${dirname(each.value)}/values.yaml"))["toPort"]
    type                    = yamldecode(file("${path.module}/env/prod/external/${dirname(each.value)}/values.yaml"))["type"]
    cidr_blocks             = yamldecode(file("${path.module}/env/prod/external/${dirname(each.value)}/values.yaml"))["sourceIpCidrs"]

}

My values.yaml files under each folder looks like below:

IXCB - values.yaml:

fromPort: 443
definition: testing
protocol: tcp
toPort: 443
type: ingress
sourceIpCidrs: ["xx.xx.xx.xx/32", "xx.xx.xx.xx/32", "xx.xx.xx.xx/32"]
IXCC - values.yaml:

fromPort: 443
definition: testing
protocol: tcp
toPort: 443
type: ingress
sourceIpCidrs: ["xx.xx.xx.xx/32", "xx.xx.xx.xx/32", "xx.xx.xx.xx/32"]

I am expecting two security group rules to be created but the plan stage shows 0 to add,0 to change & 0 to delete.

I also tried using locals but that too didnt work:

locals.tf:
locals {
  security_group_rules = flatten([
    for dir in fileset("${path.module}/env/prod/external", "*") : [
      for mapping in yamldecode(file("${path.module}/env/prod/external/${dir}/values.yaml"))["principalMappings"] : {
        customer_name = dir
        from_port     = mapping.fromPort
        description   = mapping.definition
        protocol      = upper(mapping.protocol)
        to_port       = mapping.toPort
        type          = mapping.type
        cidr_blocks   = mapping.sourceIpCidrs
      }
    ]
  ])
}

tried calling this locals into the aws security group rules. it still failed.


Solution

  • Since I don't have the code from the module you are using, I will give an example of how to create security group rules locally:

    locals {
      sg_rules_path = "${path.module}/env/prod/external"
    }
    
    resource "aws_security_group" "test" {
    
    }
    
    resource "aws_security_group_rule" "test" {
      for_each          = fileset(local.sg_rules_path, "*/values.yaml")
      security_group_id = aws_security_group.test.id
      description       = yamldecode(file("${local.sg_rules_path}/${each.key}")).definition
      from_port         = yamldecode(file("${local.sg_rules_path}/${each.key}")).fromPort
      protocol          = upper(yamldecode(file("${local.sg_rules_path}/${each.key}")).protocol)
      to_port           = yamldecode(file("${local.sg_rules_path}/${each.key}")).toPort
      type              = yamldecode(file("${local.sg_rules_path}/${each.key}")).type
      cidr_blocks       = yamldecode(file("${local.sg_rules_path}/${each.key}")).sourceIpCidrs
    }
    

    I have named your example YML files sg1 and sg2 and stored them at the same level as the root module. This will yield the following output:

    Terraform will perform the following actions:
    
      # aws_security_group.test will be created
      + resource "aws_security_group" "test" {
          + arn                    = (known after apply)
          + description            = "Managed by Terraform"
          + egress                 = (known after apply)
          + id                     = (known after apply)
          + ingress                = (known after apply)
          + name                   = (known after apply)
          + name_prefix            = (known after apply)
          + owner_id               = (known after apply)
          + revoke_rules_on_delete = false
          + tags_all               = (known after apply)
          + vpc_id                 = (known after apply)
        }
    
      # aws_security_group_rule.test["sg1/values.yaml"] will be created
      + resource "aws_security_group_rule" "test" {
          + cidr_blocks              = [
              + "1.1.1.1/32",
              + "2.2.2.2/32",
              + "3.3.3.3/32",
            ]
          + description              = "testing"
          + from_port                = 443
          + id                       = (known after apply)
          + protocol                 = "tcp"
          + security_group_id        = (known after apply)
          + security_group_rule_id   = (known after apply)
          + self                     = false
          + source_security_group_id = (known after apply)
          + to_port                  = 443
          + type                     = "ingress"
        }
    
      # aws_security_group_rule.test["sg2/values.yaml"] will be created
      + resource "aws_security_group_rule" "test" {
          + cidr_blocks              = [
              + "4.4.4.4/32",
              + "5.5.5.5/32",
              + "6.6.6.6/32",
            ]
          + description              = "testing"
          + from_port                = 443
          + id                       = (known after apply)
          + protocol                 = "tcp"
          + security_group_id        = (known after apply)
          + security_group_rule_id   = (known after apply)
          + self                     = false
          + source_security_group_id = (known after apply)
          + to_port                  = 443
          + type                     = "ingress"
        }
    

    Having this in mind, I would argue something like the following should work:

    locals {
      sg_rules_path = "${path.module}/env/prod/external"
    }
    
    module "security_group_rules" {
        source                  = "../kem-infra-modules/iam-security-group-rules"
        for_each                = fileset(local.sg_rules_path, "*/values.yaml")
    
        # Assuming you have output variables from your security_group module
        security_group_id       = module.security_group.security_group_id
    
        # Use the contents of the YAML file to populate the security group rules
        from_port               = yamldecode(file("${local.sg_rules_path}/${each.key}")).fromPort
        description             = yamldecode(file("${local.sg_rules_path}/${each.key}")).definition
        protocol                = upper(yamldecode(file("${local.sg_rules_path}/${each.key}")).protocol)
        to_port                 = yamldecode(file("${local.sg_rules_path}/${each.key}")).toPort
        type                    = yamldecode(file("${local.sg_rules_path}/${each.key}")).type
        cidr_blocks             = yamldecode(file("${local.sg_rules_path}/${each.key}")).sourceIpCidrs
    }