Repository: aws-cloudformation/cfn-lint

Issue:
False positive E0001 for CodeUri in AWS::Serverless::Function
### CloudFormation Lint Version

cfn-lint 0.76.1

### What operating system are you using?

Mac

### Describe the bug

cfn-lint gives a false-positive error when CodeUri is an S3 URI.

```E0001 Error transforming template: Resource with id [rCustomAuthorizerFunction] is invalid. 'CodeUri' requires Bucket and Key properties to be specified.```

Not only does this template deploy via CloudFormation, the warning from cfn-lint contradicts the [public documentation for this resource type](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-function.html#Examples)

### Expected behavior

cfn-lint should not give a false positive that contradicts public documentation

### Reproduction template

```yaml
AWSTemplateFormatVersion: "2010-09-09"
Transform:
  - 'AWS::LanguageExtensions'
  - 'AWS::Serverless-2016-10-31'

Description: cfn-lint test case

Mappings:
  RegionMap:
    us-east-1:
      InsightsArn: arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:21
    us-west-2:
      InsightsArn: arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:21

Resources:
  rCustomAuthorizerFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub "my-custom-authorizer-${AWS::Region}"
      Description: Custom authorizer for APIGW
      Runtime: nodejs16.x
      Handler: index.handler
      CodeUri: !Sub "s3://custom-authz-${AWS::AccountId}-${AWS::Region}/function-v1.0.zip"
      Policies:
        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Layers:
        - !FindInMap [RegionMap, !Ref AWS::Region, "InsightsArn"]

```


Discussion:

Resolving patch:
diff --git a/src/cfnlint/transform.py b/src/cfnlint/transform.py
--- a/src/cfnlint/transform.py
+++ b/src/cfnlint/transform.py
@@ -218,7 +218,7 @@ def _update_to_s3_uri(
         if isinstance(uri_property, dict):
             if len(uri_property) == 1:
                 for k in uri_property.keys():
-                    if k == "Ref":
+                    if k in ["Ref", "Fn::Sub"]:
                         resource_property_dict[property_key] = s3_uri_value
             return
         if Transform.is_s3_uri(uri_property):


Test patch:
diff --git a/test/fixtures/templates/good/transform/function_use_s3_uri.yaml b/test/fixtures/templates/good/transform/function_use_s3_uri.yaml
new file mode 100644
--- /dev/null
+++ b/test/fixtures/templates/good/transform/function_use_s3_uri.yaml
@@ -0,0 +1,28 @@
+AWSTemplateFormatVersion: "2010-09-09"
+Transform:
+  - 'AWS::LanguageExtensions'
+  - 'AWS::Serverless-2016-10-31'
+
+Description: cfn-lint test case
+
+Mappings:
+  RegionMap:
+    us-east-1:
+      InsightsArn: arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:21
+    us-west-2:
+      InsightsArn: arn:aws:lambda:us-west-2:580247275435:layer:LambdaInsightsExtension:21
+
+Resources:
+  Function:
+    Type: AWS::Serverless::Function
+    Properties:
+      FunctionName: !Sub "my-custom-authorizer-${AWS::Region}"
+      Description: Custom authorizer for APIGW
+      Runtime: nodejs16.x
+      Handler: index.handler
+      CodeUri: !Sub "s3://custom-authz-${AWS::AccountId}-${AWS::Region}/function-v1.0.zip"
+      Policies:
+        - arn:aws:iam::aws:policy/CloudWatchLambdaInsightsExecutionRolePolicy
+        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
+      Layers:
+        - !FindInMap [RegionMap, !Ref AWS::Region, "InsightsArn"]
diff --git a/test/unit/module/transform/test_transform.py b/test/unit/module/transform/test_transform.py
--- a/test/unit/module/transform/test_transform.py
+++ b/test/unit/module/transform/test_transform.py
@@ -90,6 +90,21 @@ def test_conversion_of_step_function_definition_uri(self):
             {"Bucket": "bucket", "Key": "value"},
         )
 
+    def test_conversion_of_serverless_function_uri(self):
+        """Tests that the a serverless function can convert a CodeUri Sub"""
+        filename = "test/fixtures/templates/good/transform/function_use_s3_uri.yaml"
+        region = "us-east-1"
+        template = cfn_yaml.load(filename)
+        transformed_template = Transform(filename, template, region)
+        transformed_template.transform_template()
+        self.assertDictEqual(
+            transformed_template._template.get("Resources")
+            .get("Function")
+            .get("Properties")
+            .get("Code"),
+            {"S3Bucket": "bucket", "S3Key": "value"},
+        )
+
     def test_parameter_for_autopublish_version_bad(self):
         """Test Parameter is created for autopublish version run"""
         filename = "test/fixtures/templates/bad/transform/auto_publish_alias.yaml"