def compare(self, rule_d): """ Compare two rules :param rule_d: Rule to be compared :return: 0 -> If the rule is not equal 1 -> If the rule (syntactically) is the same 2 -> If even the name of the rule is the same """ if self.DEBUG: tools_debug(self.DEBUG, 'rule.compare', 'entering') data_rule = rule_d.get_rule() result = 0 if self.source_address == data_rule[1] and \ self.destination_address == data_rule[2] and \ self.destination_port == data_rule[3] and \ self.source_port == data_rule[4] and \ self.protocol == data_rule[5] and \ self.permit == data_rule[6] and \ self.wildcard == data_rule[7]: result = 1 if self.name == data_rule[0]: result = 2 if self.DEBUG: tools_debug(self.DEBUG, 'rule.compare', 'result', result) return result
def link(self, sIP, dIP, dPort, sPort, proto, rules_exclude=[], show_deny=False, hide_allow_all=True, showallmatches=False, ignore_dsmo=False, anyport=False, strict_search=False, is_0_any=True): """ Check a flow against the policy :param sIP: Source IP :param dIP: Destination IP :param dPort: Destination Port :param sPort: Source Port :param proto: Protocol :param rules_exclude: Allow to exclude rules from being checked :param show_deny: Will show if a DENY is matched :param hide_allow_all: HIDE rules PERMIT ANY ANY :param showallmatches: will show all rules in the policy that match the flow (not just the first one) :param ignore_dsmo: Will ignore a match if the rule has a non-contiguous wildcard :param anyport: anyport matched will match the rule (usually with port ranges) :param strict_search: It makes a the search of the rules STRICT, so when we look for ANY in Source/Dest (0.0.0.0/0), only ANY will match (we are not looking for any value but for the 0.0.0.0/0.0.0.0 value). Also it applies to protocols: if we are looking for IP protocol, only IP protocol will match, but if we are looking for TCP, TCP and IP will match. if Source IP or Destination IP is 0.0.0.0 and we want to match them with rules with ANY (or equivalent) in the rule :param is_0_any: FALSE when sIP/dIP is 0.0.0.0 but is the HOST 0.0.0.0/255.255.255.255 (or 0.0.0.0/0.0.0.0 with wildcards) :return: list of rules found """ rulef = 0 rules_found = [] for rule in self.rules: if self.DEBUG: if self.rules[0] == '<empty>': print '<empty>' if not self.rules[0] == '<empty>': rulef = rule.check_ip_acl(sIP, dIP, dPort, sPort, proto, show_deny, hide_allow_all, anyport, strict_search, is_0_any) if self.DEBUG: tools_debug(self.DEBUG, 'link', 'Result check_ip_acl:', rulef) if rulef > 0: if rules_exclude and rulef in rules_exclude: continue if rule.get_contiguous() > 0 and ignore_dsmo: continue rules_found.append(rulef) if not showallmatches: break return rules_found
def _checkPort(self, DP, Port, anyport, strict_search): """ Check if "Port" is matched or not for a Source or Destination port of the rule (DP switch) :param DP: TRUE -> Destination Port, FALSE -> Source Port :param Port: Port to check :param anyport: switch to know if with any port matched is enough (usually with a port range) :return: TRUE/FALSE """ lport = False if DP: list_port = self.destination_port else: list_port = self.source_port if self.DEBUG: tools_debug(self.DEBUG, '_checkPort', DP, Port, list_port, anyport, strict_search) # Port with value 70000 means ANY if not strict_search and (Port == '0' or list_port == '0'): lport = True elif strict_search and (Port == '0' and list_port == '0'): lport = True else: for tport in list_port.split(','): if '-' in Port: Port1 = Port.split('-')[0] Port2 = Port.split('-')[1] # We are checking a port range if '-' not in tport: if anyport: lport = int(Port1) <= int(tport) <= int(Port2) # If not anyport is checked, this won't match, so no "else". else: if anyport: # dPort is bigger or dPort1 is inside the rule range or dPort2 is inside the rule range lport = int(Port1) <= int(tport.split('-')[0]) and int(tport.split('-')[1]) <= int(Port2) or \ int(tport.split('-')[0]) <= int(Port1) <= int(tport.split('-')[1]) or \ int(tport.split('-')[0]) <= int(Port2) <= int(tport.split('-')[1]) else: lport = int(tport.split('-')[0]) <= int( Port1) and int(Port2) <= int( tport.split('-')[1]) else: if '-' in tport: lport = int(tport.split('-')[0]) <= int(Port) <= int( tport.split('-')[1]) else: lport = int(tport) == int(Port) if lport: break return lport
def remove_rule(self, rule_number): if self.DEBUG: tools_debug(self.DEBUG, 'find_rule', 'Entering remove_rule', rule_number) if self.rules[0] == '<empty>': return False for rule in self.rules: if rule.get_rule_number() == rule_number: self.rules.remove(rule) break if len(self.rules) == 0: self.rules = ['<empty>'] return True
def last_deny(self): """ Check if the last rule is DENY ANY ANY :return: TRUE/FALSE """ if self.DEBUG: tools_debug(self.DEBUG, 'last_deny', 'Entering') if self.rules[0] == '<empty>': return False last = self.rules[-1].get_rule() if not last[6]: # DENY if last[7]: # Wildcard return last[1] == '0.0.0.0/255.255.255.255' and last[2] == '0.0.0.0/255.255.255.255' else: return last[1] == '0.0.0.0/0.0.0.0' and last[2] == '0.0.0.0/0.0.0.0' return False
def remove_shadowed_rules(self): """ Check for a "basic" shadowed. It's only going to remove those rules that are fully shadowed. This method requires that the rules were split before :return: Number of removed rules (None in case of any error) """ num_rules_removed = 0 list_rules_removed = {} if self.DEBUG: tools_debug(self.DEBUG, 'remove_shadowed_rules', 'Entering"') if self.rules[0] == '<empty>': return list_rules_removed num_rule = len(self.rules) while num_rule > 0: rule = self.rules[num_rule-1].get_rule() # This method requires that the rules were split before if ',' in rule[1] or ',' in rule[2]: return None if rule[6]: # Only for Permit rules if rule[7]: # If it's a wildcard we need to change it # Source ip = rule[1].split('/')[0] wild = rule[1].split('/')[1] mask = mask_to_wild(wild) # This function is "bidirectional" rule[1] = ip + '/' + mask # Destination ip = rule[2].split('/')[0] wild = rule[2].split('/')[1] mask = mask_to_wild(wild) # This function is "bidirectional" rule[2] = ip + '/' + mask check = self.link(rule[1], rule[2], rule[3], rule[4], rule[5], rules_exclude=[num_rule], show_deny=True, hide_allow_all=False, strict_search=True, is_0_any=True, anyport=True) if len(check) > 0: t_rule = self.rules[check[0]-1].get_rule() if t_rule[6]: if self.DEBUG: tools_debug(self.DEBUG, 'remove_shadowed_rules', 'Removing rule', num_rule) list_rules_removed[rule[0]] = self.rules[check[0]-1].get_rule()[0] self.remove_rule(num_rule) self.renum_policy() num_rules_removed += 1 else: if self.DEBUG: tools_debug(self.DEBUG, 'remove_shadowed_rules', 'Matched with DENY. NOT removing', num_rule) num_rule -= 1 return list_rules_removed
def split_ips(self): """ Some ACLs (like Juniper) allow to have multiple IPs in Source and Destination. This function split all these rules in rules with only one source and only one destination, adding some extra information in the name to identify them The name of the rules will be changed to: - <original rule name>{<original rule number>{<counter>{[<source_ip>,<destination_ip>] :return: True """ if self.DEBUG: tools_debug(self.DEBUG, 'split_ips', 'Entering split_ips') cont_rules = 0 for rule in self.rules: cont_rules += 1 if self.DEBUG: if self.rules[0] == '<empty>': print '<empty>' else: rule.print_rule() if not self.rules[0] == '<empty>': rule_data = rule.get_rule() if rule_data[0].startswith( '^' ): # Special rule, usually empty, we don't need to check it continue if ',' not in rule_data[1] and ',' not in rule_data[2]: continue if ',' in rule_data[1]: ips_list = rule_data[1].split(',') else: ips_list = [rule_data[1]] if ',' in rule_data[2]: ipd_list = rule_data[2].split(',') else: ipd_list = [rule_data[2]] rule_number = cont_rules num_split = 0 for ips in ips_list: for ipd in ipd_list: num_split += 1 rule_name = rule_data[0] + '{' + str( cont_rules) + '{' + str(num_split) + '{' + str( [ips, ipd]) if rule_number == cont_rules: ''' We need to split the current rule into multiple rules, so instead of remove the current one and add all the split, we change the current one with the data of the first "new rule" ''' rule.set_source(ips) rule.set_destination(ipd) rule.set_name(rule_name) else: newrule = FWRule() newrule.set_rule_data(sAddress=ips, dAddress=ipd, dPort=rule_data[3], sPort=rule_data[4], protocol=rule_data[5], ACCEPT=rule_data[6], wildcard=False, name=rule_name, rulenumber=rule_number) if self.DEBUG: newrule.set_debug() self.rules.insert(rule_number - 1, newrule) rule_number += 1 self.renum_policy() return True
def check_ip_acl(self, sIP, dIP, dPort, sPort, proto, show_deny_any, hide_allow_all, anyport, strict_search, is_0_any): """ Check a flow in a rule :param sIP: Source IP :param dIP: Destination IP :param dPort: Destination Port :param sPort: Source Port :param proto: Protocol :param show_deny_any: Switch to SHOW a match in a DENY ALL ALL :param hide_allow_all: Switch to HIDE any PERMIT ALL ALL :param anyport: switch to match any port (usually for port ranges) :param strict_search: True/False (check explanation in link) :param is_0_any: FALSE when sIP/dIP is 0.0.0.0 but is the HOST 0.0.0.0/255.255.255.255 (or 0.0.0.0/0.0.0.0 with wildcards) :return: Integer (rule number matched) """ def _any_in_source(): return ('any' in self.source_address) or \ (self.wildcard and self.source_address == '0.0.0.0/255.255.255.255') or \ (not self.wildcard and self.source_address == '0.0.0.0/0.0.0.0') def _any_in_dest(): return ('any' in self.destination_address) or \ (self.wildcard and self.destination_address == '0.0.0.0/255.255.255.255') or \ (not self.wildcard and self.destination_address == '0.0.0.0/0.0.0.0') def _check_ip(origin_ip, ip_to_check, wildcard): match = False for ip in origin_ip.split(','): if ':' in ip: # IPv6 pass else: netT = ip.split('/')[0] filtT = ip.split('/')[1] if '/' in ip_to_check: if '.' not in ip_to_check.split('/')[1]: ip_to_check = ip_to_check.split( '/')[0] + '/' + cidr_to_mask( ip_to_check.split('/')[1]) if wildcard: ip_to_check = ip_to_check.split( '/')[0] + '/' + mask_to_wild( ip_to_check.split('/')[1]) match = self._checkNetWild(netT, filtT, ip_to_check) else: match = self._checkNetMask(netT, filtT, ip_to_check) else: if wildcard: match = self._checkIPWild(netT, filtT, ip_to_check) else: match = self._checkIPMask(netT, filtT, ip_to_check) if match: break return match if self.DEBUG: print tools_debug(self.DEBUG, 'check_ip_acl', 'sIP:', sIP, 'dIP:', dIP, 'dPort:', dPort, 'sPort:', sPort, 'proto:', proto, 'show_deny_any:', show_deny_any, 'hide_allow_all:', hide_allow_all, 'anyport:', anyport, 'strict_search:', strict_search, 'is_0_any:', is_0_any) self.print_rule() match = False checked = False if not show_deny_any and not self.permit: if _any_in_source() and _any_in_dest(): return 0 if hide_allow_all and self.permit: if _any_in_source() and _any_in_dest(): return 0 if self.source_address == '' and self.destination_address == '': return 0 if self.protocol == 'ip' or self.protocol == proto or ( proto == 'ip' and not strict_search): # Link doesn't allow to check from the command line # Source IP = 0.0.0.0 and Destination IP = 0.0.0.0 # so if both are 0.0.0.0 it means that it's being used as module # In this case, the only possible matches are also any any in source and destination if sIP == '0.0.0.0' and dIP == '0.0.0.0': if _any_in_source() and _any_in_dest(): # If source/dest are any, then check ports match = True else: return 0 else: # Verifying that if strict_search = TRUE then when we have an ANY we are searching for 0.0.0.0 if strict_search and is_0_any: if sIP == '0.0.0.0' and not _any_in_source(): return 0 if dIP == '0.0.0.0' and not _any_in_dest(): return 0 if _any_in_source() and _any_in_dest(): match = True checked = True if not match and _any_in_source(): checked = True if dIP == '0.0.0.0' and is_0_any: match = True else: match = _check_ip(self.destination_address, dIP, self.wildcard) if not checked and _any_in_dest(): checked = True if sIP == '0.0.0.0' and is_0_any: match = True else: match = _check_ip(self.source_address, sIP, self.wildcard) if not checked: match = _check_ip( self.source_address, sIP, self.wildcard) and _check_ip( self.destination_address, dIP, self.wildcard) if match: if self.protocol == 'icmp' or self.protocol == 'ip': return self.rulenumber if self.protocol == 'vrrp' or self.protocol == '112': return self.rulenumber # If a dPort or sPort is specified in the command line, then it makes sense to check the ports when # the protocol is TCP/UDP if (dPort != '0' or sPort != '0') and (self.protocol != 'udp' and self.protocol != 'tcp'): return 0 result = True for tport in dPort.split(','): result = result and self._checkPort(True, tport, anyport, strict_search) if result: # If still TRUE for tport in sPort.split(','): result = result and self._checkPort( False, tport, anyport, strict_search) if result: return self.rulenumber return 0