diff --git a/docs/rules.md b/docs/rules.md index 3099e5a0aa..b1eff7a6e0 100644 --- a/docs/rules.md +++ b/docs/rules.md @@ -46,7 +46,7 @@ To include these rules, use the `-e/include-experimental` argument when running ## Rules (_This documentation is generated by running `cfn-lint --update-documentation`, do not alter this manually_) -The following **152** rules are applied by this linter: +The following **153** rules are applied by this linter: | Rule ID | Title | Description | Config
(Name:Type:Default) | Source | Tags | | -------- | ----- | ----------- | ---------- | ------ | ---- | @@ -191,6 +191,7 @@ The following **152** rules are applied by this linter: | [W2510](../src/cfnlint/rules/parameters/LambdaMemorySize.py) | Parameter Memory Size attributes should have max and min | Check if a parameter that is used for Lambda memory size should have a min and max size that matches Lambda constraints | | [Source](https://docs.aws.amazon.com/lambda/latest/dg/API_CreateFunction.html#SSS-CreateFunction-request-MemorySize) | `parameters`,`lambda` | | [W2511](../src/cfnlint/rules/resources/iam/PolicyVersion.py) | Check IAM Resource Policies syntax | See if the elements inside an IAM Resource policy are configured correctly. | | [Source](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements.html) | `properties`,`iam` | | [W2531](../src/cfnlint/rules/resources/lmbd/DeprecatedRuntimeEol.py) | Check if EOL Lambda Function Runtimes are used | Check if an EOL Lambda Runtime is specified and give a warning if used. | | [Source](https://docs.aws.amazon.com/lambda/latest/dg/runtime-support-policy.html) | `resources`,`lambda`,`runtime` | +| [W2533](../src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py) | Check required properties for Lambda if the deployment package is a .zip file | When the package type is Zip, you must also specify the `handler` and `runtime` properties. | | [Source](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html) | `resources`,`lambda` | | [W3002](../src/cfnlint/rules/resources/properties/PropertiesTemplated.py) | Warn when properties are configured to only work with the package command | Some properties can be configured to only work with the CloudFormationpackage command. Warn when this is the case so user is aware. | | [Source](https://docs.aws.amazon.com/cli/latest/reference/cloudformation/package.html) | `resources` | | [W3005](../src/cfnlint/rules/resources/DependsOnObsolete.py) | Check obsolete DependsOn configuration for Resources | Check if DependsOn is specified if not needed. A Ref or a Fn::GetAtt already is an implicit dependency. | | [Source](https://aws.amazon.com/blogs/devops/optimize-aws-cloudformation-templates/) | `resources`,`dependson`,`ref`,`getatt` | | [W3010](../src/cfnlint/rules/resources/properties/AvailabilityZone.py) | Availability Zone Parameters should not be hardcoded | Check if an Availability Zone property is hardcoded. | | [Source](https://github.com/aws-cloudformation/cfn-python-lint) | `parameters`,`availabilityzone` | diff --git a/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py new file mode 100644 index 0000000000..6254b82f28 --- /dev/null +++ b/src/cfnlint/rules/resources/lmbd/ZipPackageRequiredProperties.py @@ -0,0 +1,76 @@ +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" +from cfnlint.rules import CloudFormationLintRule, RuleMatch + + +class ZipPackageRequiredProperties(CloudFormationLintRule): + id = "W2533" + shortdesc = ( + "Check required properties for Lambda if the deployment package is a .zip file" + ) + description = ( + "When the package type is Zip, " + "you must also specify the `handler` and `runtime` properties." + ) + source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html" + tags = ["resources", "lambda"] + + def match(self, cfn): + matches = [] + required_properties = [ + "Handler", + "Runtime", + ] # required if package is a .zip file + + resources = cfn.get_resources(["AWS::Lambda::Function"]) + + for resource_name, resource in resources.items(): + properties = resource.get("Properties") + if not isinstance(properties, dict): + continue + + for scenario in cfn.get_object_without_conditions( + properties, ["PackageType", "Code", "Handler", "Runtime"] + ): + props = scenario.get("Object") + path = ["Resources", resource_name, "Properties"] + + # check is zip deployment + is_zip_deployment = True + code = props.get("Code") + + if props.get("PackageType") == "Zip": + path.append("PackageType") + elif isinstance(code, dict) and ( + code.get("ZipFile") or code.get("S3Key") + ): + path.append("Code") + else: + is_zip_deployment = False + + if not is_zip_deployment: + continue + + # check required properties for zip deployment + missing_properties = [] + for p in required_properties: + if props.get(p) is None: + missing_properties.append(p) + + if len(missing_properties) > 0: + message = "Properties {0} missing for zip file deployment at {1}" + matches.append( + RuleMatch( + path, + message.format( + missing_properties, + "/".join( + map(str, ["Resources", resource_name, "Properties"]) + ), + ), + ) + ) + + return matches diff --git a/test/fixtures/templates/bad/resources/lambda/required_properties.yaml b/test/fixtures/templates/bad/resources/lambda/required_properties.yaml new file mode 100644 index 0000000000..c087056343 --- /dev/null +++ b/test/fixtures/templates/bad/resources/lambda/required_properties.yaml @@ -0,0 +1,32 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Missing properties. +Resources: + Function1: + Type: AWS::Lambda::Function + Properties: +# Handler: index.handler + Role: arn:aws:iam::123456789012:role/lambda-role + Code: + S3Bucket: my-bucket + S3Key: function.zip +# Runtime: nodejs16.x + Function2: + Type: AWS::Lambda::Function + Properties: +# Handler: index.handler + Role: arn:aws:iam::123456789012:role/lambda-role + Code: + S3Bucket: my-bucket + S3Key: function.zip + Runtime: python3.9 + PackageType: Zip + Function3: + Type: AWS::Lambda::Function + Properties: + Handler: index.handler + Role: arn:aws:iam::123456789012:role/lambda-role + Code: + ZipFile: | + var aws = require('aws-sdk') + exports.handler = function(event, context) {} +# Runtime: nodejs16.x \ No newline at end of file diff --git a/test/fixtures/templates/good/resources/lambda/required_properties.yaml b/test/fixtures/templates/good/resources/lambda/required_properties.yaml new file mode 100644 index 0000000000..36823a575f --- /dev/null +++ b/test/fixtures/templates/good/resources/lambda/required_properties.yaml @@ -0,0 +1,29 @@ +AWSTemplateFormatVersion: '2010-09-09' +Description: Required Properties. +Resources: + Function1: # S3 key to deployment package + Type: AWS::Lambda::Function + Properties: + Handler: index.handler + Role: arn:aws:iam::123456789012:role/lambda-role + Code: + S3Bucket: my-bucket + S3Key: function.zip + Runtime: python3.9 + Function2: # source inline (zipped by CloudFormation) + Type: AWS::Lambda::Function + Properties: + Handler: index.handler + Role: arn:aws:iam::123456789012:role/lambda-role + Code: + ZipFile: | + var aws = require('aws-sdk') + exports.handler = function(event, context) {} + Runtime: nodejs16.x + Function3: # image deployment + Type: AWS::Lambda::Function + Properties: + Role: arn:aws:iam::123456789012:role/lambda-role + Code: + ImageUri : 111122223333.dkr.ecr.us-east-1.amazonaws.com/hello-world:latest + PackageType: Image \ No newline at end of file diff --git a/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py b/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py new file mode 100644 index 0000000000..409b41beea --- /dev/null +++ b/test/unit/rules/resources/lmbd/test_zip_package_required_properties.py @@ -0,0 +1,29 @@ +""" +Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +SPDX-License-Identifier: MIT-0 +""" +from test.unit.rules import BaseRuleTestCase + +from cfnlint.rules.resources.lmbd.ZipPackageRequiredProperties import ( + ZipPackageRequiredProperties, +) + + +class TestZipPackageRequiredProperties(BaseRuleTestCase): + """Test required properties""" + + def setUp(self): + super(TestZipPackageRequiredProperties, self).setUp() + self.collection.register(ZipPackageRequiredProperties()) + self.success_templates = [ + "test/fixtures/templates/good/resources/lambda/required_properties.yaml" + ] + + def test_file_positive(self): + self.helper_file_positive() + + def test_file_negative(self): + self.helper_file_negative( + "test/fixtures/templates/bad/resources/lambda/required_properties.yaml", + err_count=3, + )