Repository: bridgecrewio/checkov

Issue:
CKV_AWS_45 False Positive
**Describe the issue**
[CKV_AWS_45](https://github.com/bridgecrewio/checkov/blob/main/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py) gives a false positive to any value after a lambda environment variable that is exactly 40 characters long due to the AWS secret access key [regex](https://github.com/bridgecrewio/checkov/blob/4875adc472f409bc7039a879347b404468e88215/checkov/common/util/secrets.py#L49). Obviously this regex makes sense. However, this check could do a deeper analysis on the environment variable being passed in to have less false positives. In the example below, I think it would be at least a good idea to check if an intrinsic function is used (in this case `!Ref`) in order to reduce false positives. 

**Examples**
```yml
Resources:
  MyCoolLambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: blah
      Handler: src.app.lambda_handler
      Policies:
      - StepFunctionsExecutionPolicy:
          StateMachineName: !GetAtt MySuperCoolFortyCharLongStateMachineeeee.Name
      Environment:
        Variables:
          MY_COOL_STATE_MACHINE: !Ref MySuperCoolFortyCharLongStateMachineeeee # 40 chars in ref fails check
```
                                                                    
 
**Version (please complete the following information):**
 - Checkov Version latest

**Additional context**
Add any other context about the problem here.

You can see how this works by calling the `get_secrets_from_string` method directly in a python terminal which is what is being called in the `CKV_AWS_45` [check](https://github.com/bridgecrewio/checkov/blob/4875adc472f409bc7039a879347b404468e88215/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py#L27).

```python
from checkov.common.util.secrets import AWS, GENERAL, get_secrets_from_string

get_secrets_from_string("MySuperCoolFortyCharLongStateMachineeeee", AWS, GENERAL)
# ['MySuperCoolFortyCharLongStateMachineeeee']

get_secrets_from_string("x"*40, AWS, GENERAL)
# ['xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx']

get_secrets_from_string("x"*41, AWS, GENERAL)
# []
```



Discussion:
hey @bakosa thanks for reaching out.

Yeah, seems like a very good idea 😄 I will create a PR for it

Resolving patch:
diff --git a/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py b/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py
--- a/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py
+++ b/checkov/cloudformation/checks/resource/aws/LambdaEnvironmentCredentials.py
@@ -24,6 +24,10 @@ def scan_resource_conf(self, conf: dict[str, Any]) -> CheckResult:
                 variables = environment.get("Variables")
                 if variables and isinstance(variables, dict):
                     for var_name, value in variables.items():
+                        if isinstance(value, dict):
+                            # if it is a resolved instrinsic function like !Ref: xyz, then it can't be a secret
+                            continue
+
                         secrets = get_secrets_from_string(str(value), AWS, GENERAL)
                         if secrets:
                             self.evaluated_keys = [f"Properties/Environment/Variables/{var_name}"]
diff --git a/checkov/common/util/secrets.py b/checkov/common/util/secrets.py
--- a/checkov/common/util/secrets.py
+++ b/checkov/common/util/secrets.py
@@ -67,7 +67,7 @@
 # now combine all the compiled patterns into one long list
 _patterns['all'] = list(itertools.chain.from_iterable(_patterns.values()))
 
-_hash_patterns = list(map(lambda regex: re.compile(regex, re.IGNORECASE), ['^[a-f0-9]{32}$', '^[a-f0-9]{40}$']))
+_hash_patterns = [re.compile(regex, re.IGNORECASE) for regex in ('^[a-f0-9]{32}$', '^[a-f0-9]{40}$')]
 
 
 def is_hash(s: str) -> bool:
@@ -96,14 +96,14 @@ def string_has_secrets(s: str, *categories: str) -> bool:
     :return:
     """
 
+    if is_hash(s):
+        return False
+
     # set a default if no category is provided; or, if categories were provided and they include 'all', then just set it
     # explicitly so we don't do any duplication
     if not categories or "all" in categories:
         categories = ("all",)
 
-    if is_hash(s):
-        return False
-
     for c in categories:
         if any([pattern.search(s) for pattern in _patterns[c]]):
             return True
@@ -229,18 +229,14 @@ def omit_secret_value_from_graph_checks(
 def get_secrets_from_string(s: str, *categories: str) -> list[str]:
     # set a default if no category is provided; or, if categories were provided and they include 'all', then just set it
     # explicitly so we don't do any duplication
+    if is_hash(s):
+        return []
+
     if not categories or "all" in categories:
         categories = ("all",)
 
-    if is_hash(s):
-        return list()
-
     secrets: list[str] = []
     for c in categories:
-        matches: list[str] = []
         for pattern in _patterns[c]:
-            _matches = re.finditer(pattern, s)
-            matches.extend([str(match.group()) for match in _matches])
-        if matches:
-            secrets.extend(matches)
+            secrets.extend(str(match.group()) for match in pattern.finditer(s))
     return secrets


Test patch:
diff --git a/tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/PASS.yaml b/tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/PASS.yaml
--- a/tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/PASS.yaml
+++ b/tests/cloudformation/checks/resource/aws/example_LambdaEnvironmentCredentials/PASS.yaml
@@ -41,3 +41,18 @@ Resources:
       Description: Invoke a function during stack creation.
       TracingConfig:
         Mode: Active
+  UnresolvedEnv:
+    Type: AWS::Lambda::Function
+    Properties:
+      Runtime: nodejs12.x
+      Role: arn:aws:iam::123456789012:role/lambda-role
+      Handler: index.handler
+      Environment:
+        Variables:
+          MY_COOL_STATE_MACHINE: !Ref MySuperCoolFortyCharLongStateMachineeeee
+      Code:
+        ZipFile: |
+          print('hi')
+      Description: Invoke a function during stack creation.
+      TracingConfig:
+        Mode: Active
diff --git a/tests/cloudformation/checks/resource/aws/test_LambdaEnvironmentCredentials.py b/tests/cloudformation/checks/resource/aws/test_LambdaEnvironmentCredentials.py
--- a/tests/cloudformation/checks/resource/aws/test_LambdaEnvironmentCredentials.py
+++ b/tests/cloudformation/checks/resource/aws/test_LambdaEnvironmentCredentials.py
@@ -17,6 +17,7 @@ def test_summary(self):
             "AWS::Lambda::Function.NoEnv",
             "AWS::Lambda::Function.NoSecret",
             "AWS::Lambda::Function.EnvNull",
+            "AWS::Lambda::Function.UnresolvedEnv",
             "AWS::Serverless::Function.NoEnv",
             "AWS::Serverless::Function.NoProperties",
             "AWS::Serverless::Function.NoSecret",
@@ -29,8 +30,8 @@ def test_summary(self):
         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"], 6)
-        self.assertEqual(summary["failed"], 2)
+        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)