Ejemplo n.º 1
0
 def testIgmpMixed(self):
     self.naming.GetNetAddr.return_value = TEST_IPS
     acl = gce.GCE(
         policy.ParsePolicy(GOOD_HEADER_MIXED + GOOD_TERM_IGMP,
                            self.naming), EXP_INFO)
     self.assertIn('2', str(acl))
     self.assertIn('10.2.3.4/32', str(acl))
     self.assertNotIn('2001:4860:8000::5/128', str(acl))
     self.assertNotIn(gcp.GetIpv6TermName('good-term-pingv6'), str(acl))
Ejemplo n.º 2
0
 def testMixedIsSeparateRules(self):
     self.naming.GetNetAddr.return_value = TEST_IPS
     self.naming.GetServiceByProto.side_effect = [['53'], ['53']]
     acl = gce.GCE(
         policy.ParsePolicy(GOOD_HEADER_MIXED + GOOD_TERM + DEFAULT_DENY,
                            self.naming), EXP_INFO)
     self.assertIn('INGRESS', str(acl))
     self.assertNotIn('EGRESS', str(acl))
     self.assertIn('2001:4860:8000::5/128', str(acl))
     self.assertIn('10.2.3.4/32', str(acl))
     self.assertIn('good-term-1', str(acl))
     self.assertIn(gcp.GetIpv6TermName('good-term-1'), str(acl))
Ejemplo n.º 3
0
 def testMixedWithEgressSourceTag(self):
     self.naming.GetNetAddr.return_value = TEST_IPS
     self.naming.GetServiceByProto.side_effect = [['53'], ['53']]
     acl = gce.GCE(
         policy.ParsePolicy(
             GOOD_HEADER_EGRESS_MIXED + GOOD_TERM_EGRESS_SOURCETAG,
             self.naming), EXP_INFO)
     self.assertNotIn('INGRESS', str(acl))
     self.assertIn('EGRESS', str(acl))
     self.assertIn('10.2.3.4/32', str(acl))
     self.assertIn('2001:4860:8000::5/128', str(acl))
     self.assertIn('dns-servers', str(acl))
     self.assertIn(gcp.GetIpv6TermName('good-term-1-e'), str(acl))
Ejemplo n.º 4
0
 def testMixedWithSourceTagAndV4Addresses(self):
     self.naming.GetNetAddr.return_value = TEST_IPV4_ONLY
     self.naming.GetServiceByProto.side_effect = [['53'], ['53']]
     acl = gce.GCE(
         policy.ParsePolicy(
             GOOD_HEADER_MIXED + GOOD_TERM_INGRESS_ADDRESS_SOURCETAG +
             DEFAULT_DENY, self.naming), EXP_INFO)
     self.assertIn('INGRESS', str(acl))
     self.assertNotIn('EGRESS', str(acl))
     self.assertNotIn('2001:4860:8000::5/128', str(acl))
     self.assertIn('10.2.3.4/32', str(acl))
     self.assertIn('internal-servers', str(acl))
     self.assertNotIn(gcp.GetIpv6TermName('good-term-1'), str(acl))
Ejemplo n.º 5
0
 def testMixedDefaultDenyIngressCreation(self):
     self.naming.GetNetAddr.return_value = TEST_IPS
     self.naming.GetServiceByProto.side_effect = [['53'], ['53']]
     acl = gce.GCE(
         policy.ParsePolicy(
             GOOD_HEADER_MIXED + GOOD_TERM_INGRESS_SOURCETAG + DEFAULT_DENY,
             self.naming), EXP_INFO)
     self.assertIn('INGRESS', str(acl))
     self.assertNotIn('EGRESS', str(acl))
     self.assertIn('"priority": 65534', str(acl))
     self.assertIn('default-deny', str(acl))
     self.assertIn(gcp.GetIpv6TermName('default-deny'), str(acl))
     self.assertIn('::/0', str(acl))
     self.assertIn('0.0.0.0/0', str(acl))
