示例#1
0
    def check_allowed_pattern(self, allowed_value, allowed_pattern, path):
        """
            Check allowed value against allowed pattern
        """
        message = 'Default should be allowed by AllowedPattern'

        if not re.match(allowed_pattern, allowed_value):
            return ([RuleMatch(path, message)])

        return []
示例#2
0
    def check_allowed_values(self, allowed_value, allowed_values, path):
        """
            Check allowed value against allowed values
        """
        message = 'Default should be a value within AllowedValues'

        if allowed_value not in allowed_values:
            return ([RuleMatch(path, message)])

        return []
示例#3
0
    def check_az_value(self, value, path):
        """Check ref for VPC"""
        matches = list()

        if path[-1] != 'Fn::GetAZs':
            message = 'Don\'t hardcode {0} for AvailabilityZones at {1}'
            full_path = ('/'.join(str(x) for x in path))
            matches.append(RuleMatch(path, message.format(value, full_path)))

        return matches
示例#4
0
    def check_version(self, action, path):
        """Check that action type version is valid."""
        matches = []

        if action.get('ActionTypeId').get('Version') != '1':
            message = 'For all currently supported action types, the only valid version string is "1".'
            matches.append(
                RuleMatch(path + ['ActionTypeId', 'Version'], message))

        return matches
