Repository: bridgecrewio/checkov

Issue:
KeyError: 'aws_api_gateway_stage.stage'
Checkov crashing with KeyError when using `--repo-root-for-plan-enrichment`. 
Succeeds when used without that.

Possibly module related???

**To Reproduce**
Steps to reproduce the behavior:
1. Have TF using a module on private github, eg: main.tf
```terraform
module "api" {
  source       = "git@github.com:xxxxxxxx/terraform-common-modules.git//api-gateway?ref=main"
  <snip>
}
```
2.`terraform plan -var-file=dev.tfvars -var EXTERNAL_ID="" -var ROLE_ARN=""  -out tf.plan`
3.`terraform show -json tf.plan | jq '.' > tf.json`
4. `checkov -f tf.json --quiet  --repo-root-for-plan-enrichment $(pwd)`
5. Error:
```console
Traceback (most recent call last):
  File "/opt/bitnami/python/bin/checkov", line 8, in <module>
    exit(run())
  File "/opt/bitnami/python/lib/python3.9/site-packages/checkov/main.py", line 148, in run
    scan_reports = runner_registry.run(external_checks_dir=external_checks_dir, files=config.file,
  File "/opt/bitnami/python/lib/python3.9/site-packages/checkov/common/runners/runner_registry.py", line 47, in run
    enriched_report_with_skipped = Report("terraform_plan").handle_skipped_checks(enriched_report, enriched_resources)
  File "/opt/bitnami/python/lib/python3.9/site-packages/checkov/common/output/report.py", line 176, in handle_skipped_checks
    resource_skips = enriched_resources[record.resource]["skipped_checks"]
KeyError: 'aws_api_gateway_stage.stage'
```

**Expected behavior**
no crash

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Desktop (please complete the following information):**
 - OS: docker container `public.ecr.aws/bitnami/python:3.9-debian-10` running on iOS 10.13.6
 - Checkov Version:2.0.201

**Additional context**
 - Python version: 
 ```console
/opt/bitnami/python/bin/python
Python 3.9.5 (default, May  7 2021, 17:26:37)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.version
'3.9.5 (default, May  7 2021, 17:26:37) \n[GCC 8.3.0]'
```
 - Terraform version:
