We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Rules CKV_AWS_18 and CKV_AWS_19 fail if s3 resources are defined in a terraform module
**Describe the issue**

When upgrading the AWS provider in Terraform to a version > 3.75 there has been significant change to the aws_s3_bucket resource. If the S3 resources are referenced in a child module rather than the root level it seems Checkov is still failing CKV_AWS_18 and CKV_AWS_19 based upon our usage. I believe these to be false positives.

CKV_AWS_18: "Ensure the S3 bucket has access logging enabled"

access logging is configured by the resource aws_s3_bucket_logging

CKV_AWS_19: "Ensure all data stored in the S3 bucket is securely encrypted at rest"

encryption at rest is configured by the resource aws_s3_bucket_server_side_encryption_configuration

**Examples**

### modules/s3/main.tf

```

resource "aws_kms_key" "s3_key" {
  description             = "KMS key 1"
  deletion_window_in_days = 10
}

resource "aws_s3_bucket" "bucket" {
  bucket = "sample-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "bucket" {
  bucket = aws_s3_bucket.bucket.id

  rule {
    apply_server_side_encryption_by_default {
      kms_master_key_id = aws_kms_key.s3_key.key_id
      sse_algorithm     = "aws:kms"
    }
    bucket_key_enabled = false
  }
}

resource "aws_s3_bucket_logging" "bucket" {
  bucket = aws_s3_bucket.bucket.id

  target_bucket = "logging-bucket"
  target_prefix = "sample-bucket/"
}

```
### main.tf

```

module "s3" {
  source = "./modules/s3"
}

```

Command: checkov -f plan.json --check CKV_AWS_18,CKV_AWS_19 --repo-root-for-plan-enrichment "./"

Expected both rules pass for resource aws_s3_bucket.bucket

**Version (please complete the following information):**
 - Checkov Version 2.1.81

**Additional context**
Terraform version: 1.2.6
AWS Provider version: 4.23.0

