Repository: bridgecrewio/checkov

Issue:
Resources that contain scalar fields named "dynamic" not parsing properly
**Describe the issue**

Running checkov against a Terraform resource which includes a scalar field called "dynamic" which _is not_ an actual HCL `dynamic` block causes a crash.

**Examples**

Using the below resource definition:

```hcl
resource "elasticsearch_index_template" "logstash" {
  body = jsonencode(
    {
      index_patterns = [
        "test-*",
      ]
      mappings = {
        properties = {
          foo = {
            # Note that "dynamic" here is not HCL dynamic block, but rather an elasticsearch property declared using HCL
            dynamic = true
            properties = {
              bar = {
                type = "string"
              }
            }
          }
        }
      }
    }
  )

  name = "test"
}
```

We found this bug while attempting to write a custom check to flag up use of the `provisioner` block which, since it is a [Terraform configuration](https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax) available to all `resource` blocks, requires a custom check where `supported_resources = ("*")`:

```python
from typing import Any
from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck
from checkov.common.models.enums import CheckResult, CheckCategories


class DynamicTest(BaseResourceCheck):
    def __init__(self) -> None:
        name = "Testing crash where dynamic is a scalar value"
        id = "CKV_TEST_1"
        supported_resources = ("*",)
        categories = (CheckCategories.GENERAL_SECURITY,)
        super().__init__(
            name=name,
            id=id,
            categories=categories,
            supported_resources=supported_resources,
        )

    def scan_resource_conf(self, conf: dict[str, list[Any]]) -> CheckResult:
        return CheckResult.PASSED


check = DynamicTest()
```

We can then run checkov:

```shell
$ checkov -f test.tf --external-checks-dir=./checks
```

**Exception Trace**

The relevant part of the trace caused by this issue is below:

```
2023-10-13 10:22:17,018 [MainThread  ] [DEBUG]  Running check: Testing crash where dynamic is a scalar value on file /test.tf
2023-10-13 10:22:17,019 [MainThread  ] [ERROR]  Failed to run check CKV_TEST_1 on /test.tf:elasticsearch_index_template.test
Traceback (most recent call last):
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/common/checks/base_check.py", line 75, in run
    check_result["result"] = self.scan_entity_conf(entity_configuration, entity_type)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/checks/resource/base_resource_check.py", line 42, in scan_entity_conf
    handle_dynamic_values(conf)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 186, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block[0], has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 182, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 182, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 182, in handle_dynamic_values
    has_dynamic_block = handle_dynamic_values(conf_block, has_dynamic_block)
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 189, in handle_dynamic_values
    return process_dynamic_values(conf) or has_dynamic_block
  File "/Users/rob.westbrook/.pyenv/versions/3.10.4/lib/python3.10/site-packages/checkov/terraform/parser_functions.py", line 195, in process_dynamic_values
    for dynamic_element in conf.get("dynamic", {}):
TypeError: 'bool' object is not iterable
2023-10-13 10:22:17,020 [MainThread  ] [INFO ]  Entity configuration: {'__end_line__': 24, '__start_line__': 1, 'body': [{'index_patterns': ['test-*'], 'mappings': {'properties': {'foo': {'dynamic': True, 'properties': {'bar': {'type': 'string'}}}}}}], 'name': ['test'], '__address__': 'elasticsearch_index_template.test'}
```

**Desktop (please complete the following information):**

 - OS: MacOS 13.4.1 (Ventura)
 - Checkov Version: 2.5.8

**Additional context**

I believe this is due to the [process_dynamic_values](https://github.com/bridgecrewio/checkov/blob/main/checkov/terraform/parser_functions.py#L194) method, which directly fetches the "dynamic" property of `conf` but does not verify its Iterable-ness before trying to loop through and parse its elements.



Discussion:

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
@@ -7,7 +7,12 @@
 
 from checkov.common.util.data_structures_utils import pickle_deepcopy
 from checkov.common.util.type_forcers import convert_str_to_bool
-from checkov.common.util.parser_utils import eval_string, split_merge_args, string_to_native, to_string
+from checkov.common.util.parser_utils import (
+    eval_string,
+    split_merge_args,
+    string_to_native,
+    to_string,
+)
 
 #
 # Functions defined in this file implement terraform functions.
@@ -121,7 +126,7 @@ def toset(original: str, **_: Any) -> set[Any] | str:
 
 def tomap(original: str, **_: Any) -> dict[Hashable, Any] | str:
     # https://www.terraform.io/docs/language/functions/tomap.html
-    original = original.replace(":", "=")     # converted to colons by parser #shrug
+    original = original.replace(":", "=")  # converted to colons by parser #shrug
 
     altered_value = eval_string(original)
     if altered_value is None or not isinstance(altered_value, dict):
@@ -136,7 +141,7 @@ def map(original: str, **_: Any) -> dict[Hashable, Any] | str:
     #       the issue, act like it's a list (to allow comma separation) and let the HCL
     #       parser deal with it. Then iterating the list is easy.
     converted_to_list = eval_string(f"[{original}]")
-    if converted_to_list is None or len(converted_to_list) & 1:       # none or odd number of args
+    if converted_to_list is None or len(converted_to_list) & 1:  # none or odd number of args
         return FUNCTION_FAILED
 
     return create_map(converted_to_list)
@@ -190,8 +195,13 @@ def handle_dynamic_values(conf: Dict[str, List[Any]], has_dynamic_block: bool =
 
 
 def process_dynamic_values(conf: Dict[str, List[Any]]) -> bool:
+    dynamic_conf: Union[List[Any], Dict[str, List[Any]]] = conf.get("dynamic", {})
+
+    if not isinstance(dynamic_conf, list):
+        return False
+
     has_dynamic_block = False
-    for dynamic_element in conf.get("dynamic", {}):
+    for dynamic_element in dynamic_conf:
         if isinstance(dynamic_element, str):
             try:
                 dynamic_element = json.loads(dynamic_element)


Test patch: