示例#1
0
    def match(self, cfn):
        """Basic Rule Matching"""

        matches = []

        # Get a list of paths to every leaf node string containing at least one ${parameter}
        parameter_string_paths = self.match_values(cfn)
        # We want to search all of the paths to check if each one contains an 'Fn::Sub'
        for parameter_string_path in parameter_string_paths:
            if parameter_string_path[0] in ['Parameters']:
                continue
            # Exclude the special IAM variables
            variable = parameter_string_path[-1]

            if 'Resource' in parameter_string_path:
                if variable in self.resource_excludes:
                    continue
            if 'NotResource' in parameter_string_path:
                if variable in self.resource_excludes:
                    continue
            if 'Condition' in parameter_string_path:
                if variable in self.condition_excludes:
                    continue

            # Exclude variables that match custom exclude filters, if configured
            # (for third-party tools that pre-process templates before uploading them to AWS)
            if self._variable_custom_excluded(variable):
                continue

            # Exclude literals (https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/intrinsic-function-reference-sub.html)
            if variable.startswith('${!'):
                continue

            found_sub = False
            # Does the path contain an 'Fn::Sub'?
            for step in parameter_string_path:
                if step in self.api_excludes:
                    if self._api_exceptions(parameter_string_path[-1]):
                        found_sub = True
                elif step == 'Fn::Sub' or step in self.excludes:
                    found_sub = True

            # If we didn't find an 'Fn::Sub' it means a string containing a ${parameter} may not be evaluated correctly
            if not found_sub:
                # Remove the last item (the variable) to prevent multiple errors on 1 line errors
                path = parameter_string_path[:-1]
                message = 'Found an embedded parameter "{}" outside of an "Fn::Sub" at {}'.format(
                    variable, '/'.join(map(str, path)))
                matches.append(RuleMatch(path, message))

        return matches
示例#2
0
    def match(self, cfn):
        """Check CloudFormation Metadata Parameters Exist"""

        matches = []

        strinterface = 'AWS::CloudFormation::Interface'
        parameters = cfn.get_parameter_names()
        metadata_obj = cfn.template.get('Metadata', {})
        if metadata_obj:
            interfaces = metadata_obj.get(strinterface, {})
            if isinstance(interfaces, dict):
                # Check Parameter Group Parameters
                paramgroups = interfaces.get('ParameterGroups', [])
                if isinstance(paramgroups, list):
                    for index, value in enumerate(paramgroups):
                        if 'Parameters' in value:
                            for paramindex, paramvalue in enumerate(
                                    value['Parameters']):
                                if paramvalue not in parameters:
                                    message = 'Metadata Interface parameter doesn\'t exist {0}'
                                    matches.append(
                                        RuleMatch([
                                            'Metadata', strinterface,
                                            'ParameterGroups', index,
                                            'Parameters', paramindex
                                        ], message.format(paramvalue)))
                paramlabels = interfaces.get('ParameterLabels', {})
                if isinstance(paramlabels, dict):
                    for param in paramlabels:
                        if param not in parameters:
                            message = 'Metadata Interface parameter doesn\'t exist {0}'
                            matches.append(
                                RuleMatch([
                                    'Metadata', strinterface,
                                    'ParameterLabels', param
                                ], message.format(param)))

        return matches
示例#3
0
    def match(self, cfn: Template) -> List[RuleMatch]:
        matches = []

        resources = cfn.get_resources(["AWS::CloudFront::Distribution"])
        for resource_name, resource in resources.items():
            properties = resource.get("Properties", {})
            default_cache_behavior = properties.get(
                "DistributionConfig", {}).get("DefaultCacheBehavior", {})

            path = [
                "Resources",
                resource_name,
                "Properties",
                "DistributionConfig",
                "DefaultCacheBehavior",
            ]
            if not default_cache_behavior.get("ResponseHeadersPolicyId"):
                message = (
                    f"Property {'/'.join(path)}/ResponseHeadersPolicyId is missing"
                )
                matches.append(RuleMatch(path, message))

            for index, cache_behavior in enumerate(
                    properties.get("DistributionConfig",
                                   {}).get("CacheBehaviors", [])):
                path = [
                    "Resources",
                    resource_name,
                    "Properties",
                    "DistributionConfig",
                    "CacheBehaviors",
                    index,
                ]
                if not cache_behavior.get("ResponseHeadersPolicyId"):
                    message = f"Property ResponseHeadersPolicyId missing at {'/'.join([str(p) for p in path])}"
                    matches.append(RuleMatch(path, message))

        return matches