If I move the contents of the module file to the root module both rules pass as expected.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `checkov/terraform/plan_parser.py`
Content:
```
1 from __future__ import annotations
2 
3 import itertools
4 from typing import Optional, Tuple, Dict, List, Any
5 
6 from checkov.common.parsers.node import DictNode, ListNode
7 from checkov.terraform.context_parsers.tf_plan import parse
8 
9 SIMPLE_TYPES = (str, int, float, bool)
10 TF_PLAN_RESOURCE_ADDRESS = "__address__"
11 TF_PLAN_RESOURCE_CHANGE_ACTIONS = "__change_actions__"
12 
13 
14 def _is_simple_type(obj: Any) -> bool:
15     if obj is None:
16         return True
17     if isinstance(obj, SIMPLE_TYPES):
18         return True
19     return False
20 
21 
22 def _is_list_of_simple_types(obj: Any) -> bool:
23     if not isinstance(obj, list):
24         return False
25     for i in obj:
26         if not _is_simple_type(i):
27             return False
28     return True
29 
30 
31 def _is_list_of_dicts(obj: Any) -> bool:
32     if not isinstance(obj, list):
33         return False
34     for i in obj:
35         if isinstance(i, dict):
36             return True
37     return False
38 
39 
40 def _hclify(obj: DictNode, conf: Optional[DictNode] = None, parent_key: Optional[str] = None) -> Dict[str, List[Any]]:
41     ret_dict = {}
42     if not isinstance(obj, dict):
43         raise Exception("this method receives only dicts")
44     if hasattr(obj, "start_mark") and hasattr(obj, "end_mark"):
45         obj["start_line"] = obj.start_mark.line
46         obj["end_line"] = obj.end_mark.line
47     for key, value in obj.items():
48         if _is_simple_type(value) or _is_list_of_simple_types(value):
49             if parent_key == "tags":
50                 ret_dict[key] = value
51             else:
52                 ret_dict[key] = _clean_simple_type_list([value])
53 
54         if _is_list_of_dicts(value):
55             child_list = []
56             conf_val = conf.get(key, []) if conf else []
57             for internal_val, internal_conf_val in itertools.zip_longest(value, conf_val):
58                 if isinstance(internal_val, dict):
59                     child_list.append(_hclify(internal_val, internal_conf_val, parent_key=key))
60             if key == "tags":
61                 ret_dict[key] = [child_list]
62             else:
63                 ret_dict[key] = child_list
64         if isinstance(value, dict):
65             child_dict = _hclify(value, parent_key=key)
66             if parent_key == "tags":
67                 ret_dict[key] = child_dict
68             else:
69                 ret_dict[key] = [child_dict]
70     if conf and isinstance(conf, dict):
71         found_ref = False
72         for conf_key in conf.keys() - obj.keys():
73             ref = next((x for x in conf[conf_key].get("references", []) if not x.startswith(("var.", "local."))), None)
74             if ref:
75                 ret_dict[conf_key] = [ref]
76                 found_ref = True
77         if not found_ref:
78             for value in conf.values():
79                 if isinstance(value, dict) and "references" in value.keys():
80                     ret_dict["references_"] = value["references"]
81 
82     return ret_dict
83 
84 
85 def _prepare_resource_block(
86     resource: DictNode, conf: Optional[DictNode], resource_changes: dict[str, dict[str, Any]]
87 ) -> tuple[dict[str, dict[str, Any]], bool]:
88     """hclify resource if pre-conditions met.
89 
90     :param resource: tf planned_values resource block
91     :param conf: tf configuration resource block
92     :param resource_changes: tf resource_changes block
93 
94     :returns:
95         - resource_block: a list of strings representing the header columns
96         - prepared: whether conditions met to prepare data
97     """
98 
99     resource_block: Dict[str, Dict[str, Any]] = {}
100     resource_block[resource["type"]] = {}
101     prepared = False
102     mode = ""
103     if "mode" in resource:
104         mode = resource.get("mode")
105     # Rare cases where data block appears in resources with same name as resource block and only partial values
106     # and where *_module resources don't have values field
107     if mode == "managed" and "values" in resource:
108         expressions = conf.get("expressions") if conf else None
109 
110         resource_conf = _hclify(resource["values"], expressions)
111         resource_address = resource.get("address")
112         resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address
113 
114         changes = resource_changes.get(resource_address)
115         if changes:
116             resource_conf[TF_PLAN_RESOURCE_CHANGE_ACTIONS] = changes.get("change", {}).get("actions") or []
117 
118         resource_block[resource["type"]][resource.get("name", "default")] = resource_conf
119         prepared = True
120     return resource_block, prepared
121 
122 
123 def _find_child_modules(
124     child_modules: ListNode, resource_changes: dict[str, dict[str, Any]]
125 ) -> List[Dict[str, Dict[str, Any]]]:
126     """
127     Find all child modules if any. Including any amount of nested child modules.
128     :type: child_modules: list of tf child_module objects
129     :rtype: resource_blocks: list of hcl resources
130     """
131     resource_blocks = []
132     for child_module in child_modules:
133         if child_module.get("child_modules", []):
134             nested_child_modules = child_module.get("child_modules", [])
135             nested_blocks = _find_child_modules(nested_child_modules, resource_changes)
136             for resource in nested_blocks:
137                 resource_blocks.append(resource)
138         for resource in child_module.get("resources", []):
139             resource_block, prepared = _prepare_resource_block(
140                 resource=resource,
141                 conf=None,
142                 resource_changes=resource_changes,
143             )
144             if prepared is True:
145                 resource_blocks.append(resource_block)
146     return resource_blocks
147 
148 
149 def _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:
150     """Returns a resource address to resource changes dict"""
151 
152     resource_changes_map = {}
153 
154     resource_changes = template.get("resource_changes")
155     if resource_changes and isinstance(resource_changes, list):
156         resource_changes_map = {
157             change.get("address", ""): change
158             for change in resource_changes
159         }
160 
161     return resource_changes_map
162 
163 
164 def parse_tf_plan(tf_plan_file: str, out_parsing_errors: Dict[str, str]) -> Tuple[Optional[Dict[str, Any]], Optional[List[Tuple[int, str]]]]:
165     """
166     :type tf_plan_file: str - path to plan file
167     :rtype: tf_definition dictionary and template_lines of the plan file
168     """
169     tf_definition: Dict[str, Any] = {"resource": []}
170     template, template_lines = parse(tf_plan_file, out_parsing_errors)
171     if not template:
172         return None, None
173 
174     resource_changes = _get_resource_changes(template=template)
175 
176     for resource in template.get("planned_values", {}).get("root_module", {}).get("resources", []):
177         conf = next(
178             (
179                 x
180                 for x in template.get("configuration", {}).get("root_module", {}).get("resources", [])
181                 if x["type"] == resource["type"] and x["name"] == resource["name"]
182             ),
183             None,
184         )
185         resource_block, prepared = _prepare_resource_block(
186             resource=resource,
187             conf=conf,
188             resource_changes=resource_changes,
189         )
190         if prepared is True:
191             tf_definition["resource"].append(resource_block)
192     child_modules = template.get("planned_values", {}).get("root_module", {}).get("child_modules", [])
193     # Terraform supports modules within modules so we need to search
194     # in nested modules to find all resource blocks
195     resource_blocks = _find_child_modules(child_modules, resource_changes)
196     for resource in resource_blocks:
197         tf_definition["resource"].append(resource)
198     return tf_definition, template_lines
199 
200 
201 def _clean_simple_type_list(value_list: List[Any]) -> List[Any]:
202     """
203     Given a list of simple types return a cleaned list of simple types.
204     Converts booleans that are input as strings back to booleans to maintain consistent expectations for later evaluation.
205     Sometimes Terraform Plan will output Map values as strings regardless of boolean input.
206     """
207     for i in range(len(value_list)):
208         if isinstance(value_list[i], str):
209             lower_case_value = value_list[i].lower()
210             if lower_case_value == "true":
211                 value_list[i] = True
212             if lower_case_value == "false":
213                 value_list[i] = False         
214     return value_list
215
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/checkov/terraform/plan_parser.py b/checkov/terraform/plan_parser.py
--- a/checkov/terraform/plan_parser.py
+++ b/checkov/terraform/plan_parser.py
@@ -121,24 +121,50 @@
 
 
 def _find_child_modules(
-    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]]
+    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]], root_module_conf: dict[str, Any]
 ) -> List[Dict[str, Dict[str, Any]]]:
+    """ Find all child modules if any. Including any amount of nested child modules.
+
+    :param child_modules: list of terraform child_module objects
+    :param resource_changes: a resource address to resource changes dict
+    :param root_module_conf: configuration block of the root module
+    :returns:
+        list of terraform resource blocks
     """
-    Find all child modules if any. Including any amount of nested child modules.
-    :type: child_modules: list of tf child_module objects
-    :rtype: resource_blocks: list of hcl resources
-    """
+
     resource_blocks = []
     for child_module in child_modules:
-        if child_module.get("child_modules", []):
-            nested_child_modules = child_module.get("child_modules", [])
-            nested_blocks = _find_child_modules(nested_child_modules, resource_changes)
+        nested_child_modules = child_module.get("child_modules", [])
+        if nested_child_modules:
+            nested_blocks = _find_child_modules(
+                child_modules=nested_child_modules,
+                resource_changes=resource_changes,
+                root_module_conf=root_module_conf
+            )
             for resource in nested_blocks:
                 resource_blocks.append(resource)
+
+        module_address = child_module.get("address", "")
+        module_call_resources = _get_module_call_resources(
+            module_address=module_address,
+            root_module_conf=root_module_conf,
+        )
+
         for resource in child_module.get("resources", []):
+            module_call_conf = None
+            if module_address and module_call_resources:
+                module_call_conf = next(
+                    (
+                        module_call_resource
+                        for module_call_resource in module_call_resources
+                        if f"{module_address}.{module_call_resource['address']}" == resource["address"]
+                    ),
+                    None
+                )
+
             resource_block, prepared = _prepare_resource_block(
                 resource=resource,
-                conf=None,
+                conf=module_call_conf,
                 resource_changes=resource_changes,
             )
             if prepared is True:
@@ -146,6 +172,18 @@
     return resource_blocks
 
 
+def _get_module_call_resources(module_address: str, root_module_conf: dict[str, Any]) -> list[dict[str, Any]]:
+    """Extracts the resources from the 'module_calls' block under 'configuration'"""
+
+    for module_name in module_address.split("."):
+        if module_name == "module":
+            # module names are always prefixed with 'module.', therefore skip it
+            continue
+        root_module_conf = root_module_conf.get("module_calls", {}).get(module_name, {}).get("module", {})
+
+    return root_module_conf.get("resources", [])
+
+
 def _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:
     """Returns a resource address to resource changes dict"""
 
@@ -190,9 +228,14 @@
         if prepared is True:
             tf_definition["resource"].append(resource_block)
     child_modules = template.get("planned_values", {}).get("root_module", {}).get("child_modules", [])
+    root_module_conf = template.get("configuration", {}).get("root_module", {})
     # Terraform supports modules within modules so we need to search
     # in nested modules to find all resource blocks
-    resource_blocks = _find_child_modules(child_modules, resource_changes)
+    resource_blocks = _find_child_modules(
+        child_modules=child_modules,
+        resource_changes=resource_changes,
+        root_module_conf=root_module_conf,
+    )
     for resource in resource_blocks:
         tf_definition["resource"].append(resource)
     return tf_definition, template_lines

{"golden_diff": "diff --git a/checkov/terraform/plan_parser.py b/checkov/terraform/plan_parser.py\n--- a/checkov/terraform/plan_parser.py\n+++ b/checkov/terraform/plan_parser.py\n@@ -121,24 +121,50 @@\n \n \n def _find_child_modules(\n-    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]]\n+    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]], root_module_conf: dict[str, Any]\n ) -> List[Dict[str, Dict[str, Any]]]:\n+    \"\"\" Find all child modules if any. Including any amount of nested child modules.\n+\n+    :param child_modules: list of terraform child_module objects\n+    :param resource_changes: a resource address to resource changes dict\n+    :param root_module_conf: configuration block of the root module\n+    :returns:\n+        list of terraform resource blocks\n     \"\"\"\n-    Find all child modules if any. Including any amount of nested child modules.\n-    :type: child_modules: list of tf child_module objects\n-    :rtype: resource_blocks: list of hcl resources\n-    \"\"\"\n+\n     resource_blocks = []\n     for child_module in child_modules:\n-        if child_module.get(\"child_modules\", []):\n-            nested_child_modules = child_module.get(\"child_modules\", [])\n-            nested_blocks = _find_child_modules(nested_child_modules, resource_changes)\n+        nested_child_modules = child_module.get(\"child_modules\", [])\n+        if nested_child_modules:\n+            nested_blocks = _find_child_modules(\n+                child_modules=nested_child_modules,\n+                resource_changes=resource_changes,\n+                root_module_conf=root_module_conf\n+            )\n             for resource in nested_blocks:\n                 resource_blocks.append(resource)\n+\n+        module_address = child_module.get(\"address\", \"\")\n+        module_call_resources = _get_module_call_resources(\n+            module_address=module_address,\n+            root_module_conf=root_module_conf,\n+        )\n+\n         for resource in child_module.get(\"resources\", []):\n+            module_call_conf = None\n+            if module_address and module_call_resources:\n+                module_call_conf = next(\n+                    (\n+                        module_call_resource\n+                        for module_call_resource in module_call_resources\n+                        if f\"{module_address}.{module_call_resource['address']}\" == resource[\"address\"]\n+                    ),\n+                    None\n+                )\n+\n             resource_block, prepared = _prepare_resource_block(\n                 resource=resource,\n-                conf=None,\n+                conf=module_call_conf,\n                 resource_changes=resource_changes,\n             )\n             if prepared is True:\n@@ -146,6 +172,18 @@\n     return resource_blocks\n \n \n+def _get_module_call_resources(module_address: str, root_module_conf: dict[str, Any]) -> list[dict[str, Any]]:\n+    \"\"\"Extracts the resources from the 'module_calls' block under 'configuration'\"\"\"\n+\n+    for module_name in module_address.split(\".\"):\n+        if module_name == \"module\":\n+            # module names are always prefixed with 'module.', therefore skip it\n+            continue\n+        root_module_conf = root_module_conf.get(\"module_calls\", {}).get(module_name, {}).get(\"module\", {})\n+\n+    return root_module_conf.get(\"resources\", [])\n+\n+\n def _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:\n     \"\"\"Returns a resource address to resource changes dict\"\"\"\n \n@@ -190,9 +228,14 @@\n         if prepared is True:\n             tf_definition[\"resource\"].append(resource_block)\n     child_modules = template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"child_modules\", [])\n+    root_module_conf = template.get(\"configuration\", {}).get(\"root_module\", {})\n     # Terraform supports modules within modules so we need to search\n     # in nested modules to find all resource blocks\n-    resource_blocks = _find_child_modules(child_modules, resource_changes)\n+    resource_blocks = _find_child_modules(\n+        child_modules=child_modules,\n+        resource_changes=resource_changes,\n+        root_module_conf=root_module_conf,\n+    )\n     for resource in resource_blocks:\n         tf_definition[\"resource\"].append(resource)\n     return tf_definition, template_lines\n", "issue": "Rules CKV_AWS_18 and CKV_AWS_19 fail if s3 resources are defined in a terraform module\n**Describe the issue**\r\n\r\nWhen upgrading the AWS provider in Terraform to a version > 3.75 there has been significant change to the aws_s3_bucket resource. If the S3 resources are referenced in a child module rather than the root level it seems Checkov is still failing CKV_AWS_18 and CKV_AWS_19 based upon our usage. I believe these to be false positives.\r\n\r\nCKV_AWS_18: \"Ensure the S3 bucket has access logging enabled\"\r\n\r\naccess logging is configured by the resource aws_s3_bucket_logging\r\n\r\nCKV_AWS_19: \"Ensure all data stored in the S3 bucket is securely encrypted at rest\"\r\n\r\nencryption at rest is configured by the resource aws_s3_bucket_server_side_encryption_configuration\r\n\r\n**Examples**\r\n\r\n### modules/s3/main.tf\r\n\r\n```\r\n\r\nresource \"aws_kms_key\" \"s3_key\" {\r\n  description             = \"KMS key 1\"\r\n  deletion_window_in_days = 10\r\n}\r\n\r\nresource \"aws_s3_bucket\" \"bucket\" {\r\n  bucket = \"sample-bucket\"\r\n}\r\n\r\nresource \"aws_s3_bucket_server_side_encryption_configuration\" \"bucket\" {\r\n  bucket = aws_s3_bucket.bucket.id\r\n\r\n  rule {\r\n    apply_server_side_encryption_by_default {\r\n      kms_master_key_id = aws_kms_key.s3_key.key_id\r\n      sse_algorithm     = \"aws:kms\"\r\n    }\r\n    bucket_key_enabled = false\r\n  }\r\n}\r\n\r\nresource \"aws_s3_bucket_logging\" \"bucket\" {\r\n  bucket = aws_s3_bucket.bucket.id\r\n\r\n  target_bucket = \"logging-bucket\"\r\n  target_prefix = \"sample-bucket/\"\r\n}\r\n\r\n```\r\n### main.tf\r\n\r\n```\r\n\r\nmodule \"s3\" {\r\n  source = \"./modules/s3\"\r\n}\r\n\r\n```\r\n\r\nCommand: checkov -f plan.json --check CKV_AWS_18,CKV_AWS_19 --repo-root-for-plan-enrichment \"./\"\r\n\r\nExpected both rules pass for resource aws_s3_bucket.bucket\r\n\r\n**Version (please complete the following information):**\r\n - Checkov Version 2.1.81\r\n\r\n**Additional context**\r\nTerraform version: 1.2.6\r\nAWS Provider version: 4.23.0\r\n\r\nIf I move the contents of the module file to the root module both rules pass as expected.\r\n\n", "before_files": [{"content": "from __future__ import annotations\n\nimport itertools\nfrom typing import Optional, Tuple, Dict, List, Any\n\nfrom checkov.common.parsers.node import DictNode, ListNode\nfrom checkov.terraform.context_parsers.tf_plan import parse\n\nSIMPLE_TYPES = (str, int, float, bool)\nTF_PLAN_RESOURCE_ADDRESS = \"__address__\"\nTF_PLAN_RESOURCE_CHANGE_ACTIONS = \"__change_actions__\"\n\n\ndef _is_simple_type(obj: Any) -> bool:\n    if obj is None:\n        return True\n    if isinstance(obj, SIMPLE_TYPES):\n        return True\n    return False\n\n\ndef _is_list_of_simple_types(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if not _is_simple_type(i):\n            return False\n    return True\n\n\ndef _is_list_of_dicts(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if isinstance(i, dict):\n            return True\n    return False\n\n\ndef _hclify(obj: DictNode, conf: Optional[DictNode] = None, parent_key: Optional[str] = None) -> Dict[str, List[Any]]:\n    ret_dict = {}\n    if not isinstance(obj, dict):\n        raise Exception(\"this method receives only dicts\")\n    if hasattr(obj, \"start_mark\") and hasattr(obj, \"end_mark\"):\n        obj[\"start_line\"] = obj.start_mark.line\n        obj[\"end_line\"] = obj.end_mark.line\n    for key, value in obj.items():\n        if _is_simple_type(value) or _is_list_of_simple_types(value):\n            if parent_key == \"tags\":\n                ret_dict[key] = value\n            else:\n                ret_dict[key] = _clean_simple_type_list([value])\n\n        if _is_list_of_dicts(value):\n            child_list = []\n            conf_val = conf.get(key, []) if conf else []\n            for internal_val, internal_conf_val in itertools.zip_longest(value, conf_val):\n                if isinstance(internal_val, dict):\n                    child_list.append(_hclify(internal_val, internal_conf_val, parent_key=key))\n            if key == \"tags\":\n                ret_dict[key] = [child_list]\n            else:\n                ret_dict[key] = child_list\n        if isinstance(value, dict):\n            child_dict = _hclify(value, parent_key=key)\n            if parent_key == \"tags\":\n                ret_dict[key] = child_dict\n            else:\n                ret_dict[key] = [child_dict]\n    if conf and isinstance(conf, dict):\n        found_ref = False\n        for conf_key in conf.keys() - obj.keys():\n            ref = next((x for x in conf[conf_key].get(\"references\", []) if not x.startswith((\"var.\", \"local.\"))), None)\n            if ref:\n                ret_dict[conf_key] = [ref]\n                found_ref = True\n        if not found_ref:\n            for value in conf.values():\n                if isinstance(value, dict) and \"references\" in value.keys():\n                    ret_dict[\"references_\"] = value[\"references\"]\n\n    return ret_dict\n\n\ndef _prepare_resource_block(\n    resource: DictNode, conf: Optional[DictNode], resource_changes: dict[str, dict[str, Any]]\n) -> tuple[dict[str, dict[str, Any]], bool]:\n    \"\"\"hclify resource if pre-conditions met.\n\n    :param resource: tf planned_values resource block\n    :param conf: tf configuration resource block\n    :param resource_changes: tf resource_changes block\n\n    :returns:\n        - resource_block: a list of strings representing the header columns\n        - prepared: whether conditions met to prepare data\n    \"\"\"\n\n    resource_block: Dict[str, Dict[str, Any]] = {}\n    resource_block[resource[\"type\"]] = {}\n    prepared = False\n    mode = \"\"\n    if \"mode\" in resource:\n        mode = resource.get(\"mode\")\n    # Rare cases where data block appears in resources with same name as resource block and only partial values\n    # and where *_module resources don't have values field\n    if mode == \"managed\" and \"values\" in resource:\n        expressions = conf.get(\"expressions\") if conf else None\n\n        resource_conf = _hclify(resource[\"values\"], expressions)\n        resource_address = resource.get(\"address\")\n        resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address\n\n        changes = resource_changes.get(resource_address)\n        if changes:\n            resource_conf[TF_PLAN_RESOURCE_CHANGE_ACTIONS] = changes.get(\"change\", {}).get(\"actions\") or []\n\n        resource_block[resource[\"type\"]][resource.get(\"name\", \"default\")] = resource_conf\n        prepared = True\n    return resource_block, prepared\n\n\ndef _find_child_modules(\n    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]]\n) -> List[Dict[str, Dict[str, Any]]]:\n    \"\"\"\n    Find all child modules if any. Including any amount of nested child modules.\n    :type: child_modules: list of tf child_module objects\n    :rtype: resource_blocks: list of hcl resources\n    \"\"\"\n    resource_blocks = []\n    for child_module in child_modules:\n        if child_module.get(\"child_modules\", []):\n            nested_child_modules = child_module.get(\"child_modules\", [])\n            nested_blocks = _find_child_modules(nested_child_modules, resource_changes)\n            for resource in nested_blocks:\n                resource_blocks.append(resource)\n        for resource in child_module.get(\"resources\", []):\n            resource_block, prepared = _prepare_resource_block(\n                resource=resource,\n                conf=None,\n                resource_changes=resource_changes,\n            )\n            if prepared is True:\n                resource_blocks.append(resource_block)\n    return resource_blocks\n\n\ndef _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:\n    \"\"\"Returns a resource address to resource changes dict\"\"\"\n\n    resource_changes_map = {}\n\n    resource_changes = template.get(\"resource_changes\")\n    if resource_changes and isinstance(resource_changes, list):\n        resource_changes_map = {\n            change.get(\"address\", \"\"): change\n            for change in resource_changes\n        }\n\n    return resource_changes_map\n\n\ndef parse_tf_plan(tf_plan_file: str, out_parsing_errors: Dict[str, str]) -> Tuple[Optional[Dict[str, Any]], Optional[List[Tuple[int, str]]]]:\n    \"\"\"\n    :type tf_plan_file: str - path to plan file\n    :rtype: tf_definition dictionary and template_lines of the plan file\n    \"\"\"\n    tf_definition: Dict[str, Any] = {\"resource\": []}\n    template, template_lines = parse(tf_plan_file, out_parsing_errors)\n    if not template:\n        return None, None\n\n    resource_changes = _get_resource_changes(template=template)\n\n    for resource in template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"resources\", []):\n        conf = next(\n            (\n                x\n                for x in template.get(\"configuration\", {}).get(\"root_module\", {}).get(\"resources\", [])\n                if x[\"type\"] == resource[\"type\"] and x[\"name\"] == resource[\"name\"]\n            ),\n            None,\n        )\n        resource_block, prepared = _prepare_resource_block(\n            resource=resource,\n            conf=conf,\n            resource_changes=resource_changes,\n        )\n        if prepared is True:\n            tf_definition[\"resource\"].append(resource_block)\n    child_modules = template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"child_modules\", [])\n    # Terraform supports modules within modules so we need to search\n    # in nested modules to find all resource blocks\n    resource_blocks = _find_child_modules(child_modules, resource_changes)\n    for resource in resource_blocks:\n        tf_definition[\"resource\"].append(resource)\n    return tf_definition, template_lines\n\n\ndef _clean_simple_type_list(value_list: List[Any]) -> List[Any]:\n    \"\"\"\n    Given a list of simple types return a cleaned list of simple types.\n    Converts booleans that are input as strings back to booleans to maintain consistent expectations for later evaluation.\n    Sometimes Terraform Plan will output Map values as strings regardless of boolean input.\n    \"\"\"\n    for i in range(len(value_list)):\n        if isinstance(value_list[i], str):\n            lower_case_value = value_list[i].lower()\n            if lower_case_value == \"true\":\n                value_list[i] = True\n            if lower_case_value == \"false\":\n                value_list[i] = False         \n    return value_list\n", "path": "checkov/terraform/plan_parser.py"}], "after_files": [{"content": "from __future__ import annotations\n\nimport itertools\nfrom typing import Optional, Tuple, Dict, List, Any\n\nfrom checkov.common.parsers.node import DictNode, ListNode\nfrom checkov.terraform.context_parsers.tf_plan import parse\n\nSIMPLE_TYPES = (str, int, float, bool)\nTF_PLAN_RESOURCE_ADDRESS = \"__address__\"\nTF_PLAN_RESOURCE_CHANGE_ACTIONS = \"__change_actions__\"\n\n\ndef _is_simple_type(obj: Any) -> bool:\n    if obj is None:\n        return True\n    if isinstance(obj, SIMPLE_TYPES):\n        return True\n    return False\n\n\ndef _is_list_of_simple_types(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if not _is_simple_type(i):\n            return False\n    return True\n\n\ndef _is_list_of_dicts(obj: Any) -> bool:\n    if not isinstance(obj, list):\n        return False\n    for i in obj:\n        if isinstance(i, dict):\n            return True\n    return False\n\n\ndef _hclify(obj: DictNode, conf: Optional[DictNode] = None, parent_key: Optional[str] = None) -> Dict[str, List[Any]]:\n    ret_dict = {}\n    if not isinstance(obj, dict):\n        raise Exception(\"this method receives only dicts\")\n    if hasattr(obj, \"start_mark\") and hasattr(obj, \"end_mark\"):\n        obj[\"start_line\"] = obj.start_mark.line\n        obj[\"end_line\"] = obj.end_mark.line\n    for key, value in obj.items():\n        if _is_simple_type(value) or _is_list_of_simple_types(value):\n            if parent_key == \"tags\":\n                ret_dict[key] = value\n            else:\n                ret_dict[key] = _clean_simple_type_list([value])\n\n        if _is_list_of_dicts(value):\n            child_list = []\n            conf_val = conf.get(key, []) if conf else []\n            for internal_val, internal_conf_val in itertools.zip_longest(value, conf_val):\n                if isinstance(internal_val, dict):\n                    child_list.append(_hclify(internal_val, internal_conf_val, parent_key=key))\n            if key == \"tags\":\n                ret_dict[key] = [child_list]\n            else:\n                ret_dict[key] = child_list\n        if isinstance(value, dict):\n            child_dict = _hclify(value, parent_key=key)\n            if parent_key == \"tags\":\n                ret_dict[key] = child_dict\n            else:\n                ret_dict[key] = [child_dict]\n    if conf and isinstance(conf, dict):\n        found_ref = False\n        for conf_key in conf.keys() - obj.keys():\n            ref = next((x for x in conf[conf_key].get(\"references\", []) if not x.startswith((\"var.\", \"local.\"))), None)\n            if ref:\n                ret_dict[conf_key] = [ref]\n                found_ref = True\n        if not found_ref:\n            for value in conf.values():\n                if isinstance(value, dict) and \"references\" in value.keys():\n                    ret_dict[\"references_\"] = value[\"references\"]\n\n    return ret_dict\n\n\ndef _prepare_resource_block(\n    resource: DictNode, conf: Optional[DictNode], resource_changes: dict[str, dict[str, Any]]\n) -> tuple[dict[str, dict[str, Any]], bool]:\n    \"\"\"hclify resource if pre-conditions met.\n\n    :param resource: tf planned_values resource block\n    :param conf: tf configuration resource block\n    :param resource_changes: tf resource_changes block\n\n    :returns:\n        - resource_block: a list of strings representing the header columns\n        - prepared: whether conditions met to prepare data\n    \"\"\"\n\n    resource_block: Dict[str, Dict[str, Any]] = {}\n    resource_block[resource[\"type\"]] = {}\n    prepared = False\n    mode = \"\"\n    if \"mode\" in resource:\n        mode = resource.get(\"mode\")\n    # Rare cases where data block appears in resources with same name as resource block and only partial values\n    # and where *_module resources don't have values field\n    if mode == \"managed\" and \"values\" in resource:\n        expressions = conf.get(\"expressions\") if conf else None\n\n        resource_conf = _hclify(resource[\"values\"], expressions)\n        resource_address = resource.get(\"address\")\n        resource_conf[TF_PLAN_RESOURCE_ADDRESS] = resource_address\n\n        changes = resource_changes.get(resource_address)\n        if changes:\n            resource_conf[TF_PLAN_RESOURCE_CHANGE_ACTIONS] = changes.get(\"change\", {}).get(\"actions\") or []\n\n        resource_block[resource[\"type\"]][resource.get(\"name\", \"default\")] = resource_conf\n        prepared = True\n    return resource_block, prepared\n\n\ndef _find_child_modules(\n    child_modules: ListNode, resource_changes: dict[str, dict[str, Any]], root_module_conf: dict[str, Any]\n) -> List[Dict[str, Dict[str, Any]]]:\n    \"\"\" Find all child modules if any. Including any amount of nested child modules.\n\n    :param child_modules: list of terraform child_module objects\n    :param resource_changes: a resource address to resource changes dict\n    :param root_module_conf: configuration block of the root module\n    :returns:\n        list of terraform resource blocks\n    \"\"\"\n\n    resource_blocks = []\n    for child_module in child_modules:\n        nested_child_modules = child_module.get(\"child_modules\", [])\n        if nested_child_modules:\n            nested_blocks = _find_child_modules(\n                child_modules=nested_child_modules,\n                resource_changes=resource_changes,\n                root_module_conf=root_module_conf\n            )\n            for resource in nested_blocks:\n                resource_blocks.append(resource)\n\n        module_address = child_module.get(\"address\", \"\")\n        module_call_resources = _get_module_call_resources(\n            module_address=module_address,\n            root_module_conf=root_module_conf,\n        )\n\n        for resource in child_module.get(\"resources\", []):\n            module_call_conf = None\n            if module_address and module_call_resources:\n                module_call_conf = next(\n                    (\n                        module_call_resource\n                        for module_call_resource in module_call_resources\n                        if f\"{module_address}.{module_call_resource['address']}\" == resource[\"address\"]\n                    ),\n                    None\n                )\n\n            resource_block, prepared = _prepare_resource_block(\n                resource=resource,\n                conf=module_call_conf,\n                resource_changes=resource_changes,\n            )\n            if prepared is True:\n                resource_blocks.append(resource_block)\n    return resource_blocks\n\n\ndef _get_module_call_resources(module_address: str, root_module_conf: dict[str, Any]) -> list[dict[str, Any]]:\n    \"\"\"Extracts the resources from the 'module_calls' block under 'configuration'\"\"\"\n\n    for module_name in module_address.split(\".\"):\n        if module_name == \"module\":\n            # module names are always prefixed with 'module.', therefore skip it\n            continue\n        root_module_conf = root_module_conf.get(\"module_calls\", {}).get(module_name, {}).get(\"module\", {})\n\n    return root_module_conf.get(\"resources\", [])\n\n\ndef _get_resource_changes(template: dict[str, Any]) -> dict[str, dict[str, Any]]:\n    \"\"\"Returns a resource address to resource changes dict\"\"\"\n\n    resource_changes_map = {}\n\n    resource_changes = template.get(\"resource_changes\")\n    if resource_changes and isinstance(resource_changes, list):\n        resource_changes_map = {\n            change.get(\"address\", \"\"): change\n            for change in resource_changes\n        }\n\n    return resource_changes_map\n\n\ndef parse_tf_plan(tf_plan_file: str, out_parsing_errors: Dict[str, str]) -> Tuple[Optional[Dict[str, Any]], Optional[List[Tuple[int, str]]]]:\n    \"\"\"\n    :type tf_plan_file: str - path to plan file\n    :rtype: tf_definition dictionary and template_lines of the plan file\n    \"\"\"\n    tf_definition: Dict[str, Any] = {\"resource\": []}\n    template, template_lines = parse(tf_plan_file, out_parsing_errors)\n    if not template:\n        return None, None\n\n    resource_changes = _get_resource_changes(template=template)\n\n    for resource in template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"resources\", []):\n        conf = next(\n            (\n                x\n                for x in template.get(\"configuration\", {}).get(\"root_module\", {}).get(\"resources\", [])\n                if x[\"type\"] == resource[\"type\"] and x[\"name\"] == resource[\"name\"]\n            ),\n            None,\n        )\n        resource_block, prepared = _prepare_resource_block(\n            resource=resource,\n            conf=conf,\n            resource_changes=resource_changes,\n        )\n        if prepared is True:\n            tf_definition[\"resource\"].append(resource_block)\n    child_modules = template.get(\"planned_values\", {}).get(\"root_module\", {}).get(\"child_modules\", [])\n    root_module_conf = template.get(\"configuration\", {}).get(\"root_module\", {})\n    # Terraform supports modules within modules so we need to search\n    # in nested modules to find all resource blocks\n    resource_blocks = _find_child_modules(\n        child_modules=child_modules,\n        resource_changes=resource_changes,\n        root_module_conf=root_module_conf,\n    )\n    for resource in resource_blocks:\n        tf_definition[\"resource\"].append(resource)\n    return tf_definition, template_lines\n\n\ndef _clean_simple_type_list(value_list: List[Any]) -> List[Any]:\n    \"\"\"\n    Given a list of simple types return a cleaned list of simple types.\n    Converts booleans that are input as strings back to booleans to maintain consistent expectations for later evaluation.\n    Sometimes Terraform Plan will output Map values as strings regardless of boolean input.\n    \"\"\"\n    for i in range(len(value_list)):\n        if isinstance(value_list[i], str):\n            lower_case_value = value_list[i].lower()\n            if lower_case_value == \"true\":\n                value_list[i] = True\n            if lower_case_value == \"false\":\n                value_list[i] = False         \n    return value_list\n", "path": "checkov/terraform/plan_parser.py"}]}