Repository: bridgecrewio/checkov

Issue:
CKV_AWS_33 false positive for deny statements
**Describe the issue**
CKV_AWS_33 provides false-positives for KMS KeyPolicy statements that have a wildcard principle with `Effect: "Deny"`

**Examples**
```yaml
TheKey:
  Type: AWS::KMS::Key
  Properties:
    KeyPolicy:
      Version: "2012-10-17"
      Statement:
        - Effect: "Deny"
          Principal: "*"
          ...
```


**Version (please complete the following information):**
 - Checkov Version [2.2.327]




Discussion:

Resolving patch:
diff --git a/checkov/cloudformation/checks/resource/aws/KMSKeyWildCardPrincipal.py b/checkov/cloudformation/checks/resource/aws/KMSKeyWildCardPrincipal.py
--- a/checkov/cloudformation/checks/resource/aws/KMSKeyWildCardPrincipal.py
+++ b/checkov/cloudformation/checks/resource/aws/KMSKeyWildCardPrincipal.py
@@ -1,58 +1,43 @@
-from checkov.common.models.enums import CheckResult, CheckCategories
-from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
-
-
-def get_recursively(search_dict, field):
-    """
-    Takes a dict with nested lists and dicts,
-    and searches all dicts for a key of the field
-    provided.
-    """
-    fields_found = []
-
-    for key, value in search_dict.items():
+from __future__ import annotations
 
-        if key == field:
-            fields_found.append(value)
+from typing import Any
 
-        elif isinstance(value, dict):
-            results = get_recursively(value, field)
-            for result in results:
-                fields_found.append(result)
-
-        elif isinstance(value, list):
-            for item in value:
-                if isinstance(item, dict):
-                    more_results = get_recursively(item, field)
-                    for another_result in more_results:
-                        fields_found.append(another_result)
-
-    return fields_found
+from checkov.common.models.enums import CheckResult, CheckCategories
+from checkov.cloudformation.checks.resource.base_resource_value_check import BaseResourceValueCheck
 
 
 class KMSKeyWildCardPrincipal(BaseResourceValueCheck):
-    def __init__(self):
+    def __init__(self) -> None:
         name = "Ensure KMS key policy does not contain wildcard (*) principal"
         id = "CKV_AWS_33"
-        supported_resources = ['AWS::KMS::Key']
-        categories = [CheckCategories.ENCRYPTION]
+        supported_resources = ("AWS::KMS::Key",)
+        categories = (CheckCategories.ENCRYPTION,)
         super().__init__(name=name, id=id, categories=categories, supported_resources=supported_resources)
 
-    def get_inspected_key(self):
-        return 'Properties/KeyPolicy/Statement/Principal'
-
-    def scan_resource_conf(self, conf):
-        if conf.get('Properties'):
-            if conf['Properties'].get('KeyPolicy'):
-                policy_block = conf['Properties']['KeyPolicy']
-                principals_list = get_recursively(policy_block, 'Principal')
-                for principal in principals_list:
-                    if isinstance(principal, dict):
-                        for principal_value in principal.values():
-                            if principal_value == '*' or (isinstance(principal_value, list) and '*' in principal_value):
-                                return CheckResult.FAILED
-                    else:
-                        if principal == '*':
+    def get_inspected_key(self) -> str:
+        return "Properties/KeyPolicy/Statement/Principal"
+
+    def scan_resource_conf(self, conf: dict[str, Any]) -> CheckResult:
+        properties = conf.get("Properties")
+        if properties and isinstance(properties, dict):
+            policy_block = properties.get("KeyPolicy")
+            if policy_block and isinstance(policy_block, dict):
+                statements = policy_block.get("Statement")
+                if statements and isinstance(statements, list):
+                    for statement in statements:
+                        principal = statement.get("Principal")
+                        if not principal:
+                            continue
+                        if statement.get("Effect") == "Deny":
+                            continue
+
+                        if isinstance(principal, dict) and "AWS" in principal:
+                            # the actual principals can be under the `AWS`
+                            principal = principal["AWS"]
+
+                        if isinstance(principal, str) and principal == "*":
+                            return CheckResult.FAILED
+                        if isinstance(principal, list) and "*" in principal:
                             return CheckResult.FAILED
 
         return CheckResult.PASSED
diff --git a/checkov/cloudformation/checks/resource/base_resource_value_check.py b/checkov/cloudformation/checks/resource/base_resource_value_check.py
--- a/checkov/cloudformation/checks/resource/base_resource_value_check.py
+++ b/checkov/cloudformation/checks/resource/base_resource_value_check.py
@@ -1,11 +1,12 @@
+from __future__ import annotations
+
 import re
 from abc import abstractmethod
 from collections.abc import Iterable
-from typing import List, Any, Dict
+from typing import List, Any
 
 from checkov.cloudformation.checks.resource.base_resource_check import BaseResourceCheck
 from checkov.cloudformation.context_parser import ContextParser