```console
terraform --version
Terraform v0.15.5
on linux_amd64
+ provider registry.terraform.io/hashicorp/archive v2.2.0
+ provider registry.terraform.io/hashicorp/aws v3.45.0
+ provider registry.terraform.io/hashicorp/random v3.1.0
+ provider registry.terraform.io/hashicorp/template v2.2.0

Your version of Terraform is out of date! The latest version
is 1.0.0. You can update by downloading from https://www.terraform.io/downloads.html
```
 - [LOG_LEVEL=DEBUG](https://pastebin.com/tPq9qtuD)



Discussion:

Resolving patch:
diff --git a/checkov/common/output/report.py b/checkov/common/output/report.py
--- a/checkov/common/output/report.py
+++ b/checkov/common/output/report.py
@@ -1,5 +1,6 @@
 import json
 from collections import defaultdict
+from typing import Dict, Any
 
 from colorama import init
 from junit_xml import TestCase, TestSuite, to_xml_report_string
@@ -159,7 +160,7 @@ def _count_resources(self):
         return len(unique_resources)
 
     @staticmethod
-    def enrich_plan_report(report, enriched_resources):
+    def enrich_plan_report(report: "Report", enriched_resources: Dict[str, Dict[str, Any]]) -> "Report":
         # This enriches reports with the appropriate filepath, line numbers, and codeblock
         for record in report.failed_checks:
             enriched_resource = enriched_resources.get(record.resource)
@@ -170,10 +171,9 @@ def enrich_plan_report(report, enriched_resources):
         return report
 
     @staticmethod
-    def handle_skipped_checks(report, enriched_resources):
-        records_to_skip = []
+    def handle_skipped_checks(report: "Report", enriched_resources: Dict[str, Dict[str, Any]]) -> "Report":
         for record in report.failed_checks:
-            resource_skips = enriched_resources[record.resource]["skipped_checks"]
+            resource_skips = enriched_resources.get(record.resource, {}).get("skipped_checks", [])
             for skip in resource_skips:
                 if record.check_id in skip["id"]: 
                     # Remove and re-add the record to make Checkov mark it as skipped
diff --git a/checkov/terraform/checks/resource/base_resource_value_check.py b/checkov/terraform/checks/resource/base_resource_value_check.py
--- a/checkov/terraform/checks/resource/base_resource_value_check.py
+++ b/checkov/terraform/checks/resource/base_resource_value_check.py
@@ -60,12 +60,12 @@ def scan_resource_conf(self, conf: Dict[str, List[Any]]) -> CheckResult:
         expected_values = self.get_expected_values()
         if dpath.search(conf, inspected_key) != {}:
             # Inspected key exists
-            if ANY_VALUE in expected_values:
-                # Key is found on the configuration - if it accepts any value, the check is PASSED
-                return CheckResult.PASSED
             value = dpath.get(conf, inspected_key)
             if isinstance(value, list) and len(value) == 1:
                 value = value[0]
+            if ANY_VALUE in expected_values and value is not None:
+                # Key is found on the configuration - if it accepts any value, the check is PASSED
+                return CheckResult.PASSED
             if self._is_variable_dependant(value):
                 # If the tested attribute is variable-dependant, then result is PASSED
                 return CheckResult.PASSED


Test patch:
diff --git a/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/log_group/main.tf b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/log_group/main.tf
new file mode 100644
--- /dev/null
+++ b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/log_group/main.tf
@@ -0,0 +1,2 @@
+resource "aws_cloudwatch_log_group" "not_encrypted" {
+}
diff --git a/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/main.tf b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/main.tf
new file mode 100644
--- /dev/null
+++ b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/main.tf
@@ -0,0 +1,8 @@
+module "log_group_local" {
+  source = "./log_group"
+}
+
+module "log_group_external" {
+  source  = "terraform-aws-modules/cloudwatch/aws//modules/log-group"
+  version = "2.1.0"
+}
diff --git a/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/providers.tf b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/providers.tf
new file mode 100644
--- /dev/null
+++ b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/providers.tf
@@ -0,0 +1,3 @@
+provider "aws" {
+  region  = "eu-central-1"
+}
diff --git a/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/tfplan.json b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/tfplan.json
new file mode 100644
--- /dev/null
+++ b/tests/common/runner_registry/plan_with_tf_modules_for_enrichment/tfplan.json
@@ -0,0 +1,152 @@
+{
+    "format_version": "0.1",
+    "terraform_version": "0.15.1",
+    "planned_values": {
+        "root_module": {
+            "child_modules": [
+                {
+                    "resources": [
+                        {
+                            "address": "module.log_group_external.aws_cloudwatch_log_group.this[0]",
+                            "mode": "managed",
+                            "type": "aws_cloudwatch_log_group",
+                            "name": "this",
+                            "index": 0,
+                            "provider_name": "registry.terraform.io/hashicorp/aws",
+                            "schema_version": 0,
+                            "values": {"kms_key_id": null, "name_prefix": null, "retention_in_days": 0, "tags": null}
+                        }
+                    ],
+                    "address": "module.log_group_external"
+                },
+                {
+                    "resources": [
+                        {
+                            "address": "module.log_group_local.aws_cloudwatch_log_group.not_encrypted",
+                            "mode": "managed",
+                            "type": "aws_cloudwatch_log_group",
+                            "name": "not_encrypted",
+                            "provider_name": "registry.terraform.io/hashicorp/aws",
+                            "schema_version": 0,
+                            "values": {"kms_key_id": null, "name_prefix": null, "retention_in_days": 30, "tags": null}
+                        }
+                    ],
+                    "address": "module.log_group_local"
+                }
+            ]
+        }
+    },
+    "resource_changes": [
+        {
+            "address": "module.log_group_external.aws_cloudwatch_log_group.this[0]",
+            "module_address": "module.log_group_external",
+            "mode": "managed",
+            "type": "aws_cloudwatch_log_group",
+            "name": "this",
+            "index": 0,
+            "provider_name": "registry.terraform.io/hashicorp/aws",
+            "change": {
+                "actions": ["create"],
+                "before": null,
+                "after": {"kms_key_id": null, "name_prefix": null, "retention_in_days": 0, "tags": null},
+                "after_unknown": {"arn": true, "id": true, "name": true, "tags_all": true},
+                "before_sensitive": false,
+                "after_sensitive": {"tags_all": {}}
+            }
+        },
+        {
+            "address": "module.log_group_local.aws_cloudwatch_log_group.not_encrypted",
+            "module_address": "module.log_group_local",
+            "mode": "managed",
+            "type": "aws_cloudwatch_log_group",
+            "name": "not_encrypted",
+            "provider_name": "registry.terraform.io/hashicorp/aws",
+            "change": {
+                "actions": ["create"],
+                "before": null,
+                "after": {"kms_key_id": null, "name_prefix": null, "retention_in_days": 30, "tags": null},
+                "after_unknown": {"arn": true, "id": true, "name": true, "tags_all": true},
+                "before_sensitive": false,
+                "after_sensitive": {"tags_all": {}}
+            }
+        }
+    ],
+    "configuration": {
+        "provider_config": {
+            "aws": {"name": "aws", "expressions": {"region": {"constant_value": "eu-central-1"}}},
+            "module.log_group_external:aws": {
+                "name": "aws",
+                "version_constraint": "\u003e= 2.55.0",
+                "module_address": "module.log_group_external"
+            }
+        },
+        "root_module": {
+            "module_calls": {
+                "log_group_external": {
+                    "source": "terraform-aws-modules/cloudwatch/aws//modules/log-group",
+                    "module": {
+                        "outputs": {
+                            "cloudwatch_log_group_arn": {
+                                "expression": {"references": ["aws_cloudwatch_log_group.this"]},
+                                "description": "ARN of Cloudwatch log group"
+                            },
+                            "cloudwatch_log_group_name": {
+                                "expression": {"references": ["aws_cloudwatch_log_group.this"]},
+                                "description": "Name of Cloudwatch log group"
+                            }
+                        },
+                        "resources": [
+                            {
+                                "address": "aws_cloudwatch_log_group.this",
+                                "mode": "managed",
+                                "type": "aws_cloudwatch_log_group",
+                                "name": "this",
+                                "provider_config_key": "log_group_external:aws",
+                                "expressions": {
+                                    "kms_key_id": {"references": ["var.kms_key_id"]},
+                                    "name": {"references": ["var.name"]},
+                                    "name_prefix": {"references": ["var.name_prefix"]},
+                                    "retention_in_days": {"references": ["var.retention_in_days"]},
+                                    "tags": {"references": ["var.tags"]}
+                                },
+                                "schema_version": 0,
+                                "count_expression": {"references": ["var.create"]}
+                            }
+                        ],
+                        "variables": {
+                            "create": {"default": true, "description": "Whether to create the Cloudwatch log group"},
+                            "kms_key_id": {
+                                "default": null,
+                                "description": "The ARN of the KMS Key to use when encrypting logs"
+                            },
+                            "name": {"default": null, "description": "A name for the log group"},
+                            "name_prefix": {"default": null, "description": "A name prefix for the log group"},
+                            "retention_in_days": {
+                                "default": null,
+                                "description": "Specifies the number of days you want to retain log events in the specified log group"
+                            },
+                            "tags": {"default": {}, "description": "A map of tags to add to Cloudwatch log group"}
+                        }
+                    },
+                    "version_constraint": "~\u003e 2.0"
+                },
+                "log_group_local": {
+                    "source": "./log_group",
+                    "module": {
+                        "resources": [
+                            {
+                                "address": "aws_cloudwatch_log_group.not_encrypted",
+                                "mode": "managed",
+                                "type": "aws_cloudwatch_log_group",
+                                "name": "not_encrypted",
+                                "provider_config_key": "log_group_local:aws",
+                                "expressions": {"retention_in_days": {"constant_value": 30}},
+                                "schema_version": 0
+                            }
+                        ]
+                    }
+                }
+            }
+        }
+    }
+}
diff --git a/tests/common/runner_registry/test_runner_registry_plan_enrichment.py b/tests/common/runner_registry/test_runner_registry_plan_enrichment.py
--- a/tests/common/runner_registry/test_runner_registry_plan_enrichment.py
+++ b/tests/common/runner_registry/test_runner_registry_plan_enrichment.py
@@ -1,6 +1,6 @@
 import unittest
 
-import os
+from pathlib import Path
 
 from checkov.common.runners.runner_registry import RunnerRegistry
 from checkov.common.util.banner import banner
@@ -9,38 +9,23 @@
 
 
 class TestRunnerRegistryEnrichment(unittest.TestCase):
-
     def test_enrichment_of_plan_report(self):
         allowed_checks = ["CKV_AWS_19", "CKV_AWS_20", "CKV_AWS_28", "CKV_AWS_63", "CKV_AWS_119"]
-        runner_registry = RunnerRegistry(banner, RunnerFilter(checks=allowed_checks, framework="terraform_plan"),
-                                         tf_plan_runner())
+        runner_registry = RunnerRegistry(
+            banner, RunnerFilter(checks=allowed_checks, framework="terraform_plan"), tf_plan_runner()
+        )
 
-        current_dir = os.path.dirname(os.path.realpath(__file__))
-        repo_root = current_dir + "/plan_with_hcl_for_enrichment/"
-        valid_plan_path = repo_root + "tfplan.json"
-        print("scanning {} repo_root {}".format(valid_plan_path,repo_root))
+        repo_root = Path(__file__).parent / "plan_with_hcl_for_enrichment"
+        valid_plan_path = repo_root / "tfplan.json"
 
-        reports = runner_registry.run(
-            repo_root_for_plan_enrichment=repo_root,
-            files=[valid_plan_path]
-        )
+        report = runner_registry.run(repo_root_for_plan_enrichment=repo_root, files=[valid_plan_path])[0]
 
-        report = reports[0]
-        report.print_console()
+        failed_check_ids = {c.check_id for c in report.failed_checks}
+        skipped_check_ids = {c.check_id for c in report.skipped_checks}
+        expected_failed_check_ids = {"CKV_AWS_19", "CKV_AWS_63", "CKV_AWS_119"}
+        expected_skipped_check_ids = {"CKV_AWS_20", "CKV_AWS_28"}
 
-        failed_check_ids = set([c.check_id for c in report.failed_checks])
-        skipped_check_ids = set([c.check_id for c in report.skipped_checks])
-        expected_failed_check_ids = {
-            "CKV_AWS_19",
-            "CKV_AWS_63",
-            "CKV_AWS_119"
-        }
-        expected_skipped_check_ids = {
-            "CKV_AWS_20",
-            "CKV_AWS_28"
-        }
-        enriched_data = set(
-            [(c.file_path, tuple(c.file_line_range), tuple(c.code_block)) for c in report.failed_checks])
+        enriched_data = {(c.file_path, tuple(c.file_line_range), tuple(c.code_block)) for c in report.failed_checks}
         expected_enriched_data = {
             (
                 "iam.tf",
@@ -50,22 +35,22 @@ def test_enrichment_of_plan_report(self):
                     (2, '  name        = "my_policy-123456789101"\n'),
                     (3, '  path        = "/"\n'),
                     (4, '  description = "My test policy"\n'),
-                    (5, '  policy = <<EOF\n'),
-                    (6, '{\n'),
+                    (5, "  policy = <<EOF\n"),
+                    (6, "{\n"),
                     (7, '  "Version": "2012-10-17",\n'),
                     (8, '  "Statement": [\n'),
-                    (9, '    {\n'),
+                    (9, "    {\n"),
                     (10, '      "Action": [\n'),
                     (11, '        "*"\n'),
-                    (12, '      ],\n'),
+                    (12, "      ],\n"),
                     (13, '      "Effect": "Allow",\n'),
                     (14, '      "Resource": "arn:aws:iam::${var.aws_account_id}:role/admin"\n'),
-                    (15, '    }\n'),
-                    (16, '  ]\n'),
-                    (17, '}\n'),
-                    (18, 'EOF\n'),
-                    (19, '}')
-                )
+                    (15, "    }\n"),
+                    (16, "  ]\n"),
+                    (17, "}\n"),
+                    (18, "EOF\n"),
+                    (19, "}"),
+                ),
             ),
             (
                 "s3.tf",
@@ -73,51 +58,87 @@ def test_enrichment_of_plan_report(self):
                 (
                     (1, 'resource "aws_s3_bucket" "test-bucket1" {\n'),
                     (2, '  bucket = "test-bucket1"\n'),
-                    (3,
-                     '  # checkov:skip=CKV_AWS_20: The bucket is a public static content '
-                     'host\n'),
+                    (3, "  # checkov:skip=CKV_AWS_20: The bucket is a public static content " "host\n"),
                     (4, '  acl    = "public-read"\n'),
-                    (5, '  lifecycle_rule {\n'),
+                    (5, "  lifecycle_rule {\n"),
                     (6, '    id      = "90 Day Lifecycle"\n'),
-                    (7, '    enabled = true\n'),
-                    (8, '    expiration {\n'),
-                    (9, '      days = 90\n'),
-                    (10, '    }\n'),
-                    (11, '    noncurrent_version_expiration {\n'),
-                    (12, '      days = 90\n'),
-                    (13, '    }\n'),
-                    (14, '    abort_incomplete_multipart_upload_days = 90\n'),
-                    (15, '  }\n'),
-                    (16, '  provider = aws.current_region\n'),
-                    (17, '}')
-                )
+                    (7, "    enabled = true\n"),
+                    (8, "    expiration {\n"),
+                    (9, "      days = 90\n"),
+                    (10, "    }\n"),
+                    (11, "    noncurrent_version_expiration {\n"),
+                    (12, "      days = 90\n"),
+                    (13, "    }\n"),
+                    (14, "    abort_incomplete_multipart_upload_days = 90\n"),
+                    (15, "  }\n"),
+                    (16, "  provider = aws.current_region\n"),
+                    (17, "}"),
+                ),
             ),
             (
                 "dynamodb.tf",
                 (1, 12),
                 (
                     (1, 'resource "aws_dynamodb_table" "cross-environment-violations" {\n'),
-                    (2, '  # checkov:skip=CKV_AWS_28: ignoring backups for now\n'),
+                    (2, "  # checkov:skip=CKV_AWS_28: ignoring backups for now\n"),
                     (3, '  name           = "CrossEnvironmentViolations"\n'),
-                    (4, '  read_capacity  = 20\n'),
-                    (5, '  write_capacity = 20\n'),
+                    (4, "  read_capacity  = 20\n"),
+                    (5, "  write_capacity = 20\n"),
                     (6, '  hash_key       = "id"\n'),
-                    (7, '  attribute {\n'),
+                    (7, "  attribute {\n"),
                     (8, '    name = "id"\n'),
                     (9, '    type = "S"\n'),
-                    (10, '  }\n'),
-                    (11, '  provider = aws.current_region\n'),
-                    (12, '}')
-                )
-            )
+                    (10, "  }\n"),
+                    (11, "  provider = aws.current_region\n"),
+                    (12, "}"),
+                ),
+            ),
         }
 
