fugue / regula

Regula checks infrastructure as code templates (Terraform, CloudFormation, k8s manifests) for AWS, Azure, Google Cloud, and Kubernetes security and compliance using Open Policy Agent/Rego
https://regula.dev/
Apache License 2.0
962 stars 108 forks source link

[BUG] Identifying resources when using dynamic blocks #411

Open Mohitsharma44 opened 1 year ago

Mohitsharma44 commented 1 year ago

Describe the bug

Using regula on terraform resources that have dynamic blocks, generates mock_resources (and mock_input) that contains a "dynamic" list/array of maps but no "key" to identify what values corresponds to which argument.

How you're running Regula Please include versions of all relevant tools. Some examples:

Operating System macOS v13.3.1

Steps to reproduce

# Loding just the tf file...
❯ regula repl main.tf --no-built-ins
INFO Loaded 1 IaC configurations as test inputs
Regula 3.2.1 - built with OPA v0.46.0-dev
Run 'help' to see a list of commands.

> data.main_tf.mock_resources["aws_security_group.main"]
{
  "_filepath": "main.tf",
  "_provider": "aws",
  "_tags": {
    "Name": "AWS security group dynamic block"
  },
  "_type": "aws_security_group",
  "dynamic": [
    {
      "content": [
        {
          "cidr_blocks": [
            "0.0.0.0/0"
          ],
          "description": "ingress.value.description",
          "from_port": "ingress.value.port",
          "protocol": "tcp",
          "to_port": "ingress.value.port"
        }
      ],
      "for_each": [
        {
          "description": "foo-ing",
          "port": "123"
        },
        {
          "description": "bar-ing",
          "port": "456"
        }
      ]
    },
    {
      "content": [
        {
          "cidr_blocks": [
            "0.0.0.0/0"
          ],
          "description": "egress.value.description",
          "from_port": "egress.value.port",
          "protocol": "tcp",
          "to_port": "egress.value.port"
        }
      ],
      "for_each": [
        {
          "description": "foo-eg",
          "port": "123"
        },
        {
          "description": "bar-eg",
          "port": "456"
        }
      ]
    }
  ],
  "id": "aws_security_group.main",
  "name": "resource_with_dynamic_block",
  "tags": {
    "Name": "AWS security group dynamic block"
  },
  "vpc_id": "foobar"
}

IaC Configuration

# main.tf

variable ingress_rules {
  type = list(map(string))
  default = [{
    "description" = "foo-ing",
    "port" = "123"
  },{
    "description" = "bar-ing",
    "port" = "456"
  }
  ]
}

variable egress_rules {
  type = list(map(string))
  default = [{
    "description" = "foo-eg",
    "port" = "123"
  },{
    "description" = "bar-eg",
    "port" = "456"
  }
  ]
}

resource "aws_security_group" "main" {
   name   = "resource_with_dynamic_block"
   vpc_id = "foobar"

   dynamic "ingress" {
      for_each = var.ingress_rules

      content {
         description = ingress.value.description
         from_port   = ingress.value.port
         to_port     = ingress.value.port
         protocol    = "tcp"
         cidr_blocks = ["0.0.0.0/0"]
      }
   }

   dynamic "egress" {
      for_each = var.egress_rules

      content {
         description = egress.value.description
         from_port   = egress.value.port
         to_port     = egress.value.port
         protocol    = "tcp"
         cidr_blocks = ["0.0.0.0/0"]
      }
   }

   tags = {
      Name = "AWS security group dynamic block"
   }
}

It'd be beneficial for dynamic list of maps to have a key that would identify the resource that the dynamic block belonged to. So for example,

....
  "dynamic": [
    {
      "ingress": [        <-------- instead of content
        {
          "cidr_blocks": [
            "0.0.0.0/0"
          ],
          "description": "ingress.value.description",
          "from_port": "ingress.value.port",
          "protocol": "tcp",
          "to_port": "ingress.value.port"
        }
      ],
      "for_each": [
        {
          "description": "foo-ing",
          "port": "123"
        },
        {
          "description": "bar-ing",
          "port": "456"
        }
      ]
    },
    {
      "egress": [        <-------- instead of content
        {
          "cidr_blocks": [
            "0.0.0.0/0"
          ],
          "description": "egress.value.description",
          "from_port": "egress.value.port",
          "protocol": "tcp",
          "to_port": "egress.value.port"
        }
      ],
      "for_each": [
        {
          "description": "foo-eg",
          "port": "123"
        },
        {
          "description": "bar-eg",
          "port": "456"
        }
      ]
    }
  ],
....

Additional context Add any other context about the problem here.

amansingh14 commented 9 months ago

I am facing the same issue with a dynamic block while checking the HCL code. Here is the sample code

resource "google_compute_subnetwork" "subnetwork" {
  for_each                 = local.subnets
  name                     = each.value.subnet_name
  ip_cidr_range            = each.value.subnet_ip
  region                   = each.value.subnet_region
  private_ip_google_access = lookup(each.value, "subnet_private_access", "false")
  dynamic "log_config" {
    for_each = lookup(each.value, "subnet_flow_logs", false) ? [{
      aggregation_interval = lookup(each.value, "subnet_flow_logs_interval", "INTERVAL_5_SEC")
      flow_sampling        = lookup(each.value, "subnet_flow_logs_sampling", "0.5")
      metadata             = lookup(each.value, "subnet_flow_logs_metadata", "INCLUDE_ALL_METADATA")
    }] : []
    content {
      aggregation_interval = log_config.value.aggregation_interval
      flow_sampling        = log_config.value.flow_sampling
      metadata             = log_config.value.metadata
    }
  }