Repository: aws-cloudformation/cfn-python-lint

Issue:
E3009 Additional properties are not allowed ('Fn::Transform' was unexpected)
### CloudFormation Lint Version

1.44.0

### What operating system are you using?

Alpine (container, from cfn-lint Dockerfile)

### Describe the bug

A template that is using Fn::Transform is giving an error `E3009 Additional properties are not allowed ('Fn::Transform' was unexpected)`. CloudFormation deploys it without complaining. I suspect this has something to do with `Fn::Transform` being under `AWS::CloudFormation::Init` metadata.

### Expected behavior

Template should be parsed without error.

### Reproduction template

```yaml
AWSTemplateFormatVersion: 2010-09-09
Resources:
  ApplicationInstance:
    Type: AWS::EC2::Instance
    Metadata:
      "AWS::CloudFormation::Init":
        configSets:
          installAndConfigure:
            - installCoreDependencies
            - configureHttpd
        installCoreDependencies:
          packages:
            yum:
              httpd: []
        configureHttpd:
          files:
            /etc/httpd/conf.d/www.conf:
              Fn::Transform:
                Name: AWS::Include
                Parameters:
                  Location: "s3://unique-bucket-name/includes/www.conf.yaml"
              mode: "000644"
              owner: "root"
              group: "root"
    Properties:
      ImageId: ami-0a9f08a6603f3338e
      InstanceType: t4g.micro
      SecurityGroupIds:
        - sg-0b4c66acea364af1
      SubnetId: subnet-0f10a6f6d7cec2aef
      UserData:
        Fn::Base64: !Sub |
          #!/bin/bash -xe

          /opt/aws/bin/cfn-init \
            --stack ${AWS::StackName} \
            --resource ApplicationInstance \
            --configsets installAndConfigure \
            --region ${AWS::Region} \
            --verbose

          /opt/aws/bin/cfn-signal \
            --exit-code $? \
            --stack ${AWS::StackName} \
            --resource ApplicationInstance \
            --region ${AWS::Region}
```

Discussion hints:

Solution patch:
diff --git a/src/cfnlint/jsonschema/_utils.py b/src/cfnlint/jsonschema/_utils.py
index 5bc4db84aa..3cd9bb805e 100644
--- a/src/cfnlint/jsonschema/_utils.py
+++ b/src/cfnlint/jsonschema/_utils.py
@@ -22,6 +22,8 @@
 
 import regex as re
 
+from cfnlint.helpers import FUNCTION_TRANSFORM
+
 
 class Unset:
     """
@@ -45,6 +47,11 @@ def find_additional_properties(validator, instance, schema):
     properties = schema.get("properties", {})
     patterns = "|".join(schema.get("patternProperties", {}))
     for property in instance:
+        if (
+            property == FUNCTION_TRANSFORM
+            and FUNCTION_TRANSFORM in validator.context.functions
+        ):
+            continue
         if property not in properties:
             if validator.is_type(property, "string"):
                 if patterns and re.search(patterns, property):


Test patch:
diff --git a/test/unit/module/jsonschema/test_utils.py b/test/unit/module/jsonschema/test_utils.py
index 4aa8c3e3c5..43b7b73f36 100644
--- a/test/unit/module/jsonschema/test_utils.py
+++ b/test/unit/module/jsonschema/test_utils.py
@@ -5,7 +5,9 @@
 
 import unittest
 
-from cfnlint.jsonschema._utils import equal, uniq, uniq_keys
+import pytest
+
+from cfnlint.jsonschema._utils import equal, find_additional_properties, uniq, uniq_keys
 
 
 class TestUtils(unittest.TestCase):
@@ -48,3 +50,56 @@ def test_uniq_keys(self):
         self.assertFalse(
             uniq_keys([{"foo": "foo"}, {"bar": "bar"}, {"foo": "foo"}], ["foo"])
         )
+
+
+@pytest.mark.parametrize(
+    "instance,schema,functions,expected",
+    [
+        # No additional properties
+        (
+            {"foo": "bar"},
+            {"properties": {"foo": {}}},
+            [],
+            [],
+        ),
+        # Additional property found
+        (
+            {"foo": "bar", "baz": "qux"},
+            {"properties": {"foo": {}}},
+            [],
+            ["baz"],
+        ),
+        # Pattern property matches
+        (
+            {"foo": "bar", "test123": "value"},
+            {"properties": {"foo": {}}, "patternProperties": {"test[0-9]+": {}}},
+            [],
+            [],
+        ),
+        # Fn::Transform excluded when in functions
+        (
+            {"foo": "bar", "Fn::Transform": {"Name": "AWS::Include"}},
+            {"properties": {"foo": {}}},
+            ["Fn::Transform"],
+            [],
+        ),
+        # Fn::Transform not excluded when not in functions
+        (
+            {"foo": "bar", "Fn::Transform": {"Name": "AWS::Include"}},
+            {"properties": {"foo": {}}},
+            [],
+            ["Fn::Transform"],
+        ),
+        # Fn::Transform with other additional properties
+        (
+            {"foo": "bar", "Fn::Transform": {"Name": "AWS::Include"}, "extra": "val"},
+            {"properties": {"foo": {}}},
+            ["Fn::Transform"],
+            ["extra"],
+        ),
+    ],
+)
+def test_find_additional_properties(instance, schema, functions, expected, validator):
+    validator = validator.evolve(context=validator.context.evolve(functions=functions))
+    result = list(find_additional_properties(validator, instance, schema))
+    assert result == expected