-        # self.assertEqual(len(failed_check_ids), 3)
+        self.assertEqual(len(failed_check_ids), 3)
         self.assertEqual(failed_check_ids, expected_failed_check_ids)
         self.assertEqual(len(skipped_check_ids), 2)
         self.assertEqual(skipped_check_ids, expected_skipped_check_ids)
         self.assertEqual(enriched_data, expected_enriched_data)
 
+    def test_enrichment_of_plan_report_with_modules(self):
+        allowed_checks = ["CKV_AWS_66", "CKV_AWS_158"]
+        runner_registry = RunnerRegistry(
+            banner, RunnerFilter(checks=allowed_checks, framework="terraform_plan"), tf_plan_runner()
+        )
+
+        repo_root = Path(__file__).parent / "plan_with_tf_modules_for_enrichment"
+        valid_plan_path = repo_root / "tfplan.json"
+
+        report = runner_registry.run(repo_root_for_plan_enrichment=repo_root, files=[valid_plan_path])[0]
+
+        failed_check_ids = [c.check_id for c in report.failed_checks]
+        passed_check_ids = [c.check_id for c in report.passed_checks]
+        skipped_check_ids = [c.check_id for c in report.skipped_checks]
+        expected_failed_check_ids = ["CKV_AWS_158", "CKV_AWS_158"]
+        expected_passed_check_ids = ["CKV_AWS_66", "CKV_AWS_66"]
+        expected_skipped_check_ids = []
+
+        enriched_data = set(
+            [(c.file_path, tuple(c.file_line_range), tuple(c.code_block)) for c in report.failed_checks]
+        )
+        expected_enriched_data = {
+            (f"/{Path.relative_to(valid_plan_path, Path.cwd())}", (16, 16), (),),
+            (
+                "log_group/main.tf",
+                (1, 2),
+                ((1, 'resource "aws_cloudwatch_log_group" "not_encrypted" {\n'), (2, "}\n"),),
+            ),
+        }
+
+        self.assertEqual(len(failed_check_ids), 2)
+        self.assertEqual(failed_check_ids, expected_failed_check_ids)
+        self.assertEqual(len(passed_check_ids), 2)
+        self.assertEqual(passed_check_ids, expected_passed_check_ids)
+        self.assertEqual(len(skipped_check_ids), 0)
+        self.assertEqual(skipped_check_ids, expected_skipped_check_ids)
+        self.assertEqual(enriched_data, expected_enriched_data)
+
 
-if __name__ == '__main__':
+if __name__ == "__main__":
     unittest.main()
diff --git a/tests/terraform/runner/test_plan_runner.py b/tests/terraform/runner/test_plan_runner.py
--- a/tests/terraform/runner/test_plan_runner.py
+++ b/tests/terraform/runner/test_plan_runner.py
@@ -170,11 +170,11 @@ def test_runner_root_dir(self):
         self.assertEqual(report.get_exit_code(soft_fail=False), 1)
         self.assertEqual(report.get_exit_code(soft_fail=True), 0)
 
-        self.assertGreater(report.get_summary()["failed"], 62)
-        self.assertGreater(report.get_summary()["passed"], 65)
+        self.assertGreaterEqual(report.get_summary()["failed"], 94)
+        self.assertGreaterEqual(report.get_summary()["passed"], 76)
 
         files_scanned = list(set(map(lambda rec: rec.file_path, report.failed_checks)))
-        self.assertGreaterEqual(5, len(files_scanned))
+        self.assertGreaterEqual(len(files_scanned), 6)
 
     def test_record_relative_path_with_relative_dir(self):