示例#4
0
    def check(self, properties, atleastoneprops, path, cfn):
        """Check itself"""
        matches = []

        for atleastoneprop in atleastoneprops:
            for (safe_properties, safe_path) in properties.items_safe(path):
                property_sets = cfn.get_object_without_conditions(
                    safe_properties, atleastoneprop)
                for property_set in property_sets:
                    count = 0
                    for prop in atleastoneprop:
                        if prop in property_set['Object']:
                            count += 1

                    if count == 0:
                        if property_set['Scenario'] is None:
                            message = 'At least one of [{0}] should be specified for {1}'
                            matches.append(
                                RuleMatch(
                                    path,
                                    message.format(
                                        ', '.join(map(str, atleastoneprop)),
                                        '/'.join(map(str, safe_path)))))
                        else:
                            scenario_text = ' and '.join([
                                'when condition "%s" is %s' % (k, v)
                                for (k, v) in property_set['Scenario'].items()
                            ])
                            message = 'At least one of [{0}] should be specified {1} at {2}'
                            matches.append(
                                RuleMatch(
                                    path,
                                    message.format(
                                        ', '.join(map(str, atleastoneprop)),
                                        scenario_text,
                                        '/'.join(map(str, safe_path)))))

        return matches
示例#5
0
    def check_attributes(self, cfn, properties, spec_type, path):
        """
            Check the properties against the spec
        """
        matches = []
        spec = self.valid_attributes.get('sub').get(spec_type)
        if isinstance(properties, dict):
            for p_value_safe, p_path_safe in properties.items_safe(path):
                for p_name, p_value in p_value_safe.items():
                    if p_name in spec:
                        up_type_spec = spec.get(p_name)
                        if 'Type' in up_type_spec:
                            matches.extend(
                                self.check_attributes(
                                    cfn, p_value, up_type_spec.get('Type'),
                                    p_path_safe[:] + [p_name]))
                        else:
                            matches.extend(
                                cfn.check_value(
                                    obj={p_name: p_value},
                                    key=p_name,
                                    path=p_path_safe[:],
                                    check_value=self.check_value,
                                    valid_values=up_type_spec.get(
                                        'ValidValues', []),
                                    primitive_type=up_type_spec.get(
                                        'PrimitiveType'),
                                    key_name=p_name))
                    else:
                        message = 'UpdatePolicy doesn\'t support type {0}'
                        matches.append(
                            RuleMatch(path[:] + [p_name],
                                      message.format(p_name)))
        else:
            message = '{0} should be an object'
            matches.append(RuleMatch(path, message.format(path[-1])))

        return matches
示例#6
0
    def match(self, cfn):
        """Find all strings and match a deny list of sub strings"""
        message = '"{0}" may be interpreted as a biased term. Consider a more inclusive alternative, such as {1}'
        matches = []

        def recurse_template(item, path=None):
            if path is None:
                path = []
            if isinstance(item, dict):
                for k, v in item.items():
                    p = path.copy()
                    p.append(k)
                    recurse_template(k, p)
                    recurse_template(v, p)
            if isinstance(item, list):
                for i in range(len(item) - 1):
                    p = path.copy()
                    p.append(i)
                    recurse_template(item[i], p)
            if isinstance(item, str):
                t = match(item)
                if t:
                    matches.append(RuleMatch(path, message.format(t[0], t[1])))
            return matches

        recurse_template(cfn.template)
        return matches

        if self.id in cfn.template.get("Metadata",
                                       {}).get("QSLint",
                                               {}).get("Exclusions", []):
            return matches
        if "Metadata" in cfn.template.keys():
            if "AWS::CloudFormation::Interface" in cfn.template[
                    "Metadata"].keys():
                if "ParameterGroups" in cfn.template["Metadata"][
                        "AWS::CloudFormation::Interface"].keys():
                    for x in cfn.template["Metadata"][
                            "AWS::CloudFormation::Interface"][
                                "ParameterGroups"]:
                        labels += x['Parameters']

        if "Parameters" not in cfn.template.keys():
            return matches
        else:
            for x in cfn.template["Parameters"]:
                if str(x) not in labels:
                    matches.append(
                        RuleMatch(["Parameters", x], message.format(x)))
        return matches
