""" Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. SPDX-License-Identifier: MIT-0 """ from cfnlint.rules import CloudFormationLintRule, RuleMatch class Configuration(CloudFormationLintRule): """Check if Parameters are configured correctly""" id = "E2001" shortdesc = "Parameters have appropriate properties" description = "Making sure the parameters are properly configured" source_url = "https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/parameters-section-structure.html" tags = ["parameters"] valid_keys = { "AllowedPattern": {"Type": "String"}, "AllowedValues": { "Type": "List", "ItemType": "String", }, "ConstraintDescription": {"Type": "String"}, "Default": {"Type": "String"}, "Description": {"Type": "String"}, "MaxLength": { "Type": "Integer", "ValidForTypes": [ "String", "AWS::EC2::AvailabilityZone::Name", "AWS::EC2::Image::Id", "AWS::EC2::Instance::Id", "AWS::EC2::KeyPair::KeyName", "AWS::EC2::SecurityGroup::GroupName", "AWS::EC2::SecurityGroup::Id", "AWS::EC2::Subnet::Id", "AWS::EC2::Volume::Id", "AWS::EC2::VPC::Id", "AWS::Route53::HostedZone::Id", ], }, "MaxValue": {"Type": "Integer", "ValidForTypes": ["Number"]}, "MinLength": { "Type": "Integer", "ValidForTypes": [ "String", "AWS::EC2::AvailabilityZone::Name", "AWS::EC2::Image::Id", "AWS::EC2::Instance::Id", "AWS::EC2::KeyPair::KeyName", "AWS::EC2::SecurityGroup::GroupName", "AWS::EC2::SecurityGroup::Id", "AWS::EC2::Subnet::Id", "AWS::EC2::Volume::Id", "AWS::EC2::VPC::Id", "AWS::Route53::HostedZone::Id", ], }, "MinValue": {"Type": "Integer", "ValidForTypes": ["Number"]}, "NoEcho": {"Type": "Boolean"}, "Type": {"Type": "String"}, } required_keys = ["Type"] def check_type(self, value, path, props): """Check the type and handle recursion with lists""" results = [] prop_type = props.get("Type") if value is None: message = ( f'Property {"/".join(map(str, path))} should be of type {prop_type}' ) results.append(RuleMatch(path, message)) return results try: if prop_type in ["List"]: if isinstance(value, list): for i, item in enumerate(value): results.extend( self.check_type( item, path[:] + [i], {"Type": props.get("ItemType")} ) ) else: message = f'Property {"/".join(map(str, path))} should be of type {prop_type}' results.append(RuleMatch(path, message)) if prop_type in ["String"]: if isinstance(value, (dict, list)): message = f'Property {"/".join(map(str, path))} should be of type {prop_type}' results.append(RuleMatch(path, message)) str(value) elif prop_type in ["Boolean"]: if not isinstance(value, bool): if value not in ["True", "true", "False", "false"]: message = f'Property {"/".join(map(str, path))} should be of type {prop_type}' results.append(RuleMatch(path, message)) elif prop_type in ["Integer"]: if isinstance(value, bool): message = f'Property {"/".join(map(str, path))} should be of type {prop_type}' results.append(RuleMatch(path, message)) else: # has to be a Double int(value) except Exception: # pylint: disable=W0703 message = ( f'Property {"/".join(map(str, path))} should be of type {prop_type}' ) results.append( RuleMatch( path, message, ) ) return results def match(self, cfn): matches = [] for paramname, paramvalue in cfn.get_parameters().items(): if isinstance(paramvalue, dict): for propname, propvalue in paramvalue.items(): if propname not in self.valid_keys: message = "Parameter {0} has invalid property {1}" matches.append( RuleMatch( ["Parameters", paramname, propname], message.format(paramname, propname), ) ) else: props = self.valid_keys.get(propname) prop_path = ["Parameters", paramname, propname] matches.extend(self.check_type(propvalue, prop_path, props)) # Check that the property is needed for the current type valid_for = props.get("ValidForTypes") if valid_for is not None: if paramvalue.get("Type") not in valid_for: message = "Parameter {0} has property {1} which is only valid for {2}" matches.append( RuleMatch( ["Parameters", paramname, propname], message.format(paramname, propname, valid_for), ) ) for reqname in self.required_keys: if reqname not in paramvalue.keys(): message = "Parameter {0} is missing required property {1}" matches.append( RuleMatch( ["Parameters", paramname], message.format(paramname, reqname), ) ) else: message = "Parameter {0} is not an object" matches.append( RuleMatch( ["Parameters", paramname], message.format(paramname, reqname) ) ) return matches