Repository: aws-cloudformation/cfn-lint

Issue:
AWS::Lambda::EventSourceMapping defaulting to enforce StartingPosition when EventSourceArn is a reference/lookup
### CloudFormation Lint Version

1.4.0, 1.4.2

### What operating system are you using?

Mac, Centos, RHEL

### Describe the bug

When using a parameter lookup as the `ARN` for `EventSourceArn` in the resource type `AWS::Lambda::EventSourceMapping`, cfn-lint is throwing error `E3633`.

### Expected behavior

This error should only show up when the `ARN` of `EventSourceArn` in the resource type `AWS::Lambda::EventSourceMapping` contains `(kinesis|kafka|dynamodb)`.

If this ARN pattern is not present, enforcement of `StartingPosition` should not be enforced during linting.

### Reproduction template

```
AWSTemplateFormatVersion: '2010-09-09'
Resources:
SES_EventSourceMapping:
    Type: AWS::Lambda::EventSourceMapping
    Properties:
      BatchSize: 10
      Enabled: true
      EventSourceArn: '{{resolve:ssm:/SQS_Queue/SQS_ARN}}'
      FunctionName: MyFunctionNameHere
```


Discussion:
The basics here is that we extend the resource schemas here with additional schemas.  In this case we look for `kinesis|kafka|dynamodb` and determine if other properties should exist.  Additionally we look for dynamic references and validate those but we can no longer validate the pattern that includes `kinesis|kafka|dynamodb` so we skip those tests which results in the schema being true.  What will be appropriate is when we are doing this validation we skip the dynamic reference validation and force the pattern matching to occur.

I need evaluate some of our custom schemas because if this additional schema had an else we may just flip the problem to the other side of the if/then/else statement.  We are doing this already for functions but need to cover also do it for dynamic references.

PR incoming. 



Patch:
diff --git a/scripts/update_snapshot_results.sh b/scripts/update_snapshot_results.sh
index 5ba58587ce..557b1c4df5 100755
--- a/scripts/update_snapshot_results.sh
+++ b/scripts/update_snapshot_results.sh
@@ -1,5 +1,9 @@
 #!/usr/bin/env bash
 
+# integration/
+cfn-lint test/fixtures/templates/integration/dynamic-references.yaml -e -c I --format json > test/fixtures/results/integration/dynamic-references.json
+cfn-lint test/fixtures/templates/integration/resources-cloudformation-init.yaml -e -c I --format json > test/fixtures/results/integration/resources-cloudformation-init.json
+
 # public/
 cfn-lint test/fixtures/templates/public/lambda-poller.yaml -e -c I --format json > test/fixtures/results/public/lambda-poller.json
 cfn-lint test/fixtures/templates/public/watchmaker.json -e -x E3012:strict=true -c I --format json > test/fixtures/results/public/watchmaker.json
