Repository: bridgecrewio/checkov

Issue:
Checkov action fails for certain modules due to what seems like datatype issue
Facing an issue when checkov runs this module `"terraform-aws-modules/eks/aws"`. Specifically for `metadata_options` and `remote_access`. Seems like the issue is in the code below where checkov is expecting a list but gets a dictionary so it throws an error, if I remove the indices then it runs ok. Also, it seems that it's linked to the external modules, cause if I run checkov with `--download-external-modules false`, the error is not there.

The ERROR:
```
Traceback (most recent call last):
  File "/usr/local/bin/checkov", line 9, in <module>
    sys.exit(run())
  File "/usr/local/lib/python3.8/site-packages/checkov/main.py", line 330, in run
    scan_reports = runner_registry.run(root_folder=root_folder, external_checks_dir=external_checks_dir,
  File "/usr/local/lib/python3.8/site-packages/checkov/common/runners/runner_registry.py", line 79, in run
    self.runners[0].run(root_folder, external_checks_dir=external_checks_dir, files=files,
  File "/usr/local/lib/python3.8/site-packages/checkov/terraform/runner.py", line 147, in run
    self.check_tf_definition(report, root_folder, runner_filter, collect_skip_comments)
  File "/usr/local/lib/python3.8/site-packages/checkov/terraform/runner.py", line 292, in check_tf_definition
    self.run_all_blocks(definition, self.context, full_file_path, root_folder, report,
  File "/usr/local/lib/python3.8/site-packages/checkov/terraform/runner.py", line 304, in run_all_blocks
    self.run_block(definition[block_type], definitions_context,
  File "/usr/local/lib/python3.8/site-packages/checkov/terraform/runner.py", line 376, in run_block
    results = registry.scan(scanned_file, entity, skipped_checks, runner_filter)
  File "/usr/local/lib/python3.8/site-packages/checkov/common/checks/base_check_registry.py", line 1[27](https://github.com/amun/infra-terrafrom/actions/runs/3437973445/jobs/5733435968#step:5:28), in scan
    result = self.run_check(check, entity_configuration, entity_name, entity_type, scanned_file, skip_info)
  File "/usr/local/lib/python3.8/site-packages/checkov/common/checks/base_check_registry.py", line 141, in run_check
    result = check.run(
  File "/usr/local/lib/python3.8/site-packages/checkov/common/checks/base_check.py", line 70, in run
    check_result["result"] = self.scan_entity_conf(entity_configuration, entity_type)
  File "/usr/local/lib/python3.8/site-packages/checkov/terraform/checks/resource/base_resource_check.py", line 43, in scan_entity_conf
    return self.scan_resource_conf(conf)
  File "/usr/local/lib/python3.8/site-packages/checkov/terraform/checks/resource/aws/IMDSv1Disabled.py", line [33](https://github.com/amun/infra-terrafrom/actions/runs/3437973445/jobs/5733435968#step:5:34), in scan_resource_conf
    if 'metadata_options' not in conf.keys() or not isinstance(conf['metadata_options'][0], dict):
KeyError: 0
```

Yaml file:
```
name: Checkov
on:
  pull_request:
    branches:
      - staging
      - production
      - global

jobs:
  checkov-job:
    strategy:
      matrix:
        module: [aws-config, iam, ecs, eks, flux, lambdas, step-functions, vpc, vpc-peering, ecr]

    runs-on: 'self-hosted'
    name: checkov
    steps:
      - 
        name: Checkout repo
        uses: actions/checkout@master

      - 
        name: Run Checkov
        id: checkov
        uses: bridgecrewio/checkov-action@master
        with:
          directory: modules/${{ matrix.module }}
          quiet: true # optional: display only failed checks
          soft_fail: true # optional: do not return an error code if there are failed checks
          framework: terraform # optional: run only on a specific infrastructure {cloudformation,terraform,kubernetes,all}
          output_format: sarif # optional: the output format, one of: cli, json, junitxml, github_failed_only, or sarif. Default: sarif
          download_external_modules: true # optional: download external terraform modules from public git repositories and terraform registry
          log_level: WARNING # optional: set log level. Default WARNING

```