示例#5
0
 def check_az_ref(self, value, path, parameters, resources):
     """Check ref for AZ"""
     matches = []
     allowed_types = ['AWS::EC2::AvailabilityZone::Name', 'String']
     if value in resources:
         message = 'AvailabilityZone can\'t use a Ref to a resource for {0}'
         matches.append(RuleMatch(path, message.format(('/'.join(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
示例#6
0
    def check_value(self, value, path):
        """ Check runtime value """
        matches = list()

        message = 'You must specify a valid value for runtime at {0}'

        if value not in self.runtimes:
            matches.append(RuleMatch(path, message.format(value, ('/'.join(path)))))

        return matches
    def check_first_stage(self, stages, path):
        """Validate the first stage of a pipeline has source actions."""
        matches = []

        if len(stages) < 1:  # pylint: disable=C1801
            self.logger.debug('Stages was empty. Should have been caught by generic linting.')
            return matches

        # pylint: disable=R1718
        first_stage = set([a.get('ActionTypeId').get('Category') for a in stages[0]['Actions']])
        if first_stage and 'Source' not in first_stage:
            message = 'The first stage of a pipeline must contain at least one source action.'
            matches.append(RuleMatch(path + [0], message))

        if len(first_stage) != 1:
            message = 'The first stage of a pipeline must contain only source actions.'
            matches.append(RuleMatch(path + [0], message))

        return matches
    def check_cron(self, value, path):
        """Check Cron configuration"""
        matches = []
        # Extract the expression from cron(XXX)
        cron_expression = value[value.find('(') + 1:value.find(')')]

        if not cron_expression:
            matches.append(
                RuleMatch(path,
                          'Cron value of ScheduleExpression cannot be empty'))
        else:
            # Rate format: cron(Minutes Hours Day-of-month Month Day-of-week Year)
            items = cron_expression.split(' ')

            if len(items) != 6:
                message = 'Cron expression must contain 6 elements (Minutes Hours Day-of-month Month Day-of-week Year), cron contains {} elements'
                matches.append(RuleMatch(path, message.format(len(items))))

        return matches
    def check_value(self, value, path, **kwargs):
        """ Check a primitive value """
        matches = []

        prim_type = kwargs.get('primitive_type')
        if prim_type == 'List':
            valid_values = kwargs.get('valid_values')
            if valid_values:
                if value not in valid_values:
                    message = 'Allowed values for {0} are ({1})'
                    matches.append(
                        RuleMatch(path, message.format(kwargs.get('key_name'), ', '.join(map(str, valid_values)))))
        else:
            default_message = 'Value for {0} must be of type {1}'
            if not isinstance(value, self.valid_attributes.get('primitive_types').get(prim_type)):
                matches.append(
                    RuleMatch(path, default_message.format(kwargs.get('key_name'), prim_type)))

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

        matches = list()

        getaz_objs = cfn.search_deep_keys('Fn::GetAZs')

        for getaz_obj in getaz_objs:
            getaz_value = getaz_obj[-1]
            if isinstance(getaz_value, six.string_types):
                if getaz_value != '' and getaz_value not in cfn.regions:
                    message = 'GetAZs should be of empty or string of valid region for {0}'
                    matches.append(
                        RuleMatch(
                            getaz_obj[:-1],
                            message.format('/'.join(map(str,
                                                        getaz_obj[:-1])))))
            elif isinstance(getaz_value, dict):
                if len(getaz_value) == 1:
                    if isinstance(getaz_value, dict):
                        for key, value in getaz_value.items():
                            if key != 'Ref' or value != 'AWS::Region':
                                message = 'GetAZs should be of Ref to AWS::Region for {0}'
                                matches.append(
                                    RuleMatch(
                                        getaz_obj[:-1],
                                        message.format('/'.join(
                                            map(str, getaz_obj[:-1])))))
                    else:
                        message = 'GetAZs should be of Ref to AWS::Region for {0}'
                        matches.append(
                            RuleMatch(
                                getaz_obj[:-1],
                                message.format('/'.join(
                                    map(str, getaz_obj[:-1])))))
                else:
                    message = 'GetAZs should be of Ref to AWS::Region for {0}'
                    matches.append(
                        RuleMatch(
                            getaz_obj[:-1],
                            message.format('/'.join(map(str,
                                                        getaz_obj[:-1])))))
        return matches
示例#11
0
    def check_txt_record(self, path, recordset):
        """Check TXT record Configuration"""
        matches = list()

        # Check quotation of the records
        resource_records = recordset.get('ResourceRecords')

        for index, record in enumerate(resource_records):
            tree = path[:] + ['ResourceRecords', index]
            full_path = ('/'.join(str(x) for x in tree))

            if not record.startswith('"') or not record.endswith('"'):
                message = 'TXT record has to be enclosed in double quotation marks (") at {0}'
                matches.append(RuleMatch(tree, message.format(full_path)))
            elif len(record) > 255:
                message = 'The length of the TXT record ({0}) exceeds the limit (255) as {1}'
                matches.append(RuleMatch(tree, message.format(len(record), full_path)))

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

        matches = []

        # Build the list of functions
        equal_trees = cfn.search_deep_keys('Fn::And')

        for equal_tree in equal_trees:
            equal = equal_tree[-1]
            if not isinstance(equal, list):
                message = 'Fn::And must be a list of between 2 to 10 conditions'
                matches.append(RuleMatch(equal_tree[:-1], message.format()))
            elif not (2 <= len(equal) <= 10):
                message = 'Fn::And must be a list of between 2 to 10 conditions'
                matches.append(RuleMatch(equal_tree[:-1], message.format()))
            else:
                for index, element in enumerate(equal):
                    if isinstance(element, dict):
                        if len(element) == 1:
                            for element_key in element.keys():
                                if element_key not in [
                                        'Fn::And', 'Fn::Or', 'Fn::Not',
                                        'Condition', 'Fn::Equals'
                                ]:
                                    message = 'Fn::And list must be another valid condition'
                                    matches.append(
                                        RuleMatch(
                                            equal_tree[:-1] +
                                            [index, element_key],
                                            message.format()))
                        else:
                            message = 'Fn::And list must be another valid condition'
                            matches.append(
                                RuleMatch(equal_tree[:-1] + [index],
                                          message.format()))
                    else:
                        message = 'Fn::And list must be another valid condition'
                        matches.append(
                            RuleMatch(equal_tree[:-1] + [index],
                                      message.format()))

        return matches
示例#13
0
    def check_az_value(self, value, path):
        """Check AZ Values"""
        matches = []

        if value not in AVAILABILITY_ZONES:
            message = 'Not a valid Availbility Zone {0} at {1}'
            matches.append(
                RuleMatch(path,
                          message.format(value, ('/'.join(map(str, path))))))
        return matches
示例#14
0
    def check_vpc_value(self, value, path):
        """Check VPC Values"""
        matches = []

        if not value.startswith('vpc-'):
            message = 'VpcId needs to be of format vpc-xxxxxxxx at {1}'
            matches.append(
                RuleMatch(path,
                          message.format(value, ('/'.join(map(str, path))))))
        return matches
示例#15
0
    def check_ns_record(self, value, path):
        """Check NS record Configuration"""
        matches = []

        if not isinstance(value, dict):
            if not re.match(self.REGEX_DOMAINNAME, value):
                message = 'NS record ({}) does not contain a valid domain name'
                matches.append(RuleMatch(path, message.format(value)))

        return matches
    def check_ref(self, value, path, parameters, resources):
        """ Check Memory Size Ref """

        matches = list()
        if value in resources:
            message = 'Runtime can\'t use a Ref to a resource for {0}'
            matches.append(RuleMatch(path, message.format(('/'.join(path)))))
        elif value in parameters:
            parameter = parameters.get(value, {})
            param_type = parameter.get('Type', '')

            if param_type != 'String':
                param_path = ['Parameters', value, 'Type']
                message = 'Type for Parameter should be String at {0}'
                matches.append(
                    RuleMatch(param_path, message.format(
                        ('/'.join(param_path)))))

        return matches
示例#17
0
    def check_a_record(self, value, path):
        """Check A record Configuration"""
        matches = []

        # Check if a valid IPv4 address is specified
        if not re.match(REGEX_IPV4, value):
            message = 'A record ({}) is not a valid IPv4 address'
            matches.append(RuleMatch(path, message.format(value)))

        return matches
    def check_value(self, value, path):
        """Check SecurityGroup descriptions"""
        matches = list()
        full_path = ('/'.join(str(x) for x in path))

        # Check max length
        if len(value) > 255:
            message = 'GroupDescription length ({0}) exceeds the limit (255) at {1}'
            matches.append(
                RuleMatch(path, message.format(len(value), full_path)))
        else:
            # Check valid characters
            regex = re.compile(self.description_regex)
            if not regex.match(value):
                message = 'GroupDescription contains invalid characters (valid characters are: '\
                          '"a-zA-Z0-9. _-:/()#,@[]+=&;\\{{}}!$*"") at {0}'
                matches.append(RuleMatch(path, message.format(full_path)))

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

        matches = []

        # Build the list of functions
        not_trees = cfn.search_deep_keys('Fn::Not')

        for not_tree in not_trees:
            not_value = not_tree[-1]
            if not isinstance(not_value, list):
                message = 'Fn::Not must be a list of exactly 1 condition'
                matches.append(RuleMatch(not_tree[:-1], message.format()))
            elif not len(not_value) == 1:
                message = 'Fn::Not must be a list of exactly 1 condition'
                matches.append(RuleMatch(not_tree[:-1], message.format()))
            else:
                for index, element in enumerate(not_value):
                    if isinstance(element, dict):
                        if len(element) == 1:
                            for element_key in element.keys():
                                if element_key not in [
                                        'Fn::And', 'Fn::Or', 'Fn::Not',
                                        'Condition', 'Fn::Equals'
                                ]:
                                    message = 'Fn::Or list must be another valid condition'
                                    matches.append(
                                        RuleMatch(
                                            not_tree[:-1] +
                                            [index, element_key],
                                            message.format()))
                        else:
                            message = 'Fn::Or list must be another valid condition'
                            matches.append(
                                RuleMatch(not_tree[:-1] + [index],
                                          message.format()))
                    else:
                        message = 'Fn::Or list must be another valid condition'
                        matches.append(
                            RuleMatch(not_tree[:-1] + [index],
                                      message.format()))

        return matches
示例#20
0
    def check_cidr_value(self, value, path):
        """Check CIDR Strings"""
        matches = []

        if not re.match(REGEX_CIDR, value):
            message = 'CidrBlock needs to be of x.x.x.x/y at {0}'
            matches.append(
                RuleMatch(path,
                          message.format(('/'.join(['Parameters', value])))))
        return matches
示例#21
0
    def match(self, cfn):
        """Check CloudFormation IamInstanceProfile Parameters"""

        matches = list()

        # Build the list of keys
        trees = cfn.search_deep_keys('Fn::GetAtt')
        # Filter only resoureces
        # Disable pylint for Pylint 2
        # pylint: disable=W0110
        trees = filter(lambda x: x[0] == 'Resources', trees)
        for tree in trees:
            if any(e == 'IamInstanceProfile' for e in tree):
                obj = tree[-1]
                objtype = cfn.template.get('Resources', {}).get(obj[0],
                                                                {}).get('Type')
                if objtype:
                    if objtype != 'AWS::IAM::InstanceProfile':
                        message = 'Property IamInstanceProfile should relate to AWS::IAM::InstanceProfile for %s' % (
                            '/'.join(map(str, tree[:-1])))
                        matches.append(RuleMatch(tree[:-1], message))
                    else:
                        if obj[1] == 'Arn':
                            message = 'Property IamInstanceProfile shouldn\'t be an ARN for %s' % (
                                '/'.join(map(str, tree[:-1])))
                            matches.append(RuleMatch(tree[:-1], message))

        # Search Refs
        trees = cfn.search_deep_keys('Ref')
        # Filter only resoureces
        trees = filter(lambda x: x[0] == 'Resources', trees)
        for tree in trees:
            if any(e == 'IamInstanceProfile' for e in tree):
                obj = tree[-1]
                objtype = cfn.template.get('Resources', {}).get(obj,
                                                                {}).get('Type')
                if objtype:
                    if objtype != 'AWS::IAM::InstanceProfile':
                        message = 'Property IamInstanceProfile should relate to AWS::IAM::InstanceProfile for %s' % (
                            '/'.join(map(str, tree[:-1])))
                        matches.append(RuleMatch(tree[:-1], message))

        return matches
    def match(self, cfn):
        """Check EC2 Security Group Ingress Resource Parameters"""

        matches = list()

        resources = cfn.get_resources(resource_type='AWS::EC2::SecurityGroup')
        for resource_name, resource_object in resources.items():
            properties = resource_object.get('Properties', {})
            if properties:
                vpc_id = properties.get('VpcId', None)
                ingress_rules = properties.get('SecurityGroupIngress')
                if isinstance(ingress_rules, list):
                    for index, ingress_rule in enumerate(ingress_rules):
                        path = [
                            'Resources', resource_name, 'Properties',
                            'SecurityGroupIngress', index
                        ]
                        matches.extend(
                            self.check_ingress_rule(
                                vpc_id=vpc_id,
                                properties=ingress_rule,
                                path=path,
                                cfn=cfn
                            )
                        )

        resources = None
        resources = cfn.get_resources(resource_type='AWS::EC2::SecurityGroupIngress')
        for resource_name, resource_object in resources.items():
            properties = resource_object.get('Properties', {})
            group_id = properties.get('GroupId', None)
            group_name = properties.get('GroupName', None)
            path = ['Resources', resource_name, 'Properties']
            if group_id and not group_name:
                vpc_id = 'vpc-1234567'
            elif group_name and not group_id:
                vpc_id = None
            else:
                message = "GroupId and GroupName shouldn't be specified together " \
                          "at {0}"
                matches.append(
                    RuleMatch(path, message.format('/'.join(map(str, path)))))
                continue

            if properties:
                path = ['Resources', resource_name, 'Properties']
                matches.extend(
                    self.check_ingress_rule(
                        vpc_id=vpc_id,
                        properties=properties,
                        path=path,
                        cfn=cfn
                    )
                )
        return matches
示例#23
0
    def _check_number_value(self, value, path, **kwargs):
        """ Check if the value is in the given ranges"""
        matches = []
        number_min = kwargs.get('number_min')
        number_max = kwargs.get('number_max')

        # The Python types considered a "number"
        if sys.version_info < (3, ):
            number_types = (
                float,
                int,
                long,
            )  # pylint: disable=undefined-variable
        else:
            number_types = (
                float,
                int,
            )

        if isinstance(value, six.string_types):
            try:
                value = float(value)
            except ValueError:
                message = 'Value has to be between {0} and {1} at {2}'
                matches.append(
                    RuleMatch(
                        path,
                        message.format(number_min, number_max,
                                       '/'.join(map(str, path))),
                    ))

        if isinstance(value, number_types):
            if not (number_min <= value <= number_max):
                message = 'Value has to be between {0} and {1} at {2}'
                matches.append(
                    RuleMatch(
                        path,
                        message.format(number_min, number_max,
                                       '/'.join(map(str, path))),
                    ))

        return matches
示例#24
0
    def check_caa_record(self, path, recordset):
        """Check CAA record Configuration"""
        matches = []

        resource_records = recordset.get('ResourceRecords')

        for index, record in enumerate(resource_records):
            tree = path[:] + ['ResourceRecords', index]

            if not isinstance(record, dict):
                # Split the record up to the mandatory settings (flags tag "value")
                items = record.split(' ', 2)

                # Check if the 3 settings are given.
                if len(items) != 3:
                    message = 'CAA record must contain 3 settings (flags tag "value"), record contains {} settings.'
                    matches.append(RuleMatch(tree, message.format(len(items))))
                else:
                    # Check the flag value
                    if not items[0].isdigit():
                        message = 'CAA record flag setting ({}) should be of type Integer.'
                        matches.append(
                            RuleMatch(tree, message.format(items[0])))
                    else:
                        if int(items[0]) not in [0, 128]:
                            message = 'Invalid CAA record flag setting ({}) given, must be 0 or 128.'
                            matches.append(
                                RuleMatch(tree, message.format(items[0])))

                    # Check the tag value
                    if not re.match(REGEX_ALPHANUMERIC, items[1]):
                        message = 'Invalid CAA record tag setting {}. Value has to be alphanumeric.'
                        matches.append(
                            RuleMatch(tree, message.format(items[0])))

                    # Check the value
                    if not items[2].startswith('"') or not items[2].endswith(
                            '"'):
                        message = 'CAA record value setting has to be enclosed in double quotation marks (").'
                        matches.append(RuleMatch(tree, message))

        return matches
示例#25
0
    def _value_check(self, value, path, item_type, extra_args):
        """ Checks non strict """
        matches = []
        if not self.config['strict']:
            try:
                if item_type in ['String']:
                    str(value)
                elif item_type in ['Boolean']:
                    if value not in ['True', 'true', 'False', 'false']:
                        message = 'Property %s should be of type %s' % (
                            '/'.join(map(str, path)), item_type)
                        matches.append(RuleMatch(path, message, **extra_args))
                elif item_type in ['Integer', 'Long', 'Double']:
                    if isinstance(value, bool):
                        message = 'Property %s should be of type %s' % (
                            '/'.join(map(str, path)), item_type)
                        matches.append(RuleMatch(path, message, **extra_args))
                    elif item_type in ['Integer']:
                        int(value)
                    elif item_type in ['Long']:
                        # Some times python will strip the decimals when doing a conversion
                        if isinstance(value, float):
                            message = 'Property %s should be of type %s' % (
                                '/'.join(map(str, path)), item_type)
                            matches.append(
                                RuleMatch(path, message, **extra_args))
                        if sys.version_info < (3, ):
                            long(value)  # pylint: disable=undefined-variable
                        else:
                            int(value)
                    else:  # has to be a Double
                        float(value)
            except Exception:  # pylint: disable=W0703
                message = 'Property %s should be of type %s' % ('/'.join(
                    map(str, path)), item_type)
                matches.append(RuleMatch(path, message, **extra_args))
        else:
            message = 'Property %s should be of type %s' % ('/'.join(
                map(str, path)), item_type)
            matches.append(RuleMatch(path, message, **extra_args))

        return matches
示例#26
0
    def check_policy_document(self, value, path, cfn, is_identity_policy,
                              resource_exceptions):
        """Check policy document"""
        matches = []

        valid_keys = [
            'Version',
            'Id',
            'Statement',
        ]
        valid_versions = [
            '2012-10-17', '2008-10-17',
            date(2012, 10, 17),
            date(2008, 10, 17)
        ]

        if not isinstance(value, dict):
            message = 'IAM Policy Documents needs to be JSON'
            matches.append(RuleMatch(path[:], message))
            return matches

        for parent_key, parent_value in value.items():
            if parent_key not in valid_keys:
                message = 'IAM Policy key %s doesn\'t exist.' % (parent_key)
                matches.append(RuleMatch(path[:] + [parent_key], message))
            if parent_key == 'Version':
                if parent_value not in valid_versions:
                    message = 'IAM Policy Version needs to be one of (%s).' % (
                        ', '.join(map(str, ['2012-10-17', '2008-10-17'])))
                    matches.append(RuleMatch(path[:] + [parent_key], message))
            if parent_key == 'Statement':
                if isinstance(parent_value, (list)):
                    statements = cfn.get_values(value, 'Statement', path[:])
                    for statement in statements:
                        matches.extend(
                            self._check_policy_statement(
                                statement['Path'], statement['Value'],
                                is_identity_policy, resource_exceptions))
                else:
                    message = 'IAM Policy statement should be of list.'
                    matches.append(RuleMatch(path[:] + [parent_key], message))
        return matches
示例#27
0
    def check_keys(self, map_name, keys, mappings, tree):
        """ Check the validity of the first key """
        matches = []
        first_key = keys[0]
        second_key = keys[1]
        if isinstance(second_key, (six.string_types, int)):
            if isinstance(map_name, (six.string_types)):
                mapping = mappings.get(map_name)
                if mapping:
                    if isinstance(first_key, (six.string_types, int)):
                        if isinstance(map_name, (six.string_types)):
                            if mapping.get(first_key) is None:
                                message = 'FindInMap first key "{0}" doesn\'t exist in map "{1}" at {3}'
                                matches.append(
                                    RuleMatch(
                                        tree[:] + [1],
                                        message.format(
                                            first_key, map_name, first_key,
                                            '/'.join(map(str, tree)))))
                        if mapping.get(first_key):
                            # Don't double error if they first key doesn't exist
                            if mapping.get(first_key,
                                           {}).get(second_key) is None:
                                message = 'FindInMap second key "{0}" doesn\'t exist in map "{1}" under "{2}" at {3}'
                                matches.append(
                                    RuleMatch(
                                        tree[:] + [2],
                                        message.format(
                                            second_key, map_name, first_key,
                                            '/'.join(map(str, tree)))))
                    else:
                        for key, value in mapping.items():
                            if value.get(second_key) is None:
                                message = 'FindInMap second key "{0}" doesn\'t exist in map "{1}" under "{2}" at {3}'
                                matches.append(
                                    RuleMatch(
                                        tree[:] + [2],
                                        message.format(
                                            second_key, map_name, key,
                                            '/'.join(map(str, tree)))))

        return matches
    def check_stage_count(self, stages, path, scenario):
        """Check that there is minimum 2 stages."""
        matches = []

        if len(stages) < 2:
            message = 'CodePipeline has {} stages. There must be at least two stages.'.format(
                len(stages))
            matches.append(
                RuleMatch(path, self._format_error_message(message, scenario)))

        return matches
示例#29
0
    def check_txt_record(self, path, recordset):
        """Check TXT record Configuration"""
        matches = []

        # Check quotation of the records
        resource_records = recordset.get('ResourceRecords')

        for index, record in enumerate(resource_records):
            tree = path[:] + ['ResourceRecords', index]

            if not isinstance(record, dict):
                if not record.startswith('"') or not record.endswith('"'):
                    message = 'TXT record ({}) has to be enclosed in double quotation marks (")'
                    matches.append(RuleMatch(tree, message.format(record)))
                elif len(record) > 255:
                    message = 'The length of the TXT record ({}) exceeds the limit (255)'
                    matches.append(RuleMatch(tree,
                                             message.format(len(record))))

        return matches
示例#30
0
    def check_cname_record(self, path, recordset):
        """Check CNAME record Configuration"""
        matches = []

        resource_records = recordset.get('ResourceRecords')
        if len(resource_records) > 1:
            message = 'A CNAME recordset can only contain 1 value'
            matches.append(RuleMatch(path + ['ResourceRecords'], message))
        else:
            for index, record in enumerate(resource_records):
                if not isinstance(record, dict):
                    tree = path[:] + ['ResourceRecords', index]
                    if (not re.match(self.REGEX_CNAME, record)
                            # ACM Route 53 validation uses invalid CNAMEs starting with `_`,
                            # special-case them rather than complicate the regex.
                            and not record.endswith('.acm-validations.aws.')):
                        message = 'CNAME record ({}) does not contain a valid domain name'
                        matches.append(RuleMatch(tree, message.format(record)))

        return matches