Ejemplo n.º 6
0
    def ConvertToDict(self, priority_index):
        """Converts term to dict representation of SecurityPolicy.Rule JSON format.

    Takes all of the attributes associated with a term (match, action, etc) and
    converts them into a dictionary which most closely represents
    the SecurityPolicy.Rule JSON format.

    Args:
      priority_index: An integer priority value assigned to the term.

    Returns:
      A dict term.
    """
        if self.skip:
            return {}

        rules = []

        # Identify if this is inet6 processing for a term under a mixed policy.
        mixed_policy_inet6_term = False
        if self.policy_inet_version == 'mixed' and self.address_family == 'inet6':
            mixed_policy_inet6_term = True

        term_dict = {
            'action': self.ACTION_MAP.get(self.term.action[0],
                                          self.term.action[0]),
            'direction': self.term.direction,
            'priority': priority_index
        }

        # Get the correct syntax for API versions.
        src_ip_range = ApiVersionSyntaxMap.SYNTAX_MAP[
            self.api_version]['src_ip_range']
        dest_ip_range = ApiVersionSyntaxMap.SYNTAX_MAP[
            self.api_version]['dest_ip_range']
        layer_4_config = ApiVersionSyntaxMap.SYNTAX_MAP[
            self.api_version]['layer_4_config']

        target_resources = []
        for proj, vpc in self.term.target_resources:
            target_resources.append(
                self._TARGET_RESOURCE_FORMAT.format(proj, vpc))

        if target_resources:  # Only set when non-empty.
            term_dict['targetResources'] = target_resources

        term_dict['enableLogging'] = self._GetLoggingSetting()

        # This combo provides ability to identify the rule.
        term_name = self.term.name
        if mixed_policy_inet6_term:
            term_name = gcp.GetIpv6TermName(term_name)
        raw_description = term_name + ': ' + ' '.join(self.term.comment)
        term_dict['description'] = gcp.TruncateString(
            raw_description, self._MAX_TERM_COMMENT_LENGTH)

        filtered_protocols = []
        for proto in self.term.protocol:
            # ICMP filtering by inet_version
            # Since each term has inet_version, 'mixed' is correctly processed here.
            if proto == 'icmp' and self.address_family == 'inet6':
                logging.warning(
                    'WARNING: Term %s is being rendered for inet6, ICMP '
                    'protocol will not be rendered.', self.term.name)
                continue
            if proto == 'icmpv6' and self.address_family == 'inet':
                logging.warning(
                    'WARNING: Term %s is being rendered for inet, ICMPv6 '
                    'protocol will not be rendered.', self.term.name)
                continue
            if proto == 'igmp' and self.address_family == 'inet6':
                logging.warning(
                    'WARNING: Term %s is being rendered for inet6, IGMP '
                    'protocol will not be rendered.', self.term.name)
                continue
            filtered_protocols.append(proto)
        # If there is no protocol left after ICMP/IGMP filtering, drop this term.
        # But only do this for terms that originally had protocols.
        # Otherwise you end up dropping the default-deny.
        if self.term.protocol and not filtered_protocols:
            return {}

        protocols_and_ports = []
        if not self.term.protocol:
            # Empty protocol list means any protocol, but any protocol in HF is
            # represented as "all"
            protocols_and_ports = [{'ipProtocol': 'all'}]
        else:
            for proto in filtered_protocols:
                # If the protocol name is not supported, use the protocol number.
                if proto not in self._ALLOW_PROTO_NAME:
                    proto = str(self.PROTO_MAP[proto])
                    logging.info(
                        'INFO: Term %s is being rendered using protocol number',
                        self.term.name)
                proto_ports = {'ipProtocol': proto}
                if self.term.destination_port:
                    ports = self._GetPorts()
                    if ports:  # Only set when non-empty.
                        proto_ports['ports'] = ports
                protocols_and_ports.append(proto_ports)

        if self.api_version == 'ga':
            term_dict['match'] = {layer_4_config: protocols_and_ports}
        else:
            term_dict['match'] = {
                'config': {
                    layer_4_config: protocols_and_ports
                }
            }

        # match needs a field called versionedExpr with value FIREWALL
        # See documentation:
        # https://cloud.google.com/compute/docs/reference/rest/beta/organizationSecurityPolicies/addRule
        term_dict['match']['versionedExpr'] = 'FIREWALL'

        ip_version = self.AF_MAP[self.address_family]
        if ip_version == 4:
            any_ip = [nacaddr.IP('0.0.0.0/0')]
        else:
            any_ip = [nacaddr.IPv6('::/0')]

        if self.term.direction == 'EGRESS':
            daddrs = self.term.GetAddressOfVersion('destination_address',
                                                   ip_version)

            # If the address got filtered out and is empty due to address family, we
            # don't render the term. At this point of term processing, the direction
            # has already been validated, so we can just log and return empty rule.
            if self.term.destination_address and not daddrs:
                logging.warning(
                    'WARNING: Term %s is not being rendered for %s, '
                    'because there are no addresses of that family.',
                    self.term.name, self.address_family)
                return []
            # This should only happen if there were no addresses set originally.
            if not daddrs:
                daddrs = any_ip

            destination_address_chunks = [
                daddrs[x:x + self._TERM_ADDRESS_LIMIT]
                for x in range(0, len(daddrs), self._TERM_ADDRESS_LIMIT)
            ]

            for daddr_chunk in destination_address_chunks:
                rule = copy.deepcopy(term_dict)
                if self.api_version == 'ga':
                    rule['match'][dest_ip_range] = [
                        daddr.with_prefixlen for daddr in daddr_chunk
                    ]
                else:
                    rule['match']['config'][dest_ip_range] = [
                        daddr.with_prefixlen for daddr in daddr_chunk
                    ]
                rule['priority'] = priority_index
                rules.append(rule)
                priority_index += 1
        else:
            saddrs = self.term.GetAddressOfVersion('source_address',
                                                   ip_version)

            # If the address got filtered out and is empty due to address family, we
            # don't render the term. At this point of term processing, the direction
            # has already been validated, so we can just log and return empty rule.
            if self.term.source_address and not saddrs:
                logging.warning(
                    'WARNING: Term %s is not being rendered for %s, '
                    'because there are no addresses of that family.',
                    self.term.name, self.address_family)
                return []
            # This should only happen if there were no addresses set originally.
            if not saddrs:
                saddrs = any_ip

            source_address_chunks = [
                saddrs[x:x + self._TERM_ADDRESS_LIMIT]
                for x in range(0, len(saddrs), self._TERM_ADDRESS_LIMIT)
            ]
            for saddr_chunk in source_address_chunks:
                rule = copy.deepcopy(term_dict)
                if self.api_version == 'ga':
                    rule['match'][src_ip_range] = [
                        saddr.with_prefixlen for saddr in saddr_chunk
                    ]
                else:
                    rule['match']['config'][src_ip_range] = [
                        saddr.with_prefixlen for saddr in saddr_chunk
                    ]
                rule['priority'] = priority_index
                rules.append(rule)
                priority_index += 1

        return rules