**Functions in checkov source code that might be responsible for the error?:**

from `IMDSv1Disabled.py`:
```
def scan_resource_conf(self, conf):
        """
        Looks for if the metadata service is disabled or requires session tokens:
            https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/instance#metadata-options
            or
            https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/launch_template#metadata-options

        :param conf: dict of supported resource configuration
        :return: <CheckResult>
        """
        if 'metadata_options' not in conf.keys() or not isinstance(conf['metadata_options'][0], dict):
            return CheckResult.FAILED
        metadata_options = conf['metadata_options'][0]
        if ('http_tokens' in metadata_options and metadata_options["http_tokens"] == ["required"]) or \
                ('http_endpoint' in metadata_options and metadata_options["http_endpoint"] == ["disabled"]):
            return CheckResult.PASSED
        return CheckResult.FAILED
```
from `EKSNodeGroupRemoteAccess.py`:
```
def scan_resource_conf(self, conf):
        remote_access = conf.get("remote_access")
        if remote_access and remote_access[0] and "ec2_ssh_key" in remote_access[0].keys() \
                and "source_security_group_ids" not in remote_access[0].keys():
            return CheckResult.FAILED
        return CheckResult.PASSED
```

Appreciate your help and let me know if you need me to provide more details.


Discussion:
hey @Julio-M thanks for reaching out. Yeah this looks like a `checkov` issue, so I'm moving it to the other project 🙂 

Resolving patch:
diff --git a/checkov/terraform/parser_functions.py b/checkov/terraform/parser_functions.py
--- a/checkov/terraform/parser_functions.py
+++ b/checkov/terraform/parser_functions.py
@@ -199,7 +199,8 @@ def process_dynamic_values(conf: Dict[str, List[Any]]) -> bool:
                 conf[element_name] = element_value["content"]
             else:
                 # this should be the result of a successful dynamic block rendering
-                conf[element_name] = element_value
+                # in some cases a whole dict is added, which doesn't have a list around it
+                conf[element_name] = element_value if isinstance(element_value, list) else [element_value]
 
         has_dynamic_block = True
 


Test patch:
diff --git a/tests/terraform/graph/resources/dynamic_lambda_function/lambda.tf b/tests/terraform/graph/resources/dynamic_lambda_function/lambda.tf
--- a/tests/terraform/graph/resources/dynamic_lambda_function/lambda.tf
+++ b/tests/terraform/graph/resources/dynamic_lambda_function/lambda.tf
@@ -9,4 +9,11 @@ resource "aws_lambda_function" "lambda" {
       target_arn = dead_letter_config.value.target_arn
     }
   }
+
+  dynamic "environment" {
+    for_each = var.environment == null ? [] : [var.environment]
+    content {
+      variables = environment.value.variables
+    }
+  }
 }
\ No newline at end of file
diff --git a/tests/terraform/graph/runner/test_runner.py b/tests/terraform/graph/runner/test_runner.py
new file mode 100644
--- /dev/null
+++ b/tests/terraform/graph/runner/test_runner.py
@@ -0,0 +1,30 @@
+from pathlib import Path
+
+from checkov.runner_filter import RunnerFilter
+from checkov.terraform.runner import Runner
+
+
+def test_dynamics():
+    # given
+    test_files_dir = Path(__file__).parent.parent / "resources/dynamic_lambda_function"
+
+    # when
+    report = Runner().run(
+        root_folder=str(test_files_dir),
+        runner_filter=RunnerFilter(
+            checks=[
+                "CKV_AWS_45",
+                "CKV_AWS_116",
+                "CKV_AWS_173",
+                "CKV_AWS_272",
+            ]
+        ),
+    )
+
+    # then
+    summary = report.get_summary()
+
+    assert summary["passed"] == 2
+    assert summary["failed"] == 2
+    assert summary["skipped"] == 0
+    assert summary["parsing_errors"] == 0