def _TranslatePolicy(self, pol, exp_info): """Transform a policy object into a PaloAltoFW object. Args: pol: policy.Policy object exp_info: print a info message when a term is set to expire in that many weeks Raises: UnsupportedFilterError: An unsupported filter was specified UnsupportedHeaderError: A header option exists that is not understood/usable PaloAltoFWDuplicateTermError: Two terms were found with same name in same filter PaloAltoFWBadIcmpTypeError: The referenced ICMP type is not supported by the policy term. PaloAltoFWUnsupportedProtocolError: The term contains unsupporter protocol name. """ current_date = datetime.date.today() exp_info_date = current_date + datetime.timedelta(weeks=exp_info) first_addr_obj = None for header, terms in pol.filters: if self._PLATFORM not in header.platforms: continue # The filter_options is a list of options from header, e.g. # ['from-zone', 'internal', 'to-zone', 'external'] filter_options = header.FilterOptions(self._PLATFORM) if (len(filter_options) < 4 or filter_options[0] != "from-zone" or filter_options[2] != "to-zone"): raise UnsupportedFilterError( "Palo Alto Firewall filter arguments must specify from-zone and " "to-zone.") self.from_zone = filter_options[1] self.to_zone = filter_options[3] # The filter_type values are either inet, inet6, or mixed. Later, the # code analyzes source and destination IP addresses and determines whether # it is an appropriate type for the filter_type value. if len(filter_options) > 4: filter_type = filter_options[4] else: filter_type = "inet" if filter_type not in self._SUPPORTED_AF: raise UnsupportedHeaderError( 'Palo Alto Firewall Generator invalid address family option: "%s"' '; expect {%s}' % (filter_type, '|'.join(self._SUPPORTED_AF))) valid_addr_obj = ["addr-obj", "no-addr-obj"] if len(filter_options ) > 5 and filter_options[5] not in valid_addr_obj: raise UnsupportedHeaderError( 'Palo Alto Firewall Generator invalid address objects option: "%s"' '; expect {%s}' % (filter_options[5], '|'.join(valid_addr_obj))) no_addr_obj = True if ( len(filter_options) > 5 and filter_options[5] == "no-addr-obj") else False if first_addr_obj is None: first_addr_obj = no_addr_obj if first_addr_obj != no_addr_obj: raise UnsupportedHeaderError( "Cannot mix addr-obj and no-addr-obj header option in " "a single policy file") term_dup_check = set() new_terms = [] for term in terms: if term.stateless_reply: logging.warning( "WARNING: Term %s in policy %s>%s is a stateless reply " "term and will not be rendered.", term.name, self.from_zone, self.to_zone) continue if "established" in term.option: logging.warning( "WARNING: Term %s in policy %s>%s is a established " "term and will not be rendered.", term.name, self.from_zone, self.to_zone) continue if "tcp-established" in term.option: logging.warning( "WARNING: Term %s in policy %s>%s is a tcp-established " "term and will not be rendered.", term.name, self.from_zone, self.to_zone) continue # Verify platform specific terms. Skip whole term if platform does not # match. if term.platform: if self._PLATFORM not in term.platform: continue if term.platform_exclude: if self._PLATFORM in term.platform_exclude: continue term.name = self.FixTermLength(term.name) if term.name in term_dup_check: raise PaloAltoFWDuplicateTermError( "You have a duplicate term: %s" % term.name) term_dup_check.add(term.name) services = {"tcp", "udp"} & set(term.protocol) others = set(term.protocol) - services if others and term.pan_application: raise UnsupportedFilterError( "Term %s contains non tcp, udp protocols with pan-application: %s: %s" "\npan-application can only be used with protocols tcp, udp" % (term.name, ', '.join( term.pan_application), ', '.join(term.protocol))) if term.expiration: if term.expiration <= exp_info_date: logging.info( "INFO: Term %s in policy %s>%s expires " "in less than two weeks.", term.name, self.from_zone, self.to_zone) if term.expiration <= current_date: logging.warning( "WARNING: Term %s in policy %s>%s is expired and " "will not be rendered.", term.name, self.from_zone, self.to_zone) continue for i in term.source_address_exclude: term.source_address = nacaddr.RemoveAddressFromList( term.source_address, i) for i in term.destination_address_exclude: term.destination_address = nacaddr.RemoveAddressFromList( term.destination_address, i) # Count the number of occurencies of a particular version of the # address family, i.e. v4/v6 in source and destination IP addresses. afc = { 4: { "src": 0, "dst": 0 }, 6: { "src": 0, "dst": 0 }, } # Determine the address families in the source and destination # addresses references in the term. Next, determine IPv4 and IPv6 # traffic flow patterns. exclude_address_family = [] flows = [] src_any = False dst_any = False if not term.source_address: src_any = True if not term.destination_address: dst_any = True for addr in term.source_address: afc[addr.version]["src"] += 1 for addr in term.destination_address: afc[addr.version]["dst"] += 1 for v in [4, 6]: if src_any and dst_any: flows.append("ip%d-ip%d" % (v, v)) continue if (afc[v]["src"] == 0 and not src_any) and (afc[v]["dst"] == 0 and not dst_any): continue if (afc[v]["src"] > 0 or src_any) and (afc[v]["dst"] > 0 or dst_any): flows.append("ip%d-ip%d" % (v, v)) continue if (afc[v]["src"] > 0 or src_any) and afc[v]["dst"] == 0: flows.append("ip%d-src-only" % v) flows.append("ip%d-only" % v) continue if afc[v]["src"] == 0 and (afc[v]["dst"] > 0 or dst_any): flows.append("ip%d-dst-only" % v) flows.append("ip%d-only" % v) if filter_type == "inet": if "icmpv6" in term.protocol: logging.warning( "WARNING: Term %s in policy %s>%s references ICMPv6 protocol, " "term will not be rendered.", term.name, self.from_zone, self.to_zone) continue if "ip4-ip4" not in flows: logging.warning( "WARNING: Term %s in policy %s>%s has one or more invalid " "src-dest combinations %s, term will not be rendered.", term.name, self.from_zone, self.to_zone, flows) continue # exclude IPv6 addresses exclude_address_family.append(6) elif filter_type == "inet6": if "icmp" in term.protocol: logging.warning( "WARNING: Term %s in policy %s>%s references ICMP protocol, " "term and will not be rendered.", term.name, self.from_zone, self.to_zone) continue if "ip6-ip6" not in flows: logging.warning( "WARNING: Term %s in policy %s>%s has one or more invalid " "src-dest combinations %s, term will not be rendered.", term.name, self.from_zone, self.to_zone, flows) continue exclude_address_family.append(4) elif filter_type == "mixed": if "ip4-ip4" in flows and "ip6-ip6" not in flows: exclude_address_family.append(6) pass elif "ip6-ip6" in flows and "ip4-ip4" not in flows: exclude_address_family.append(4) pass elif "ip4-ip4" in flows and "ip6-ip6" in flows: pass elif "ip4-only" in flows and "ip6-only" in flows: logging.warning( "WARNING: Term %s in policy %s>%s has source and destinations " "of different address families %s, term will not be " "rendered.", term.name, self.from_zone, self.to_zone, filter(lambda p: re.search(p, "(src|dst)-only"), flows)) continue else: logging.warning( "WARNING: Term %s in policy %s>%s has invalid src-dest " "combinations %s, the term will be rendered without them.", term.name, self.from_zone, self.to_zone, filter(lambda p: re.search(p, "(src|dst)-only"), flows)) if "ip4-ip4" in flows: exclude_address_family.append(6) else: exclude_address_family.append(4) # Build address book for the addresses referenced in the term. for addr in term.source_address: if addr.version in exclude_address_family: continue self._BuildAddressBook(self.from_zone, addr) for addr in term.destination_address: if addr.version in exclude_address_family: continue self._BuildAddressBook(self.to_zone, addr) # Handle ICMP/ICMPv6 terms. if term.icmp_type and ("icmp" not in term.protocol and "icmpv6" not in term.protocol): raise UnsupportedFilterError( "Palo Alto Firewall filter must have ICMP or ICMPv6 protocol " + "specified when using icmp_type keyword") for icmp_version in ["icmp", "icmpv6"]: if ("icmp" not in term.protocol and "icmpv6" not in term.protocol): # the protocol is not ICMP or ICMPv6 break if icmp_version not in term.protocol: # skip if this icmp_version isn't in the term protocol. continue if icmp_version == "icmp" and "ip4-ip4" not in flows: # skip if there is no ip4 to ipv4 communication continue if icmp_version == "icmpv6" and "ip6-ip6" not in flows: # skip if there is no ip4 to ipv4 communication continue if icmp_version == "icmp": if filter_type == "inet6": continue if not term.icmp_type: term.pan_application.append("icmp") continue icmp_type_keyword = "ident-by-icmp-type" # The risk level 4 is the default PANOS' risk level for ICMP. risk_level = 4 else: if filter_type == "inet": continue if not term.icmp_type: term.pan_application.append("ipv6-icmp") continue icmp_type_keyword = "ident-by-icmp6-type" # The risk level 2 is the default PANOS' risk level for ICMPv6. risk_level = 2 # The term contains ICMP types for term_icmp_type_name in term.icmp_type: if icmp_version == "icmp": icmp_app_name = "icmp-%s" % term_icmp_type_name if term_icmp_type_name not in policy.Term.ICMP_TYPE[ 4]: raise PaloAltoFWBadIcmpTypeError( "term with bad icmp type: %s, icmp_type: %s" % (term.name, term_icmp_type_name)) term_icmp_type = policy.Term.ICMP_TYPE[4][ term_icmp_type_name] else: icmp_app_name = "icmp6-%s" % term_icmp_type_name if term_icmp_type_name not in policy.Term.ICMP_TYPE[ 6]: raise PaloAltoFWBadIcmpTypeError( "term with bad icmp type: %s, icmp_type: %s" % (term.name, term_icmp_type_name)) term_icmp_type = policy.Term.ICMP_TYPE[6][ term_icmp_type_name] if icmp_app_name in self.application_refs: # the custom icmp application already exists continue app_entry = { "category": "networking", "subcategory": "ip-protocol", "technology": "network-protocol", "description": icmp_app_name, "default": { icmp_type_keyword: "%d" % term_icmp_type, }, "risk": "%d" % risk_level, } self.application_refs[icmp_app_name] = app_entry self.applications.append(icmp_app_name) if icmp_app_name not in term.pan_application: term.pan_application.append(icmp_app_name) # Filter out unsupported protocols for proto_name in term.protocol: if proto_name in self._SUPPORTED_PROTO_NAMES: continue raise PaloAltoFWUnsupportedProtocolError( "protocol %s is not supported" % proto_name) if term.icmp_type: if set(term.protocol) == {'icmp', 'icmpv6'}: raise UnsupportedFilterError('%s %s' % ( 'icmp-type specified for both icmp and icmpv6 protocols' ' in a single term:', term.name)) if term.protocol != ['icmp' ] and term.protocol != ['icmpv6']: raise UnsupportedFilterError('%s %s' % ( 'icmp-type specified for non-icmp protocols in term:', term.name)) new_terms.append(term) # Create a ruleset. It contains the rules for the terms defined under # a single header on a particular platform. ruleset = {} for term in new_terms: current_rule = Rule(self.from_zone, self.to_zone, term, self.service_map) if len(current_rule.options) > 1: for i, v in enumerate(current_rule.options): name = "%s-%d" % (term.name, i + 1) name = self.FixTermLength(name) ruleset[name] = v else: ruleset[term.name] = current_rule.options[0] self.pafw_policies.append((header, ruleset, filter_options))
def _TranslatePolicy(self, pol, exp_info): # pylint: disable=attribute-defined-outside-init """Transform a policy object into a JuniperSRX object. Args: pol: policy.Policy object exp_info: print a info message when a term is set to expire in that many weeks Raises: UnsupportedFilterError: An unsupported filter was specified UnsupportedHeader: A header option exists that is not understood/usable SRXDuplicateTermError: Two terms were found with same name in same filter ConflictingTargetOptions: Two target options are conflicting in the header MixedAddrBookTypes: Global and Zone address books in the same policy ConflictingApplicationSets: When two duplicate named terms have conflicting application entries """ self.srx_policies = [] self.addressbook = collections.OrderedDict() self.applications = [] self.ports = [] self.from_zone = '' self.to_zone = '' self.addr_book_type = set() current_date = datetime.datetime.utcnow().date() exp_info_date = current_date + datetime.timedelta(weeks=exp_info) for header, terms in pol.filters: if self._PLATFORM not in header.platforms: continue filter_options = header.FilterOptions(self._PLATFORM) verbose = True if self._NOVERBOSE in filter_options[4:]: verbose = False # TODO(robankeny): Clean up option section. if (len(filter_options) < 4 or filter_options[0] != 'from-zone' or filter_options[2] != 'to-zone'): raise UnsupportedFilterError( 'SRX filter arguments must specify ' 'from-zone and to-zone.') # check if to-zone is not a supported target option if filter_options[1] in self._SUPPORTED_TARGET_OPTIONS: raise UnsupportedFilterError( 'to-zone %s cannot be the same as any ' 'valid SRX target-options' % (filter_options[1])) else: self.from_zone = filter_options[1] # check if from-zone is not a supported target option if filter_options[3] in self._SUPPORTED_TARGET_OPTIONS: raise UnsupportedFilterError( 'from-zone %s cannot be the same as any ' 'valid SRX target-options' % (filter_options[3])) else: self.to_zone = filter_options[3] # variables used to collect target-options and set defaults filter_type = '' # parse srx target options extra_options = filter_options[4:] if self._ADDRESSBOOK_TYPES.issubset(extra_options): raise ConflictingTargetOptions( 'only one address-book-type can ' 'be specified per header "%s"' % ' '.join(filter_options)) else: address_book_type = set( [self._ZONE_ADDR_BOOK, self._GLOBAL_ADDR_BOOK]).intersection(extra_options) if len(address_book_type) is 0: address_book_type = {self._GLOBAL_ADDR_BOOK} self.addr_book_type.update(address_book_type) if len(self.addr_book_type) > 1: raise MixedAddrBookTypes( 'Global and Zone address-book-types cannot ' 'be used in the same policy') if self.from_zone == 'all' and self.to_zone == 'all': if self._ZONE_ADDR_BOOK in self.addr_book_type: raise UnsupportedFilterError( 'Zone address books cannot be used ' 'with a global policy.') elif self.from_zone == 'all' or self.to_zone == 'all': raise UnsupportedFilterError( 'The zone name all is reserved for ' 'global policies.') if self._EXPRESSPATH in filter_options[4:]: self.expresspath = True else: self.expresspath = False for filter_opt in filter_options[4:]: # validate address families if filter_opt in self._SUPPORTED_AF: if not filter_type: filter_type = filter_opt else: raise ConflictingTargetOptions( 'only one address family can be ' 'specified per header "%s"' % ' '.join(filter_options)) elif filter_opt in self._SUPPORTED_TARGET_OPTIONS: continue else: raise UnsupportedHeader( 'SRX Generator currently does not support ' '%s as a header option "%s"' % (filter_opt, ' '.join(filter_options))) # if address-family and address-book-type have not been set then default if not filter_type: filter_type = 'mixed' term_dup_check = set() new_terms = [] self._FixLargePolices(terms, filter_type) for term in terms: if set(['established', 'tcp-established']).intersection(term.option): logging.debug( 'Skipping established term %s ' + 'because SRX is stateful.', term.name) continue term.name = self.FixTermLength(term.name) if term.name in term_dup_check: raise SRXDuplicateTermError( 'You have a duplicate term: %s' % term.name) term_dup_check.add(term.name) if term.expiration: if term.expiration <= exp_info_date: logging.info( 'INFO: Term %s in policy %s>%s expires ' 'in less than two weeks.', term.name, self.from_zone, self.to_zone) if term.expiration <= current_date: logging.warn( 'WARNING: Term %s in policy %s>%s is expired.', term.name, self.from_zone, self.to_zone) continue # SRX address books leverage network token names for IPs. # When excluding addresses, we lose those distinct names so we need # to create a new unique name based off the term name before excluding. if term.source_address_exclude: # If we have a naked source_exclude, we need something to exclude from if not term.source_address: term.source_address = [ nacaddr.IP('0.0.0.0/0', term.name.upper(), term.name.upper()) ] # Use the term name as the token & parent_token new_src_parent_token = term.name.upper() + '_SRC_EXCLUDE' new_src_token = new_src_parent_token for i in term.source_address_exclude: term.source_address = nacaddr.RemoveAddressFromList( term.source_address, i) for i in term.source_address: i.token = new_src_token i.parent_token = new_src_parent_token if term.destination_address_exclude: if not term.destination_address: term.destination_address = [ nacaddr.IP('0.0.0.0/0', term.name.upper(), term.name.upper()) ] new_dst_parent_token = term.name.upper() + '_DST_EXCLUDE' new_dst_token = new_dst_parent_token for i in term.destination_address_exclude: term.destination_address = nacaddr.RemoveAddressFromList( term.destination_address, i) for i in term.destination_address: i.token = new_dst_token i.parent_token = new_dst_parent_token # SRX policies are controlled by addresses that are used within, so # policy can be at the same time inet and inet6. if self._GLOBAL_ADDR_BOOK in self.addr_book_type: for zone in self.addressbook: for unused_name, ips in sorted( six.iteritems(self.addressbook[zone])): ips = [i for i in ips] if term.source_address == ips: term.source_address = ips if term.destination_address == ips: term.destination_address = ips for addr in term.source_address: if addr.version in self._AF_MAP[filter_type]: self._BuildAddressBook(self.from_zone, addr) for addr in term.destination_address: if addr.version in self._AF_MAP[filter_type]: self._BuildAddressBook(self.to_zone, addr) new_term = Term(term, self.from_zone, self.to_zone, self.expresspath, verbose) new_terms.append(new_term) # Because SRX terms can contain inet and inet6 addresses. We have to # have ability to recover proper AF for ICMP type we need. # If protocol is empty or we cannot map to inet or inet6 we insert bogus # af_type name which will cause new_term.NormalizeIcmpTypes to fail. if not term.protocol: icmp_af_type = 'unknown_af_icmp' else: icmp_af_type = self._AF_ICMP_MAP.get( term.protocol[0], 'unknown_af_icmp') tmp_icmptype = new_term.NormalizeIcmpTypes( term.icmp_type, term.protocol, icmp_af_type) # NormalizeIcmpTypes returns [''] for empty, convert to [] for eval normalized_icmptype = tmp_icmptype if tmp_icmptype != [ '' ] else [] # rewrites the protocol icmpv6 to icmp6 if 'icmpv6' in term.protocol: protocol = list(term.protocol) protocol[protocol.index('icmpv6')] = 'icmp6' else: protocol = term.protocol new_application_set = { 'sport': self._BuildPort(term.source_port), 'dport': self._BuildPort(term.destination_port), 'protocol': protocol, 'icmp-type': normalized_icmptype, 'timeout': term.timeout } for application_set in self.applications: if all(item in list(application_set.items()) for item in new_application_set.items()): new_application_set = '' term.replacement_application_name = application_set[ 'name'] break if (term.name == application_set['name'] and new_application_set != application_set): raise ConflictingApplicationSets( 'Application set %s has a conflicting entry' % term.name) if new_application_set: new_application_set['name'] = term.name self.applications.append(new_application_set) self.srx_policies.append((header, new_terms, filter_options))
def _TranslatePolicy(self, pol, exp_info): """Transform a policy object into a PaloAltoFW object. Args: pol: policy.Policy object exp_info: print a info message when a term is set to expire in that many weeks Raises: UnsupportedFilterError: An unsupported filter was specified UnsupportedHeader: A header option exists that is not understood/usable PaloAltoFWDuplicateTermError: Two terms were found with same name in same filter """ current_date = datetime.date.today() exp_info_date = current_date + datetime.timedelta(weeks=exp_info) for header, terms in pol.filters: if self._PLATFORM not in header.platforms: continue filter_options = header.FilterOptions(self._PLATFORM) if (len(filter_options) < 4 or filter_options[0] != "from-zone" or filter_options[2] != "to-zone"): raise UnsupportedFilterError( "Palo Alto Firewall filter arguments must specify from-zone and " "to-zone.") self.from_zone = filter_options[1] self.to_zone = filter_options[3] if len(filter_options) > 4: filter_type = filter_options[4] else: filter_type = "inet" if filter_type not in self._SUPPORTED_AF: raise UnsupportedHeader( "Palo Alto Firewall Generator currently does not support" " %s as a header option" % (filter_type)) term_dup_check = set() new_terms = [] for term in terms: term.name = self.FixTermLength(term.name) if term.name in term_dup_check: raise PaloAltoFWDuplicateTermError( "You have a duplicate term: %s" % term.name) term_dup_check.add(term.name) if term.expiration: if term.expiration <= exp_info_date: logging.info( "INFO: Term %s in policy %s>%s expires " "in less than two weeks.", term.name, self.from_zone, self.to_zone) if term.expiration <= current_date: logging.warn( "WARNING: Term %s in policy %s>%s is expired and " "will not be rendered.", term.name, self.from_zone, self.to_zone) continue for i in term.source_address_exclude: term.source_address = nacaddr.RemoveAddressFromList( term.source_address, i) for i in term.destination_address_exclude: term.destination_address = nacaddr.RemoveAddressFromList( term.destination_address, i) for addr in term.source_address: self._BuildAddressBook(self.from_zone, addr) for addr in term.destination_address: self._BuildAddressBook(self.to_zone, addr) new_term = Term(term, filter_type, filter_options) new_terms.append(new_term) tmp_icmptype = new_term.NormalizeIcmpTypes( term.icmp_type, term.protocol, filter_type) # NormalizeIcmpTypes returns [''] for empty, convert to [] for # eval normalized_icmptype = tmp_icmptype if tmp_icmptype != [ "" ] else [] # rewrites the protocol icmpv6 to icmp6 if "icmpv6" in term.protocol: protocol = list(term.protocol) protocol[protocol.index("icmpv6")] = "icmp6" else: protocol = term.protocol self.applications.append({ "sport": self._BuildPort(term.source_port), "dport": self._BuildPort(term.destination_port), "name": term.name, "protocol": protocol, "icmp-type": normalized_icmptype, "timeout": term.timeout }) self.pafw_policies.append((header, new_terms, filter_options)) # create Palo Alto Firewall Rule object for term in new_terms: unused_rule = Rule(self.from_zone, self.to_zone, term)