示例#7
0
    def match(self, cfn):
        """Check CloudFormation Resources"""

        matches = []

        resources = cfn.template.get('Resources', {})

        for resource_name in resources:
            path = ['Resources', resource_name]
            if len(resource_name) > LIMITS['resources']['name']:
                message = 'The length of resource name ({0}) exceeds the limit ({1})'
                matches.append(RuleMatch(path, message.format(len(resource_name), LIMITS['resources']['name'])))

        return matches
示例#8
0
    def check_ref(self, value, parameters, resources, path):  # pylint: disable=W0613
        """ Check Ref """
        matches = []

        iam_path = resources.get(value, {}).get('Properties', {}).get('Path')
        if not iam_path:
            return matches

        if iam_path != '/':
            message = 'When using a Ref to IAM resource the Path must be \'/\'.  Switch to GetAtt if the Path has to be \'{}\'.'
            matches.append(
                RuleMatch(path, message.format(iam_path)))

        return matches
示例#9
0
 def check_az_ref(self, value, path, parameters, resources):
     """Check ref for AZ"""
     matches = []
     allowed_types = [
         'AWS::EC2::AvailabilityZone::Name', 'String',
         'AWS::SSM::Parameter::Value<AWS::EC2::AvailabilityZone::Name>'
     ]
     if value in resources:
         message = 'AvailabilityZone can\'t use a Ref to a resource for {0}'
         matches.append(
             RuleMatch(path, message.format(('/'.join(map(str, path))))))
     elif value in parameters:
         parameter = parameters.get(value, {})
         param_type = parameter.get('Type', '')
         if param_type not in allowed_types:
             param_path = ['Parameters', value, 'Type']
             message = 'Availability Zone should be of type [{0}] for {1}'
             matches.append(
                 RuleMatch(
                     param_path,
                     message.format(', '.join(map(str, allowed_types)),
                                    '/'.join(map(str, param_path)))))
     return matches
示例#10
0
    def match(self, cfn):
        """Check CloudFormation Mapping"""

        matches = []

        resources = cfn.template.get('Resources', {})
        for resource_name, _ in resources.items():
            if not re.match(REGEX_ALPHANUMERIC, resource_name):
                message = 'Resources {0} has invalid name.  Name has to be alphanumeric.'
                matches.append(
                    RuleMatch(['Resources', resource_name],
                              message.format(resource_name)))

        return matches
示例#11
0
    def check_obj(self, obj, required_attributes, path, _):
        matches = []

        for safe_obj, safe_path in obj.items_safe(path):
            for required_attribute in required_attributes:
                if required_attribute not in safe_obj:
                    message = 'Property {0} missing at {1}'
                    matches.append(
                        RuleMatch(
                            safe_path,
                            message.format(required_attribute,
                                           '/'.join(map(str, safe_path)))))

        return matches
示例#12
0
    def check_value(self, value, path):
        """Count ScheduledExpression value"""
        matches = []

        # Value is either "cron()" or "rate()"
        if value.startswith('rate(') and value.endswith(')'):
            matches.extend(self.check_rate(value, path))
        elif value.startswith('cron(') and value.endswith(')'):
            matches.extend(self.check_cron(value, path))
        else:
            message = 'Invalid ScheduledExpression specified ({}). Value has to be either cron() or rate()'
            matches.append(RuleMatch(path, message.format(value)))

        return matches
示例#13
0
    def match(self, cfn):
        """Check CloudFormation Outputs"""

        matches = []

        outputs = cfn.template.get('Outputs', {})

        for output_name in outputs:
            path = ['Outputs', output_name]
            if len(output_name) > LIMITS['outputs']['name']:
                message = 'The length of output name ({0}) exceeds the limit ({1})'
                matches.append(RuleMatch(path, message.format(len(output_name), LIMITS['outputs']['name'])))

        return matches
示例#14
0
    def match(self, cfn):
        """
        Match against Lambda functions without an explicity Timeout
        """

        matches = []

        for key, value in cfn.get_resources(["AWS::Lambda::Function"]).items():
            timeout = value.get("Properties", {}).get("Timeout", None)

            if timeout is None:
                matches.append(RuleMatch(["Resources", key], self._message.format(key)))

        return matches