diff --git a/src/cfnlint/data/schemas/extensions/aws_rds_dbinstance/aurora_exclusive.json b/src/cfnlint/data/schemas/extensions/aws_rds_dbinstance/aurora_exclusive.json
index 42efdcd482..bee9daf906 100644
--- a/src/cfnlint/data/schemas/extensions/aws_rds_dbinstance/aurora_exclusive.json
+++ b/src/cfnlint/data/schemas/extensions/aws_rds_dbinstance/aurora_exclusive.json
@@ -1,7 +1,6 @@
 {
  "additionalProperties": true,
  "description": "When creating an aurora DBInstance don't specify 'AllocatedStorage', 'BackupRetentionPeriod', 'CopyTagsToSnapshot', 'DeletionProtection', 'EnableIAMDatabaseAuthentication', 'MasterUserPassword', or 'StorageEncrypted'",
- "else": {},
  "if": {
   "properties": {
    "Engine": {
diff --git a/src/cfnlint/data/schemas/extensions/aws_route53_healthcheck/healthcheckconfig_type_inclusive.json b/src/cfnlint/data/schemas/extensions/aws_route53_healthcheck/healthcheckconfig_type_inclusive.json
index 96f6ec7533..26d65e8908 100644
--- a/src/cfnlint/data/schemas/extensions/aws_route53_healthcheck/healthcheckconfig_type_inclusive.json
+++ b/src/cfnlint/data/schemas/extensions/aws_route53_healthcheck/healthcheckconfig_type_inclusive.json
@@ -1,7 +1,6 @@
 {
  "additionalProperties": true,
  "description": "When 'Type' is 'CLOUDWATCH_METRIC' you must specify 'AlarmIdentifier'",
- "else": {},
  "if": {
   "properties": {
    "Type": {
diff --git a/src/cfnlint/jsonschema/_filter.py b/src/cfnlint/jsonschema/_filter.py
index 2c24b99674..3cd695c99a 100644
--- a/src/cfnlint/jsonschema/_filter.py
+++ b/src/cfnlint/jsonschema/_filter.py
@@ -57,6 +57,11 @@ class FunctionFilter:
         default=True,
     )
 
+    validate_dynamic_references: bool = field(
+        init=True,
+        default=True,
+    )
+
     def _filter_schemas(self, schema, validator: Validator) -> Tuple[Any, Any]:
         """
         Filter the schemas to only include the ones that are required
@@ -104,7 +109,7 @@ def _filter_schemas(self, schema, validator: Validator) -> Tuple[Any, Any]:
 
     def filter(self, validator: Any, instance: Any, schema: Any):
         # Lets validate dynamic references when appropriate
-        if validator.is_type(instance, "string"):
+        if validator.is_type(instance, "string") and self.validate_dynamic_references:
             if REGEX_DYN_REF.findall(instance):
                 # if we are in a function we can't validate
                 # dynamic references the same way
diff --git a/src/cfnlint/rules/functions/DynamicReference.py b/src/cfnlint/rules/functions/DynamicReference.py
index 745e3c6bc8..7819ba0bf9 100644
--- a/src/cfnlint/rules/functions/DynamicReference.py
+++ b/src/cfnlint/rules/functions/DynamicReference.py
@@ -95,12 +95,17 @@ def dynamicReference(
         # SSM parameters can be used in Resources and Outputs and Parameters
         # SSM secrets are only used in a small number of locations
         # Secrets manager can be used only in resource properties
+        validator = validator.evolve(
+            function_filter=validator.function_filter.evolve(
+                add_cfn_lint_keyword=False,
+            ),
+        )
 
         for v in REGEX_DYN_REF.findall(instance):
             parts = v.split(":")
 
-            evolved = validator.evolve(schema=_all)
             found = False
+            evolved = validator.evolve(schema=_all)
             for err in evolved.iter_errors(parts):
                 yield self._clean_errors(err)
                 found = True
@@ -126,4 +131,3 @@ def dynamicReference(
 
             for err in evolved.iter_errors(parts):
                 yield self._clean_errors(err)
-                found = True
diff --git a/src/cfnlint/rules/jsonschema/CfnLintJsonSchema.py b/src/cfnlint/rules/jsonschema/CfnLintJsonSchema.py
index e00f58ec51..8e3fda748c 100644
--- a/src/cfnlint/rules/jsonschema/CfnLintJsonSchema.py
+++ b/src/cfnlint/rules/jsonschema/CfnLintJsonSchema.py
@@ -67,7 +67,12 @@ def validate(
             schema = self._schema
 
         cfn_validator = self.extend_validator(
-            validator=validator,
+            validator=validator.evolve(
+                function_filter=validator.function_filter.evolve(
+                    validate_dynamic_references=False,
+                    add_cfn_lint_keyword=False,
+                )
+            ),
             schema=schema,
             context=validator.context.evolve(
                 functions=[],


Test patch:
diff --git a/test/fixtures/results/integration/dynamic-references.json b/test/fixtures/results/integration/dynamic-references.json
new file mode 100644
index 0000000000..70ae3fd909
--- /dev/null
+++ b/test/fixtures/results/integration/dynamic-references.json
@@ -0,0 +1,31 @@
+[
+    {
+        "Filename": "test/fixtures/templates/integration/dynamic-references.yaml",
+        "Id": "e019d69f-56db-cf2f-2bbb-f951c3bc9782",
+        "Level": "Error",
+        "Location": {
+            "End": {
+                "ColumnNumber": 21,
+                "LineNumber": 16
+            },
+            "Path": [
+                "Resources",
+                "SESEventSourceMappingBadDynamicReference",
+                "Properties",
+                "EventSourceArn"
+            ],
+            "Start": {
+                "ColumnNumber": 7,
+                "LineNumber": 16
+            }
+        },
+        "Message": "'{{:ssm:/SQS_Queue/SQS_ARN}}' does not match 'arn:(aws[a-zA-Z0-9-]*):([a-zA-Z0-9\\\\-])+:([a-z]{2}(-gov)?(-iso([a-z])?)?-[a-z]+-\\\\d{1})?:(\\\\d{12})?:(.*)'",
+        "ParentId": null,
+        "Rule": {
+            "Description": "Check if properties have a valid value in case of a pattern (Regular Expression)",
+            "Id": "E3031",
+            "ShortDescription": "Check if property values adhere to a specific pattern",
+            "Source": "https://github.com/aws-cloudformation/cfn-lint/blob/main/docs/cfn-schema-specification.md#pattern"
+        }
+    }
+]
diff --git a/test/fixtures/results/integration/good/resources-cloudformation-init.json b/test/fixtures/results/integration/resources-cloudformation-init.json
similarity index 100%
rename from test/fixtures/results/integration/good/resources-cloudformation-init.json
rename to test/fixtures/results/integration/resources-cloudformation-init.json
diff --git a/test/fixtures/templates/integration/dynamic-references.yaml b/test/fixtures/templates/integration/dynamic-references.yaml
new file mode 100644
index 0000000000..cc9dc10042
--- /dev/null
+++ b/test/fixtures/templates/integration/dynamic-references.yaml
@@ -0,0 +1,31 @@
+
+AWSTemplateFormatVersion: '2010-09-09'
+Resources:
+  SESEventSourceMapping:
+    Type: AWS::Lambda::EventSourceMapping
+    Properties:
+      BatchSize: 10
+      Enabled: true
+      EventSourceArn: '{{resolve:ssm:/SQS_Queue/SQS_ARN}}'
+      FunctionName: MyFunctionNameHere
+  SESEventSourceMappingBadDynamicReference:
+    Type: AWS::Lambda::EventSourceMapping
+    Properties:
+      BatchSize: 10
+      Enabled: true
+      EventSourceArn: '{{:ssm:/SQS_Queue/SQS_ARN}}'
+      FunctionName: MyFunctionNameHere
+  Broker:
+    Type: AWS::AmazonMQ::Broker
+    Properties:
+        Users:
+        - Username: test
+          Password: '{{resolve:secretsmanager:MyRDSSecret:SecretString:password}}'
+        DeploymentMode: SINGLE_INSTANCE
+        EngineType: RABBITMQ
+        EngineVersion: test
+        # testing dynamic reference against the regional schema validation
+        HostInstanceType: '{{resolve:ssm:/SQS_Queue/SQS_ARN}}'
+        AutoMinorVersionUpgrade: true
+        BrokerName: test
+        PubliclyAccessible: false
diff --git a/test/fixtures/templates/integration/good/resources-cloudformation-init.yaml b/test/fixtures/templates/integration/resources-cloudformation-init.yaml
similarity index 100%
rename from test/fixtures/templates/integration/good/resources-cloudformation-init.yaml
rename to test/fixtures/templates/integration/resources-cloudformation-init.yaml
diff --git a/test/integration/jsonschema/test_resources_cfn_init.py b/test/integration/test_integration_templates.py
similarity index 70%
rename from test/integration/jsonschema/test_resources_cfn_init.py
rename to test/integration/test_integration_templates.py
index e510447d88..87fd92938e 100644
--- a/test/integration/jsonschema/test_resources_cfn_init.py
+++ b/test/integration/test_integration_templates.py
@@ -14,15 +14,24 @@ class TestQuickStartTemplates(BaseCliTestCase):
     scenarios = [
         {
             "filename": (
-                "test/fixtures/templates/integration/good"
+                "test/fixtures/templates/integration"
                 "/resources-cloudformation-init.yaml"
             ),
             "results_filename": (
                 "test/fixtures/results/integration/"
-                "good/resources-cloudformation-init.json"
+                "resources-cloudformation-init.json"
             ),
             "exit_code": 0,
         },
+        {
+            "filename": (
+                "test/fixtures/templates/integration" "/dynamic-references.yaml"
+            ),
+            "results_filename": (
+                "test/fixtures/results/integration/" "dynamic-references.json"
+            ),
+            "exit_code": 2,
+        },
     ]
 
     def test_templates(self):
diff --git a/test/unit/rules/jsonschema/test_cfn_schema.py b/test/unit/rules/jsonschema/test_cfn_schema.py
index ec847473da..3e9a3a9957 100644
--- a/test/unit/rules/jsonschema/test_cfn_schema.py
+++ b/test/unit/rules/jsonschema/test_cfn_schema.py
@@ -3,6 +3,8 @@
 SPDX-License-Identifier: MIT-0
 """
 
+from __future__ import annotations
+
 from collections import deque
 
 import pytest
@@ -18,6 +20,10 @@
             "type": "string",
         },
         "b": {"type": "object", "properties": {"1": {"type": "string"}}},
+        "c": {
+            "type": "string",
+            "pattern": "^foo$",
+        },
     },
 }
 
@@ -78,6 +84,20 @@ def __init__(self) -> None:
                 ),
             ],
         ),
+        (
+            "Validation error with dynamic reference",
+            _BestError(),
+            {"c": "{{resolve:ssm:/SQS_Queue/SQS_ARN}}"},
+            [
+                ValidationError(
+                    message="Rule that returns the best error",
+                    validator="pattern",
+                    path=deque(["c"]),
+                    rule=_BestError(),
+                    schema_path=deque(["properties", "c", "pattern"]),
+                ),
+            ],
+        ),
     ],
 )
 def test_cfn_schema(name, rule, instance, expected_errs):