def _build_to_or_from_chain(endpoint_id, profile_ids, chain_name, direction, expected_mac=None): # Ensure the MARK is set to 0 when we start so that unmatched packets will # be dropped. chain = ["--append %s --jump MARK --set-mark 0" % chain_name] if expected_mac: _log.debug("Policing source MAC: %s", expected_mac) chain.append( "--append %s --match mac ! --mac-source %s --jump DROP " '--match comment --comment "Incorrect source MAC"' % (chain_name, expected_mac) ) # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN the packet with MARK==1, indicating it accepted the packet. In # which case, we RETURN and skip further profiles. # * RETURN the packet with MARK==0, indicating it did not match the packet. # In which case, we carry on and process the next profile. deps = set() for profile_id in profile_ids: profile_chain = profile_to_chain_name(direction, profile_id) deps.add(profile_chain) chain.append("--append %s --jump %s" % (chain_name, profile_chain)) # If the profile accepted the packet, it sets MARK==1. Immediately # RETURN the packet to signal that it's been accepted. chain.append( "--append %s --match mark --mark 1/1 " '--match comment --comment "Profile accepted packet" ' "--jump RETURN" % chain_name ) # Default drop rule. chain.append(commented_drop_fragment(chain_name, "Default DROP if no match (endpoint %s):" % endpoint_id)) return chain, deps
def _stub_drop_rules(chain): """ :return: List of rule fragments to replace the given chain with a single drop rule. """ return ["--flush %s" % chain, frules.commented_drop_fragment(chain, 'WARNING Missing chain DROP:')]
def _build_to_or_from_chain(endpoint_id, profile_ids, chain_name, direction, expected_mac=None): # Ensure the MARK is set to 0 when we start so that unmatched packets will # be dropped. chain = ["--append %s --jump MARK --set-mark 0" % chain_name] if expected_mac: _log.debug("Policing source MAC: %s", expected_mac) chain.append('--append %s --match mac ! --mac-source %s --jump DROP ' '--match comment --comment "Incorrect source MAC"' % (chain_name, expected_mac)) # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN the packet with MARK==1, indicating it accepted the packet. In # which case, we RETURN and skip further profiles. # * RETURN the packet with MARK==0, indicating it did not match the packet. # In which case, we carry on and process the next profile. deps = set() for profile_id in profile_ids: profile_chain = profile_to_chain_name(direction, profile_id) deps.add(profile_chain) chain.append("--append %s --jump %s" % (chain_name, profile_chain)) # If the profile accepted the packet, it sets MARK==1. Immediately # RETURN the packet to signal that it's been accepted. chain.append('--append %s --match mark --mark 1/1 ' '--match comment --comment "Profile accepted packet" ' '--jump RETURN' % chain_name) # Default drop rule. chain.append( commented_drop_fragment( chain_name, "Default DROP if no match (endpoint %s):" % endpoint_id)) return chain, deps
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac, profile_ids): to_chain_name, from_chain_name = chain_names(suffix) # First build the chain that manages packets to the interface. # Common chain prefixes. Allow IPv6 ICMP and conntrack rules. to_chain = ["--flush %s" % to_chain_name] if ip_version == 6: # In ipv6 only, there are 6 rules that need to be created first. # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 130 # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 131 # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 132 # RETURN ipv6-icmp anywhere anywhere ipv6-icmp router-advertisement # RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-solicitation # RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-advertisement # # These rules are ICMP types 130, 131, 132, 134, 135 and 136. for icmp_type in ["130", "131", "132", "134", "135", "136"]: to_chain.append("--append %s --jump RETURN " "--protocol ipv6-icmp " "--icmpv6-type %s" % (to_chain_name, icmp_type)) to_chain.append("--append %s --match conntrack --ctstate INVALID " "--jump DROP" % to_chain_name) to_chain.append("--append %s --match conntrack " "--ctstate RELATED,ESTABLISHED --jump RETURN" % to_chain_name) # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN without MARKing the packet; indicates that the packet was # accepted. # * RETURN with a mark on the packet, indicates reaching the end # of the chain. to_deps = set() for profile_id in profile_ids: profile_in_chain = profile_to_chain_name("inbound", profile_id) to_deps.add(profile_in_chain) to_chain.append("--append %s --jump MARK --set-mark 0" % to_chain_name) to_chain.append("--append %s --jump %s" % (to_chain_name, profile_in_chain)) to_chain.append('--append %s --match mark ! --mark 1/1 ' '--match comment --comment "No mark means profile ' 'accepted packet" --jump RETURN' % to_chain_name) # Default drop rule. to_chain.append(commented_drop_fragment(to_chain_name, "Endpoint %s:" % endpoint_id)) # Now the chain that manages packets from the interface... from_chain = ["--flush %s" % from_chain_name] if ip_version == 6: # In ipv6 only, allow all ICMP traffic from this endpoint to anywhere. from_chain.append("--append %s --protocol ipv6-icmp --jump RETURN" % from_chain_name) # Conntrack rules. from_chain.append("--append %s --match conntrack --ctstate INVALID " "--jump DROP" % from_chain_name) from_chain.append("--append %s --match conntrack " "--ctstate RELATED,ESTABLISHED --jump RETURN" % from_chain_name) if ip_version == 4: from_chain.append("--append %s --protocol udp --sport 68 --dport 67 " "--jump RETURN" % from_chain_name) else: assert ip_version == 6 from_chain.append("--append %s --protocol udp --sport 546 --dport 547 " "--jump RETURN" % from_chain_name) # Combined anti-spoofing and jump to profile rules. The only way to # get to a profile chain is to have the correct IP and MAC address. from_deps = set() for profile_id in profile_ids: profile_out_chain = profile_to_chain_name("outbound", profile_id) from_deps.add(profile_out_chain) from_chain.append("--append %s --jump MARK --set-mark 0" % from_chain_name) for ip in local_ips: if "/" in ip: cidr = ip else: cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN without MARKing the packet; indicates that the packet # was accepted. # * RETURN with a mark on the packet, indicates reaching the end # of the chain. from_chain.append("--append %s --src %s --match mac " "--mac-source %s --jump %s" % (from_chain_name, cidr, mac.upper(), profile_out_chain)) from_chain.append('--append %s --match mark ! --mark 1/1 ' '--match comment --comment "No mark means profile ' 'accepted packet" --jump RETURN' % from_chain_name) # Final default DROP if no profile RETURNed or no MAC matched. drop_frag = commented_drop_fragment(from_chain_name, "Default DROP if no match (endpoint %s):" % endpoint_id) from_chain.append(drop_frag) updates = {to_chain_name: to_chain, from_chain_name: from_chain} deps = {to_chain_name: to_deps, from_chain_name: from_deps} return updates, deps
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac, profile_ids): to_chain_name, from_chain_name = chain_names(suffix) # First build the chain that manages packets to the interface. # Common chain prefixes. to_chain = [] # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN without MARKing the packet; indicates that the packet was # accepted. # * RETURN with a mark on the packet, indicates reaching the end # of the chain. to_deps = set() for profile_id in profile_ids: profile_in_chain = profile_to_chain_name("inbound", profile_id) to_deps.add(profile_in_chain) to_chain.append("--append %s --jump MARK --set-mark 0" % to_chain_name) to_chain.append("--append %s --jump %s" % (to_chain_name, profile_in_chain)) to_chain.append('--append %s --match mark ! --mark 1/1 ' '--match comment --comment "No mark means profile ' 'accepted packet" --jump RETURN' % to_chain_name) # Default drop rule. to_chain.append(commented_drop_fragment(to_chain_name, "Endpoint %s:" % endpoint_id)) # Now the chain that manages packets from the interface... from_chain = [] # Combined anti-spoofing and jump to profile rules. The only way to # get to a profile chain is to have the correct IP and MAC address. from_deps = set() for profile_id in profile_ids: profile_out_chain = profile_to_chain_name("outbound", profile_id) from_deps.add(profile_out_chain) from_chain.append("--append %s --jump MARK --set-mark 0" % from_chain_name) for ip in local_ips: if "/" in ip: cidr = ip else: cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN without MARKing the packet; indicates that the packet # was accepted. # * RETURN with a mark on the packet, indicates reaching the end # of the chain. from_chain.append("--append %s --src %s --match mac " "--mac-source %s --jump %s" % (from_chain_name, cidr, mac.upper(), profile_out_chain)) from_chain.append('--append %s --match mark ! --mark 1/1 ' '--match comment --comment "No mark means profile ' 'accepted packet" --jump RETURN' % from_chain_name) # Final default DROP if no profile RETURNed or no MAC matched. drop_frag = commented_drop_fragment(from_chain_name, "Default DROP if no match (endpoint %s):" % endpoint_id) from_chain.append(drop_frag) updates = {to_chain_name: to_chain, from_chain_name: from_chain} deps = {to_chain_name: to_deps, from_chain_name: from_deps} return updates, deps
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac, profile_ids): to_chain_name, from_chain_name = chain_names(suffix) # First build the chain that manages packets to the interface. # Common chain prefixes. to_chain = [] # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN without MARKing the packet; indicates that the packet was # accepted. # * RETURN with a mark on the packet, indicates reaching the end # of the chain. to_deps = set() for profile_id in profile_ids: profile_in_chain = profile_to_chain_name("inbound", profile_id) to_deps.add(profile_in_chain) to_chain.append("--append %s --jump MARK --set-mark 0" % to_chain_name) to_chain.append("--append %s --jump %s" % (to_chain_name, profile_in_chain)) to_chain.append('--append %s --match mark ! --mark 1/1 ' '--match comment --comment "No mark means profile ' 'accepted packet" --jump RETURN' % to_chain_name) # Default drop rule. to_chain.append( commented_drop_fragment(to_chain_name, "Endpoint %s:" % endpoint_id)) # Now the chain that manages packets from the interface... from_chain = [] # Combined anti-spoofing and jump to profile rules. The only way to # get to a profile chain is to have the correct IP and MAC address. from_deps = set() for profile_id in profile_ids: profile_out_chain = profile_to_chain_name("outbound", profile_id) from_deps.add(profile_out_chain) from_chain.append("--append %s --jump MARK --set-mark 0" % from_chain_name) for ip in local_ips: if "/" in ip: cidr = ip else: cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip # Jump to each profile in turn. The profile will do one of the # following: # * DROP the packet; in which case we won't see it again. # * RETURN without MARKing the packet; indicates that the packet # was accepted. # * RETURN with a mark on the packet, indicates reaching the end # of the chain. from_chain.append( "--append %s --src %s --match mac " "--mac-source %s --jump %s" % (from_chain_name, cidr, mac.upper(), profile_out_chain)) from_chain.append('--append %s --match mark ! --mark 1/1 ' '--match comment --comment "No mark means profile ' 'accepted packet" --jump RETURN' % from_chain_name) # Final default DROP if no profile RETURNed or no MAC matched. drop_frag = commented_drop_fragment( from_chain_name, "Default DROP if no match (endpoint %s):" % endpoint_id) from_chain.append(drop_frag) updates = {to_chain_name: to_chain, from_chain_name: from_chain} deps = {to_chain_name: to_deps, from_chain_name: from_deps} return updates, deps
def _get_endpoint_rules(suffix, iface, ip_version, local_ips, mac, profile_id): to_chain_name, from_chain_name = chain_names(suffix) to_chain = ["--flush %s" % to_chain_name] if ip_version == 6: # In ipv6 only, there are 6 rules that need to be created first. # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 130 # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 131 # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 132 # RETURN ipv6-icmp anywhere anywhere ipv6-icmp router-advertisement # RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-solicitation # RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-advertisement # # These rules are ICMP types 130, 131, 132, 134, 135 and 136, and can # be created on the command line with something like : # ip6tables -A plw -j RETURN --protocol ipv6-icmp --icmpv6-type 130 for icmp_type in ["130", "131", "132", "134", "135", "136"]: to_chain.append("--append %s --jump RETURN " "--protocol ipv6-icmp " "--icmpv6-type %s" % (to_chain_name, icmp_type)) to_chain.append("--append %s --match conntrack --ctstate INVALID " "--jump DROP" % to_chain_name) to_chain.append("--append %s --match conntrack " "--ctstate RELATED,ESTABLISHED --jump RETURN" % to_chain_name) assert profile_id, "Profile ID should be set, not %s" % profile_id profile_in_chain = profile_to_chain_name("inbound", profile_id) to_chain.append("--append %s --goto %s" % (to_chain_name, profile_in_chain)) to_deps = set([profile_in_chain]) # Now the chain that manages packets from the interface... from_chain = ["--flush %s" % from_chain_name] if ip_version == 6: # In ipv6 only, allows all ICMP traffic from this endpoint to anywhere. from_chain.append("--append %s --protocol ipv6-icmp" % from_chain_name) # Conntrack rules. from_chain.append("--append %s --match conntrack --ctstate INVALID " "--jump DROP" % from_chain_name) from_chain.append("--append %s --match conntrack " "--ctstate RELATED,ESTABLISHED --jump RETURN" % from_chain_name) if ip_version == 4: from_chain.append("--append %s --protocol udp --sport 68 --dport 67 " "--jump RETURN" % from_chain_name) else: assert ip_version == 6 from_chain.append("--append %s --protocol udp --sport 546 --dport 547 " "--jump RETURN" % from_chain_name) # Anti-spoofing rules. Only allow traffic from known (IP, MAC) pairs to # get to the profile chain, drop other traffic. profile_out_chain = profile_to_chain_name("outbound", profile_id) from_deps = set([profile_out_chain]) for ip in local_ips: if "/" in ip: cidr = ip else: cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip # Note use of --goto rather than --jump; this means that when the # profile chain returns, it will return the chain that called us, not # this chain. from_chain.append( "--append %s --src %s --match mac --mac-source %s " "--goto %s" % (from_chain_name, cidr, mac.upper(), profile_out_chain)) from_chain.append( commented_drop_fragment(from_chain_name, "Anti-spoof DROP:")) updates = {to_chain_name: to_chain, from_chain_name: from_chain} deps = {to_chain_name: to_deps, from_chain_name: from_deps} return updates, deps
def _get_endpoint_rules(endpoint_id, suffix, ip_version, local_ips, mac, profile_id): to_chain_name, from_chain_name = chain_names(suffix) to_chain = ["--flush %s" % to_chain_name] if ip_version == 6: # In ipv6 only, there are 6 rules that need to be created first. # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 130 # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 131 # RETURN ipv6-icmp anywhere anywhere ipv6-icmptype 132 # RETURN ipv6-icmp anywhere anywhere ipv6-icmp router-advertisement # RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-solicitation # RETURN ipv6-icmp anywhere anywhere ipv6-icmp neighbour-advertisement # # These rules are ICMP types 130, 131, 132, 134, 135 and 136, and can # be created on the command line with something like : # ip6tables -A plw -j RETURN --protocol ipv6-icmp --icmpv6-type 130 for icmp_type in ["130", "131", "132", "134", "135", "136"]: to_chain.append("--append %s --jump RETURN " "--protocol ipv6-icmp " "--icmpv6-type %s" % (to_chain_name, icmp_type)) to_chain.append("--append %s --match conntrack --ctstate INVALID " "--jump DROP" % to_chain_name) to_chain.append("--append %s --match conntrack " "--ctstate RELATED,ESTABLISHED --jump RETURN" % to_chain_name) assert profile_id, "Profile ID should be set, not %s" % profile_id profile_in_chain = profile_to_chain_name("inbound", profile_id) to_chain.append("--append %s --goto %s" % (to_chain_name, profile_in_chain)) # This drop rule is not hittable, but it gives us a place to stash the # comment with our ID. to_chain.append(commented_drop_fragment(to_chain_name, "Endpoint %s:" % endpoint_id)) to_deps = set([profile_in_chain]) # Now the chain that manages packets from the interface... from_chain = ["--flush %s" % from_chain_name] if ip_version == 6: # In ipv6 only, allows all ICMP traffic from this endpoint to anywhere. from_chain.append("--append %s --protocol ipv6-icmp --jump RETURN" % from_chain_name) # Conntrack rules. from_chain.append("--append %s --match conntrack --ctstate INVALID " "--jump DROP" % from_chain_name) from_chain.append("--append %s --match conntrack " "--ctstate RELATED,ESTABLISHED --jump RETURN" % from_chain_name) if ip_version == 4: from_chain.append("--append %s --protocol udp --sport 68 --dport 67 " "--jump RETURN" % from_chain_name) else: assert ip_version == 6 from_chain.append("--append %s --protocol udp --sport 546 --dport 547 " "--jump RETURN" % from_chain_name) # Anti-spoofing rules. Only allow traffic from known (IP, MAC) pairs to # get to the profile chain, drop other traffic. profile_out_chain = profile_to_chain_name("outbound", profile_id) from_deps = set([profile_out_chain]) for ip in local_ips: if "/" in ip: cidr = ip else: cidr = "%s/32" % ip if ip_version == 4 else "%s/128" % ip # Note use of --goto rather than --jump; this means that when the # profile chain returns, it will return the chain that called us, not # this chain. from_chain.append("--append %s --src %s --match mac --mac-source %s " "--goto %s" % (from_chain_name, cidr, mac.upper(), profile_out_chain)) drop_frag = commented_drop_fragment(from_chain_name, "Anti-spoof DROP (endpoint %s):" % endpoint_id) from_chain.append(drop_frag) updates = {to_chain_name: to_chain, from_chain_name: from_chain} deps = {to_chain_name: to_deps, from_chain_name: from_deps} return updates, deps