示例#15
0
    def match(self, cfn):
        """
        Match against Lambda functions without tracing enabled
        """

        matches = []

        for key, value in cfn.get_resources(["AWS::Lambda::Function"]).items():
            tracing_mode = value.get("Properties", {}).get("TracingConfig", {}).get("Mode", None)

            if tracing_mode != "Active":
                matches.append(RuleMatch(["Resources", key], self._message.format(key)))

        return matches
示例#16
0
    def match(self, cfn):
        """Check CloudFormation Conditions"""

        matches = []

        conditions = cfn.template.get('Conditions', {})
        if conditions:
            for condname, condobj in conditions.items():
                if not isinstance(condobj, dict):
                    message = 'Condition {0} has invalid property'
                    matches.append(RuleMatch(
                        ['Conditions', condname],
                        message.format(condname)
                    ))
                else:
                    if len(condobj) != 1:
                        message = 'Condition {0} has to many intrinsic conditions'
                        matches.append(RuleMatch(
                            ['Conditions', condname],
                            message.format(condname)
                        ))

        return matches
    def check_rate(self, value, path):
        """Check Rate configuration"""
        matches = []
        # Extract the expression from rate(XXX)
        rate_expression = value[value.find('(')+1:value.find(')')]

        if not rate_expression:
            matches.append(RuleMatch(path, 'Rate value of ScheduleExpression cannot be empty'))
        else:
            # Rate format: rate(Value Unit)
            items = rate_expression.split(' ')

            if len(items) != 2:
                message = 'Rate expression must contain 2 elements (Value Unit), rate contains {} elements'
                matches.append(RuleMatch(path, message.format(len(items))))
            else:
                # Check the Value
                if not items[0].isdigit():
                    message = 'Rate Value ({}) should be of type Integer.'
                    extra_args = {'actual_type': type(items[0]).__name__, 'expected_type': int.__name__}
                    matches.append(RuleMatch(path, message.format(items[0]), **extra_args))

        return matches
示例#18
0
    def match(self, cfn):
        """Check CloudFormation GetAtt"""

        matches = []

        fnnots = cfn.search_deep_keys('Fn::Not')
        for fnnot in fnnots:
            if not isinstance(fnnot[-1], list):
                message = 'Function Not {0} should be a list'
                matches.append(
                    RuleMatch(fnnot,
                              message.format('/'.join(map(str, fnnot[:-2])))))

        return matches
示例#19
0
    def match(self, cfn):
        """Check CloudFormation Parameters"""

        matches = []

        for paramname, paramvalue in cfn.get_parameters().items():
            for propname, _ 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)))

        return matches
    def match(self, cfn):
        """Check CloudFormation Mappings"""

        matches = []
        pattern = re.compile("^([m][A-Z_0-9]+[a-zA-Z0-9]*)+$")
        mappings = cfn.template.get('Mappings', {})
        if mappings:
            for mappingname, val in mappings.items():
                if pattern.match(mappingname):
                    message = 'Mapping {0} should begin with a lowercase \'m\' and follow mCamelCase'
                    matches.append(
                        RuleMatch(['Mappings', mappingname],
                                  message.format(mappingname)))
        return matches
示例#21
0
    def match(self, cfn):
        """Check CloudFormation Outputs"""

        matches = []

        outputs = cfn.template.get('Outputs', {})
        if outputs:
            for output_name, output_value in outputs.items():
                if 'Value' not in output_value:
                    message = 'Output {0} is missing property {1}'
                    matches.append(RuleMatch(
                        ['Outputs', output_name, 'Value'],
                        message.format(output_name, 'Value')
                    ))
                if 'Export' in output_value:
                    if 'Name' not in output_value['Export']:
                        message = 'Output {0} is missing property {1}'
                        matches.append(RuleMatch(
                            ['Outputs', output_name, 'Export'],
                            message.format(output_name, 'Name')
                        ))

        return matches