Ejemplo n.º 7
0
    def ConvertToDict(self):
        """Convert term to a dictionary.

    This is used to get a dictionary describing this term which can be
    output easily as a JSON blob.

    Returns:
      A dictionary that contains all fields necessary to create or update a GCE
      firewall.

    Raises:
      GceFirewallError: The term name is too long.
    """
        if self.term.owner:
            self.term.comment.append('Owner: %s' % self.term.owner)
        term_dict = {
            'description': ' '.join(self.term.comment),
            'name': self.term.name,
            'direction': self.term.direction
        }
        if self.term.network:
            term_dict['network'] = self.term.network
            term_dict['name'] = '%s-%s' % (self.term.network.split('/')[-1],
                                           term_dict['name'])
        # Identify if this is inet6 processing for a term under a mixed policy.
        mixed_policy_inet6_term = False
        if self.policy_inet_version == 'mixed' and self.inet_version == 'inet6':
            mixed_policy_inet6_term = True
        # Update term name to have the IPv6 suffix for the inet6 rule.
        if mixed_policy_inet6_term:
            term_dict['name'] = gcp.GetIpv6TermName(term_dict['name'])

        # Checking counts of tags, and ports to see if they exceeded limits.
        if len(self.term.source_tag) > self._TERM_SOURCE_TAGS_LIMIT:
            raise GceFirewallError(
                'GCE firewall rule exceeded number of source tags per rule: %s'
                % self.term.name)
        if len(self.term.destination_tag) > self._TERM_TARGET_TAGS_LIMIT:
            raise GceFirewallError(
                'GCE firewall rule exceeded number of target tags per rule: %s'
                % self.term.name)

        if self.term.source_tag:
            if self.term.direction == 'INGRESS':
                term_dict['sourceTags'] = self.term.source_tag
            elif self.term.direction == 'EGRESS':
                term_dict['targetTags'] = self.term.source_tag
        if self.term.destination_tag and self.term.direction == 'INGRESS':
            term_dict['targetTags'] = self.term.destination_tag
        if self.term.priority:
            term_dict['priority'] = self.term.priority
            # Update term priority for the inet6 rule.
            if mixed_policy_inet6_term:
                term_dict['priority'] = GetNextPriority(term_dict['priority'])

        rules = []
        # If 'mixed' ends up in indvidual term inet_version, something has gone
        # horribly wrong. The only valid values are inet/inet6.
        term_af = self.AF_MAP.get(self.inet_version)
        if self.inet_version == 'mixed':
            raise GceFirewallError(
                'GCE firewall rule has incorrect inet_version for rule: %s' %
                self.term.name)

        # Exit early for inet6 processing of mixed rules that have only tags,
        # and no IP addresses, since this is handled in the inet processing.
        if mixed_policy_inet6_term:
            if not self.term.source_address and not self.term.destination_address:
                if 'targetTags' in term_dict or 'sourceTags' in term_dict:
                    return []

        saddrs = sorted(self.term.GetAddressOfVersion('source_address',
                                                      term_af),
                        key=ipaddress.get_mixed_type_key)
        daddrs = sorted(self.term.GetAddressOfVersion('destination_address',
                                                      term_af),
                        key=ipaddress.get_mixed_type_key)

        # If the address got filtered out and is empty due to address family, we
        # don't render the term. At this point of term processing, the direction
        # has already been validated, so we can just log and return empty rule.
        if self.term.source_address and not saddrs:
            logging.warning(
                'WARNING: Term %s is not being rendered for %s, '
                'because there are no addresses of that family.',
                self.term.name, self.inet_version)
            return []
        if self.term.destination_address and not daddrs:
            logging.warning(
                'WARNING: Term %s is not being rendered for %s, '
                'because there are no addresses of that family.',
                self.term.name, self.inet_version)
            return []

        filtered_protocols = []
        if not self.term.protocol:
            # Any protocol is represented as "all"
            filtered_protocols = ['all']
            logging.info(
                'INFO: Term %s has no protocol specified,'
                'which is interpreted as "all" protocols.', self.term.name)

        proto_dict = copy.deepcopy(term_dict)

        if self.term.logging:
            proto_dict['logConfig'] = {'enable': True}

        for proto in self.term.protocol:
            # ICMP filtering by inet_version
            # Since each term has inet_version, 'mixed' is correctly processed here.
            # Convert protocol to number for uniformity of comparison.
            # PROTO_MAP always returns protocol number.
            if proto in self._ALLOW_PROTO_NAME:
                proto_num = self.PROTO_MAP[proto]
            else:
                proto_num = proto
            if proto_num == self.PROTO_MAP[
                    'icmp'] and self.inet_version == 'inet6':
                logging.warning(
                    'WARNING: Term %s is being rendered for inet6, ICMP '
                    'protocol will not be rendered.', self.term.name)
                continue
            if proto_num == self.PROTO_MAP[
                    'icmpv6'] and self.inet_version == 'inet':
                logging.warning(
                    'WARNING: Term %s is being rendered for inet, ICMPv6 '
                    'protocol will not be rendered.', self.term.name)
                continue
            if proto_num == self.PROTO_MAP[
                    'igmp'] and self.inet_version == 'inet6':
                logging.warning(
                    'WARNING: Term %s is being rendered for inet6, IGMP '
                    'protocol will not be rendered.', self.term.name)
                continue
            filtered_protocols.append(proto)
        # If there is no protocol left after ICMP/IGMP filtering, drop this term.
        if not filtered_protocols:
            return []
        for proto in filtered_protocols:
            # If the protocol name is not supported, protocol number is used.
            # This is done by default in policy.py.
            if proto not in self._ALLOW_PROTO_NAME:
                logging.info(
                    'INFO: Term %s is being rendered using protocol number',
                    self.term.name)
            dest = {'IPProtocol': proto}

            if self.term.destination_port:
                ports = []
                for start, end in self.term.destination_port:
                    if start == end:
                        ports.append(str(start))
                    else:
                        ports.append('%d-%d' % (start, end))
                if len(ports) > self._TERM_PORTS_LIMIT:
                    raise GceFirewallError(
                        'GCE firewall rule exceeded number of ports per rule: %s'
                        % self.term.name)
                dest['ports'] = ports

            action = self.ACTION_MAP[self.term.action[0]]
            dict_val = []
            if action in proto_dict:
                dict_val = proto_dict[action]
                if not isinstance(dict_val, list):
                    dict_val = [dict_val]
            dict_val.append(dest)
            proto_dict[action] = dict_val

        # There's a limit of 256 addresses each term can contain.
        # If we're above that limit, we're breaking it down in more terms.
        if saddrs:
            source_addr_chunks = [
                saddrs[x:x + self._TERM_ADDRESS_LIMIT]
                for x in range(0, len(saddrs), self._TERM_ADDRESS_LIMIT)
            ]
            for i, chunk in enumerate(source_addr_chunks):
                rule = copy.deepcopy(proto_dict)
                if len(source_addr_chunks) > 1:
                    rule['name'] = '%s-%d' % (rule['name'], i + 1)
                rule['sourceRanges'] = [str(saddr) for saddr in chunk]
                rules.append(rule)
        elif daddrs:
            dest_addr_chunks = [
                daddrs[x:x + self._TERM_ADDRESS_LIMIT]
                for x in range(0, len(daddrs), self._TERM_ADDRESS_LIMIT)
            ]
            for i, chunk in enumerate(dest_addr_chunks):
                rule = copy.deepcopy(proto_dict)
                if len(dest_addr_chunks) > 1:
                    rule['name'] = '%s-%d' % (rule['name'], i + 1)
                rule['destinationRanges'] = [str(daddr) for daddr in chunk]
                rules.append(rule)
        else:
            rules.append(proto_dict)

        # Sanity checking term name lengths.
        long_rules = [rule['name'] for rule in rules if len(rule['name']) > 63]
        if long_rules:
            raise GceFirewallError(
                'GCE firewall name ended up being too long: %s' % long_rules)
        return rules
Ejemplo n.º 8
0
 def testGetIpv6TermName(self, term_name, expected):
     self.assertEqual(expected, gcp.GetIpv6TermName(term_name))