-from checkov.common.parsers.node import StrNode, DictNode
 from checkov.common.models.consts import ANY_VALUE
 from checkov.common.models.enums import CheckResult, CheckCategories
 from checkov.common.util.type_forcers import force_list
@@ -50,7 +51,7 @@ def _is_nesting_key(inspected_attributes: List[str], key: str) -> bool:
         """
         return any(x in key for x in inspected_attributes)
 
-    def scan_resource_conf(self, conf: Dict[StrNode, DictNode]) -> CheckResult:
+    def scan_resource_conf(self, conf: dict[str, Any]) -> CheckResult:
         inspected_key = self.get_inspected_key()
         expected_values = self.get_expected_values()
         path_elements = inspected_key.split("/")


Test patch:
diff --git a/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-AWS-Wildcard.yaml b/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-AWS-Wildcard.yaml
--- a/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-AWS-Wildcard.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-AWS-Wildcard.yaml
@@ -1,7 +1,7 @@
 AWSTemplateFormatVersion: 2010-09-09
 Description: KMS key example template
 Resources:
-  myKey:
+  AwsWildcard:
     Type: AWS::KMS::Key
     Properties:
       KeyPolicy:
@@ -15,7 +15,7 @@ Resources:
           Action: kms:*
           Resource: '*'
       EnableKeyRotation: true
-  SomeKmsKey:
+  AwsWildcardList:
     Type: 'AWS::KMS::Key'
     Properties:
       KeyPolicy:
diff --git a/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-Wildcard.yaml b/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-Wildcard.yaml
--- a/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-Wildcard.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-FAILED-Wildcard.yaml
@@ -1,7 +1,7 @@
 AWSTemplateFormatVersion: 2010-09-09
 Description: KMS key example template
 Resources:
-  myKey:
+  Wildcard:
     Type: AWS::KMS::Key
     Properties:
       KeyPolicy:
diff --git a/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-PASSED.yaml b/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-PASSED.yaml
--- a/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-PASSED.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_KMSKeyWildCardPrincipal/KMSKeyWildCardPrincipal-PASSED.yaml
@@ -1,7 +1,7 @@
 AWSTemplateFormatVersion: 2010-09-09
 Description: KMS key example template
 Resources:
-  myKey:
+  Root:
     Type: AWS::KMS::Key
     Properties:
       KeyPolicy:
@@ -15,3 +15,17 @@ Resources:
           Action: 'kms:*'
           Resource: '*'
       EnableKeyRotation: true
+  Deny:
+    Type: AWS::KMS::Key
+    Properties:
+      KeyPolicy:
+        Version: '2012-10-17'
+        Id: key-default-1
+        Statement:
+        - Sid: Enable Permissions for All AWS Principals
+          Effect: Deny
+          Principal:
+            AWS: '*'
+          Action: kms:*
+          Resource: '*'
+      EnableKeyRotation: true
diff --git a/tests/cloudformation/checks/resource/aws/test_KMSKeyWildCardPrincipal.py b/tests/cloudformation/checks/resource/aws/test_KMSKeyWildCardPrincipal.py
--- a/tests/cloudformation/checks/resource/aws/test_KMSKeyWildCardPrincipal.py
+++ b/tests/cloudformation/checks/resource/aws/test_KMSKeyWildCardPrincipal.py
@@ -7,7 +7,6 @@
 
 
 class TestKMSKeyWildCardPrincipal(unittest.TestCase):
-
     def test_summary(self):
         runner = Runner()
         current_dir = os.path.dirname(os.path.realpath(__file__))
@@ -16,11 +15,28 @@ def test_summary(self):
         report = runner.run(root_folder=test_files_dir,runner_filter=RunnerFilter(checks=[check.id]))
         summary = report.get_summary()
 
-        self.assertEqual(summary['passed'], 1)
-        self.assertEqual(summary['failed'], 3)
+
+        passing_resources = {
+            "AWS::KMS::Key.Root",
+            "AWS::KMS::Key.Deny",
+        }
+        failing_resources = {
+            "AWS::KMS::Key.AwsWildcard",
+            "AWS::KMS::Key.AwsWildcardList",
+            "AWS::KMS::Key.Wildcard",
+        }
+
+        passed_check_resources = {c.resource for c in report.passed_checks}
+        failed_check_resources = {c.resource for c in report.failed_checks}
+
+        self.assertEqual(summary['passed'], len(passing_resources))
+        self.assertEqual(summary['failed'], len(failing_resources))
         self.assertEqual(summary['skipped'], 0)
         self.assertEqual(summary['parsing_errors'], 0)
 
+        self.assertEqual(passing_resources, passed_check_resources)
+        self.assertEqual(failing_resources, failed_check_resources)
+
 
 if __name__ == '__main__':
     unittest.main()