示例#22
0
 def check_metadata_keys(self, cfn):
     """ Ensure reserved metadata key AWS::CloudFormation::Module is not used """
     modules = cfn.get_modules().keys()
     matches = []
     reserved_key = 'AWS::CloudFormation::Module'
     refs = cfn.search_deep_keys(reserved_key)
     for ref in refs:
         if (ref[1] in modules) and (len(ref) > 3):
             if ref[0] == 'Resources' and ref[2] == 'Metadata':
                 matches.append(
                     RuleMatch(
                         ref, 'The Metadata key {} is reserved'.format(
                             reserved_key)))
     return matches
    def check_version(self, action, path):
        """Check that action type version is valid."""
        matches = []

        REGEX_VERSION_STRING = re.compile(r'^[0-9A-Za-z_-]+$')
        LENGTH_MIN = 1
        LENGTH_MAX = 9

        version = action.get('ActionTypeId', {}).get('Version')
        if isinstance(version, dict):
            self.logger.debug(
                'Unable to validate version when an object is used.  Skipping')
        elif isinstance(version, (six.string_types)):
            if not LENGTH_MIN <= len(version) <= LENGTH_MAX:
                message = 'Version string ({0}) must be between {1} and {2} characters in length.'
                matches.append(
                    RuleMatch(path + ['ActionTypeId', 'Version'],
                              message.format(version, LENGTH_MIN, LENGTH_MAX)))
            elif not re.match(REGEX_VERSION_STRING, version):
                message = 'Version string must match the pattern [0-9A-Za-z_-]+.'
                matches.append(
                    RuleMatch(path + ['ActionTypeId', 'Version'], message))
        return matches
    def match(self, cfn):
        """Check CloudFormation Parameters"""

        matches = []
        pattern = re.compile("^([p][A-Z_0-9]+[a-zA-Z0-9]*)+$")
        parameters = cfn.template.get('Parameters', {})
        if parameters:
            for paramname, val in parameters.items():
                if not pattern.match(paramname):
                    message = 'Parameters {0} should begin with a lowercase \'p\' and follow pCamelCase'
                    matches.append(
                        RuleMatch(['Parameters', paramname],
                                  message.format(paramname)))
        return matches
示例#25
0
    def match(self, cfn):
        """Check CloudFormation Resources"""

        matches = []

        graph = Graph(cfn)
        for cycle in graph.get_cycles(cfn):
            source, target = cycle[:2]
            message = 'Circular Dependencies for resource {0}. Circular dependency with [{1}]'.format(
                source, target)
            path = ['Resources', source]
            matches.append(RuleMatch(path, message))

        return matches
示例#26
0
 def match(self, cfn):
     """Basic Matching"""
     matches = []
     # Only check if the file exists. The template could be passed in using stdIn
     if cfn.filename:
         if Path(cfn.filename).is_file():
             statinfo = os.stat(cfn.filename)
             if statinfo.st_size > LIMITS['template']['body']:
                 message = 'The template file size ({0} bytes) exceeds the limit ({1} bytes)'
                 matches.append(
                     RuleMatch(['Template'],
                               message.format(statinfo.st_size,
                                              LIMITS['template']['body'])))
     return matches
示例#27
0
    def match(self, cfn):
        matches = []

        ref_objs = cfn.search_deep_keys('Ref')
        for ref_obj in ref_objs:
            value = ref_obj[-1]
            if not isinstance(value, (six.string_types)):
                message = 'Ref can only be a string for {0}'
                matches.append(
                    RuleMatch(ref_obj[:-1],
                              message.format('/'.join(map(str,
                                                          ref_obj[:-1])))))

        return matches
示例#28
0
    def match(self, cfn):
        """Check CloudFormation Mapping"""

        matches = []

        parameters = cfn.template.get('Parameters', {})
        for parameter_name, _ in parameters.items():
            if not re.match(REGEX_ALPHANUMERIC, parameter_name):
                message = 'Parameter {0} has invalid name.  Name has to be alphanumeric.'
                matches.append(
                    RuleMatch(['Parameters', parameter_name],
                              message.format(parameter_name)))

        return matches
示例#29
0
    def check_allowed_pattern(self, allowed_value, allowed_pattern, path):
        """
            Check allowed value against allowed pattern
        """
        message = 'Default should be allowed by AllowedPattern'
        try:
            if not re.match(allowed_pattern, str(allowed_value)):
                return ([RuleMatch(path, message)])
        except re.error as ex:
            self.logger.debug(
                'Regex pattern "%s" isn\'t supported by Python: %s',
                allowed_pattern, ex)

        return []
示例#30
0
 def findComplementOfLists(listA, listB, message):
     matches = []
     for listAElement in listA:
         logGroupName = listAElement[1]['Properties']['LogGroupName']
         isFound = False
         for listBElement in listB:
             if listBElement[1]['Properties'][
                     'LogGroupName'] == logGroupName:
                 isFound = True
                 break
         if not isFound:
             path = ['Resources', listAElement[0]]
             matches.append(RuleMatch(path, message))
     return matches