def __str__(self): """Render config output from this term object.""" # Verify platform specific terms. Skip whole term if platform does not # match. if self.term.platform: if self._PLATFORM not in self.term.platform: return '' if self.term.platform_exclude: if self._PLATFORM in self.term.platform_exclude: return '' conditions = [] # if terms does not specify action, use filter default action if not self.term.action: self.term.action[0].value = self.default_action if str(self.term.action[0]) not in self._ACTION_TABLE: raise aclgenerator.UnsupportedFilterError( '%s %s %s %s' % ('\n', self.term.name, self.term.action[0], 'action not currently supported.')) # source address term_saddrs = self._CheckAddressAf(self.term.source_address) if not term_saddrs: logging.debug( self.NO_AF_LOG_ADDR.substitute(term=self.term.name, direction='source', af=self.af)) return '' conditions.append( self._GenerateAddrStatement(term_saddrs, self.term.source_address_exclude)) # destination address term_daddrs = self._CheckAddressAf(self.term.destination_address) if not term_daddrs: logging.debug( self.NO_AF_LOG_ADDR.substitute(term=self.term.name, direction='destination', af=self.af)) return '' conditions.append( self._GenerateAddrStatement(term_daddrs, self.term.destination_address_exclude)) # protocol if self.term.protocol_except: raise aclgenerator.UnsupportedFilterError( '%s %s %s' % ('\n', self.term.name, 'protocol_except logic not currently supported.')) conditions.append(self._GenerateProtoStatement(self.term.protocol)) conditions.append( self._GeneratePortStatement(self.term.source_port, 'src')) conditions.append( self._GeneratePortStatement(self.term.destination_port, 'dst')) # icmp-type icmp_types = [''] if self.term.icmp_type: if self.term.protocol == ['icmp']: af = 'inet' elif self.term.protocol == ['icmpv6']: af = 'inet6' else: raise aclgenerator.UnsupportedFilterError( '%s %s %s' % ('\n', self.term.name, 'icmp protocol is not defined or not supported.')) icmp_types = self.NormalizeIcmpTypes(self.term.icmp_type, self.term.protocol, af) if 'icmp' in self.term.protocol: conditions.append( self._GenerateIcmpType(icmp_types, self.term.icmp_code)) # tcp options if 'tcp' in self.term.protocol: conditions.append(self._GenerateTcpOptions(self.term.option)) cond = Term.JoinConditionals(conditions, 'and') # Note that directionally-based pcap filter requires post-processing to # replace 'localhost' with whatever the IP(s) of the local machine happen # to be. This bit of logic ensure there's a placeholder with the # appropriate booleans around it. We also have to check that there exists # some form of condition already, else we'll end up with something overly # broad like 'dst net localhost' (e.g., 'default-deny'). if cond and self.direction == 'in': cond = Term.JoinConditionals(['dst net localhost', cond], 'and') elif cond and self.direction == 'out': cond = Term.JoinConditionals(['src net localhost', cond], 'and') return cond + '\n'
def _TranslatePolicy(self, pol, exp_info): self.pcap_policies = [] current_date = datetime.datetime.utcnow().date() exp_info_date = current_date + datetime.timedelta(weeks=exp_info) good_afs = ['inet', 'inet6', 'mixed'] good_options = ['in', 'out'] direction = '' for header, terms in pol.filters: filter_type = None if self._PLATFORM not in header.platforms: continue filter_options = header.FilterOptions(self._PLATFORM)[1:] filter_name = header.FilterName(self._PLATFORM) # ensure all options after the filter name are expected for opt in filter_options: if opt not in good_afs + good_options: raise UnsupportedTargetOptionError( '%s %s %s %s' % ('\nUnsupported option found in', self._PLATFORM, 'target definition:', opt)) if 'in' in filter_options: direction = 'in' elif 'out' in filter_options: direction = 'out' # Check for matching af for address_family in good_afs: if address_family in filter_options: # should not specify more than one AF in options if filter_type is not None: raise aclgenerator.UnsupportedFilterError( '%s %s %s %s' % ('\nMay only specify one of', good_afs, 'in filter options:', filter_options)) filter_type = address_family if filter_type is None: filter_type = 'mixed' # add the terms accept_terms = [] deny_terms = [] term_names = set() for term in terms: if term.name in term_names: raise aclgenerator.DuplicateTermError( 'You have a duplicate term: %s' % term.name) if term.expiration: if term.expiration <= exp_info_date: logging.info( 'INFO: Term %s in policy %s expires ' 'in less than two weeks.', term.name, filter_name) if term.expiration <= current_date: logging.warning( 'WARNING: Term %s in policy %s is expired and ' 'will not be rendered.', term.name, filter_name) continue if not term: continue if term.action[0] == 'accept': accept_terms.append( self._TERM(term, filter_name, filter_type, direction)) elif term.action[0] == 'deny' or term.action[0] == 'reject': deny_terms.append( self._TERM(term, filter_name, filter_type, direction)) self.pcap_policies.append( (header, filter_name, filter_type, accept_terms, deny_terms))
def _HandleIcmpTypes(self, icmp_types, protocols): if icmp_types: raise aclgenerator.UnsupportedFilterError( '\n%s %s %s %s' % ('icmp types unsupported by', self._PLATFORM, '\nError in term:', self.term.name)) return ([''], protocols)
def _TranslatePolicy(self, pol, exp_info): self.pf_policies = [] self.address_book = {} self.def_short_to_long = {} current_date = datetime.datetime.utcnow().date() exp_info_date = current_date + datetime.timedelta(weeks=exp_info) good_afs = ['inet', 'inet6', 'mixed'] good_options = ['in', 'out', 'nostate'] all_protocols_stateful = True for header, terms in pol.filters: filter_type = None if self._PLATFORM not in header.platforms: continue filter_options = header.FilterOptions(self._PLATFORM)[1:] filter_name = header.FilterName(self._PLATFORM) direction = '' # ensure all options after the filter name are expected for opt in filter_options: if opt not in good_afs + good_options: raise aclgenerator.UnsupportedTargetOption( '%s %s %s %s' % ('\nUnsupported option found in', self._PLATFORM, 'target definition:', opt)) # pf will automatically add 'keep state flags S/SA' to all TCP connections # by default. if 'nostate' in filter_options: all_protocols_stateful = False if 'in' in filter_options: direction = 'in' elif 'out' in filter_options: direction = 'out' # Check for matching af for address_family in good_afs: if address_family in filter_options: # should not specify more than one AF in options if filter_type is not None: raise aclgenerator.UnsupportedFilterError( '%s %s %s %s' % ('\nMay only specify one of', good_afs, 'in filter options:', filter_options)) filter_type = address_family if filter_type is None: filter_type = 'inet' # add the terms new_terms = [] term_names = set() for term in terms: term.name = self.FixTermLength(term.name) if term.name in term_names: raise DuplicateTermError('You have a duplicate term: %s' % term.name) term_names.add(term.name) for source_addr in term.source_address: src_token = source_addr.parent_token[:self._DEF_MAX_LENGTH] if (src_token in self.def_short_to_long and self.def_short_to_long[src_token] != source_addr.parent_token): raise DuplicateShortenedTableName( 'There is a shortened name conflict between names %s and %s ' '(different named objects would conflict when shortened to %s)' % (self.def_short_to_long[src_token], source_addr.parent_token, src_token)) else: self.def_short_to_long[ src_token] = source_addr.parent_token if src_token not in self.address_book: self.address_book[src_token] = set([source_addr]) else: self.address_book[src_token].add(source_addr) for dest_addr in term.destination_address: dst_token = dest_addr.parent_token[:self._DEF_MAX_LENGTH] if (dst_token in self.def_short_to_long and self.def_short_to_long[dst_token] != dest_addr.parent_token): raise DuplicateShortenedTableName( 'There is a shortened name conflict between names %s and %s ' '(different named objects would conflict when shortened to %s)' % (self.def_short_to_long[dst_token], dest_addr.parent_token, dst_token)) else: self.def_short_to_long[ dst_token] = dest_addr.parent_token if dst_token not in self.address_book: self.address_book[dst_token] = set([dest_addr]) else: self.address_book[dst_token].add(dest_addr) if not term: continue if term.expiration: if term.expiration <= exp_info_date: logging.info( 'INFO: Term %s in policy %s expires ' 'in less than two weeks.', term.name, filter_name) if term.expiration <= current_date: logging.warn( 'WARNING: Term %s in policy %s is expired and ' 'will not be rendered.', term.name, filter_name) continue new_terms.append( self._TERM(term, filter_name, all_protocols_stateful, filter_type, direction)) self.pf_policies.append( (header, filter_name, filter_type, new_terms))
def __str__(self): """Render config output from this term object.""" # Verify platform specific terms. Skip whole term if platform does not # match. if self.term.platform: if self._PLATFORM not in self.term.platform: return '' if self.term.platform_exclude: if self._PLATFORM in self.term.platform_exclude: return '' ret_str = [] self._SetDefaultAction() # Create a new term ret_str.append('\n# term %s' % self.term.name) comments = aclgenerator.WrapWords(self.term.comment, 80) # append comments to output if comments and comments[0]: for line in comments: ret_str.append('# %s' % str(line)) if str(self.term.action[0]) not in self._ACTION_TABLE: raise aclgenerator.UnsupportedFilterError( '%s %s %s %s' % ('\n', self.term.name, self.term.action[0], 'action not currently supported.')) if self.direction and str(self.direction) not in self._DIRECTION_TABLE: raise aclgenerator.UnsupportedFilterError( '%s %s %s %s' % ('\n', self.term.name, self.term.direction, 'direction not currently supported.')) # protocol if self.term.protocol: protocol = self.term.protocol else: protocol = [] # source address term_saddrs = self._CheckAddressAf(self.term.source_address) if not term_saddrs: logging.debug( self.NO_AF_LOG_ADDR.substitute(term=self.term.name, direction='source', af=self.af)) return '' term_saddr = self._GenerateAddrStatement( term_saddrs, self.term.source_address_exclude) # destination address term_daddrs = self._CheckAddressAf(self.term.destination_address) if not term_daddrs: logging.debug( self.NO_AF_LOG_ADDR.substitute(term=self.term.name, direction='destination', af=self.af)) return '' term_daddr = self._GenerateAddrStatement( term_daddrs, self.term.destination_address_exclude) # ports source_port = [] destination_port = [] if self.term.source_port: source_port = self._GeneratePortStatement(self.term.source_port) if self.term.destination_port: destination_port = self._GeneratePortStatement( self.term.destination_port) # icmp-type icmp_types = [''] if self.term.icmp_type: if self.af != 'mixed': af = self.af elif protocol == ['icmp']: af = 'inet' elif protocol == ['icmpv6']: af = 'inet6' else: raise aclgenerator.UnsupportedFilterError( '%s %s %s' % ('\n', self.term.name, 'icmp protocol is not defined or not supported.')) icmp_types = self.NormalizeIcmpTypes(self.term.icmp_type, protocol, af) # options tcp_flags_set = [] tcp_flags_check = [] for next_opt in [str(x) for x in self.term.option]: for next_flag in self._TCP_FLAGS_TABLE: if next_opt.find(next_flag) == 0: if protocol != ['tcp']: raise aclgenerator.UnsupportedFilterError( '%s %s %s' % ('\n', self.term.name, 'tcp flags may only be specified with tcp protocol.' )) tcp_flags_set.append(self._TCP_FLAGS_TABLE.get(next_flag)) tcp_flags_check.append( self._TCP_FLAGS_TABLE.get(next_flag)) # If tcp-established is set, override any of the flags above with the # S/SA flags. Issue an error if flags are specified with 'established'. for opt in [str(x) for x in self.term.option]: if opt == 'established' or opt == 'tcp-established': if tcp_flags_set or tcp_flags_check: raise aclgenerator.UnsupportedFilterError( '%s %s %s' % ('\n', self.term.name, 'tcp flags may not be specified with tcp-established.' )) # We need to set 'flags A/A' for established regardless of whether or # not we're stateful: # - if we stateful, the default is 'flags S/SA' which prevent writing # rules for reply packets. # - if we're stateless, this is the only way to do it. if not protocol or 'tcp' in protocol: tcp_flags_set.append(self._TCP_FLAGS_TABLE.get('ack')) tcp_flags_check.append(self._TCP_FLAGS_TABLE.get('ack')) # The default behavior of pf is 'keep state flags S/SA'. If we're not # stateless, and if flags have not been specified explicitly via options, # append that here. Note that pf allows appending flags for udp and icmp; # they are just ignored, as long as TCP is in the proto. This lets you # doing things like 'proto { tcp udp icmp } flags S/SA' and have the flags # only applied to the tcp bits that match. However, the policy description # language prohibits setting flags on non-TCP, since it doesn't make sense # on all platforms. if ((not protocol or protocol == ['tcp']) and self.stateful and not tcp_flags_set and not tcp_flags_check): tcp_flags_set.append(self._TCP_FLAGS_TABLE.get('syn')) tcp_flags_check.append(self._TCP_FLAGS_TABLE.get('syn')) tcp_flags_check.append(self._TCP_FLAGS_TABLE.get('ack')) ret_str.extend( self._FormatPart( self.term.action[0], self.direction, self.term.logging, self.af, protocol, term_saddr, source_port, term_daddr, destination_port, tcp_flags_set, tcp_flags_check, icmp_types, self.options, self.stateful, )) return '\n'.join(str(v) for v in ret_str if v is not '')
def __str__(self): # Verify platform specific terms. Skip whole term if platform does not # match. if self.term.platform: if self._PLATFORM not in self.term.platform: return '' if self.term.platform_exclude: if self._PLATFORM in self.term.platform_exclude: return '' ret_str = [] # Don't render icmpv6 protocol terms under inet, or icmp under inet6 if ((self.af == 'inet6' and 'icmp' in self.term.protocol) or (self.af == 'inet' and 'icmpv6' in self.term.protocol)): logging.debug( self.NO_AF_LOG_PROTO.substitute(term=self.term.name, proto=self.term.protocol, af=self.af)) return '' # append comments to output ret_str.append( self._COMMENT_FORMAT.substitute(filter=self.filter, term=self.term_name, comment=self.term.comment)) # if terms does not specify action, use filter default action if not self.term.action: self.term.action[0].value = self.default_action if self.term.action[0] == 'next': return '' if len(self.term.action) > 1: raise aclgenerator.UnsupportedFilterError( '\n%s %s %s %s' % ('Multiple actions unsupported by', self._PLATFORM, '\nError in term:', self.term.name)) # protocol if self.term.protocol: protocols = self.term.protocol else: protocols = ['any'] # addresses src_addr = self.term.source_address if not src_addr: src_addr = [self._all_ips] dst_addr = self.term.destination_address if not dst_addr: dst_addr = [self._all_ips] if (self.term.source_address_exclude or self.term.destination_address_exclude): raise aclgenerator.UnsupportedFilterError( '\n%s %s %s %s' % ('address exclusions unsupported by', self._PLATFORM, '\nError in term:', self.term.name)) # ports = Map the ports in a straight list since multiports aren't supported (src_ports, dst_ports) = self._HandlePorts(self.term.source_port, self.term.destination_port) # The windows ipsec driver requires either 'tcp' or 'udp' to be specified # if a srcport or dstport is specified. Fail if src or dst ports are # specified and of the protocols are not exactly one or both of 'tcp' # or 'udp'. if ((not set(protocols).issubset(set(['tcp', 'udp']))) and (len(src_ports) > 1 or len(dst_ports) > 1)): raise aclgenerator.UnsupportedFilterError('%s %s %s' % ( '\n', self.term.name, 'src or dst ports may only be specified with "tcp" and/or "udp".' )) # icmp-types (icmp_types, protocols) = self._HandleIcmpTypes(self.term.icmp_type, protocols) ret_str = [] self._HandlePreRule(ret_str) self._CartesianProduct(src_addr, dst_addr, protocols, icmp_types, src_ports, dst_ports, ret_str) self._HandlePreRule(ret_str) return '\n'.join(str(v) for v in ret_str if v)
def _TranslatePolicy(self, pol, exp_info): """Translate a policy from objects into strings.""" self.windows_policies = [] current_date = datetime.datetime.utcnow().date() exp_info_date = current_date + datetime.timedelta(weeks=exp_info) default_action = None good_default_actions = ['permit', 'block'] good_options = [] for header, terms in pol.filters: filter_type = None if self._PLATFORM not in header.platforms: continue filter_options = header.FilterOptions(self._PLATFORM)[1:] filter_name = header.FilterName(self._PLATFORM) # ensure all options after the filter name are expected for opt in filter_options: if opt not in good_default_actions + self._GOOD_AFS + good_options: raise aclgenerator.UnsupportedTargetOptionError( '%s %s %s %s' % ('\nUnsupported option found in', self._PLATFORM, 'target definition:', opt)) # Check for matching af for address_family in self._GOOD_AFS: if address_family in filter_options: # should not specify more than one AF in options if filter_type is not None: raise aclgenerator.UnsupportedFilterError( '%s %s %s %s' % ('\nMay only specify one of', self._GOOD_AFS, 'in filter options:', filter_options)) filter_type = address_family if filter_type is None: filter_type = 'inet' # does this policy override the default filter actions? for next_target in header.target: if next_target.platform == self._PLATFORM: if len(next_target.options) > 1: for arg in next_target.options: if arg in good_default_actions: default_action = arg if default_action and default_action not in good_default_actions: raise aclgenerator.UnsupportedTargetOptionError( '%s %s %s %s %s' % ('\nOnly', ', '.join(good_default_actions), 'default filter action allowed;', default_action, 'used.')) # add the terms new_terms = [] term_names = set() for term in terms: if term.name in term_names: raise aclgenerator.DuplicateTermError( 'You have a duplicate term: %s' % term.name) term_names.add(term.name) if term.expiration: if term.expiration <= exp_info_date: logging.info( 'INFO: Term %s in policy %s expires ' 'in less than two weeks.', term.name, filter_name) if term.expiration <= current_date: logging.warning( 'WARNING: Term %s in policy %s is expired and ' 'will not be rendered.', term.name, filter_name) continue if 'established' in term.option or 'tcp-established' in term.option: continue new_terms.append( self._TERM(term, filter_name, default_action, filter_type)) self.windows_policies.append( (header, filter_name, filter_type, default_action, new_terms))