def _inbound_traffic_rule(conf, service_name, instance_name, protocol="tcp"): """Return iptables rules for inbound traffic If this is set to "reject", this is limited only to traffic from localhost""" policy = conf.get_inbound_firewall() if policy == "reject": for port in _nerve_ports_for_service_instance(service_name, instance_name): yield iptables.Rule( protocol=protocol, src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="REJECT", matches=((protocol, (("dport", (str(port), )), )), ), target_parameters=((("reject-with", ("icmp-port-unreachable", ))), ), ) for ip_range in INBOUND_PRIVATE_IP_RANGES: yield iptables.Rule( protocol=protocol, src=ip_range, dst="0.0.0.0/0.0.0.0", target="ACCEPT", matches=((protocol, (("dport", (str(port), )), )), ), target_parameters=(), )
def _ensure_internet_chain(): iptables.ensure_chain( "PAASTA-INTERNET", ( iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="ACCEPT", matches=(), target_parameters=(), ), ) + tuple( iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst=ip_range, target="RETURN", matches=(), target_parameters=(), ) for ip_range in OUTBOUND_PRIVATE_IP_RANGES ), )
def _ensure_common_chain(): """The common chain allows access for all services to certain resources.""" iptables.ensure_chain( 'PAASTA-COMMON', ( # Allow return traffic for incoming connections iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='ACCEPT', matches=(('conntrack', (('ctstate', ('ESTABLISHED', )), )), ), target_parameters=(), ), _yocalhost_rule(1463, 'scribed'), _yocalhost_rule(8125, 'metrics-relay', protocol='udp'), _yocalhost_rule(3030, 'sensu'), iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='PAASTA-DNS', matches=(), target_parameters=(), ), ), )
def _ensure_common_chain(): """The common chain allows access for all services to certain resources.""" iptables.ensure_chain( "PAASTA-COMMON", ( # Allow return traffic for incoming connections iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="ACCEPT", matches=(("conntrack", (("ctstate", ("ESTABLISHED", )), )), ), target_parameters=(), ), _yocalhost_rule(1463, "scribed"), _yocalhost_rule(8125, "metrics-relay", protocol="udp"), _yocalhost_rule(3030, "sensu"), iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="PAASTA-DNS", matches=(), target_parameters=(), ), ), )
def _well_known_rules(conf): # Allow access to certain resources for all services by default. yield iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='PAASTA-COMMON', matches=(), target_parameters=(), ) for dep in conf.get_dependencies(): resource = dep.get('well-known') if resource == 'internet': yield iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='PAASTA-INTERNET', matches=(), target_parameters=(), ) elif resource is not None: # TODO: handle better raise AssertionError(resource)
def _ensure_dns_chain(): iptables.ensure_chain( "PAASTA-DNS", tuple( itertools.chain.from_iterable( ( iptables.Rule( protocol="udp", src="0.0.0.0/0.0.0.0", dst=f"{dns_server}/255.255.255.255", target="ACCEPT", matches=(("udp", (("dport", ("53",)),)),), target_parameters=(), ), # DNS goes over TCP sometimes, too! iptables.Rule( protocol="tcp", src="0.0.0.0/0.0.0.0", dst=f"{dns_server}/255.255.255.255", target="ACCEPT", matches=(("tcp", (("dport", ("53",)),)),), target_parameters=(), ), ) for dns_server in _dns_servers() ) ), )
def _default_rules(conf, log_prefix): log_rule = iptables.Rule(protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='LOG', target_parameters=(('log-prefix', (log_prefix, )), ), matches=(('limit', ( ('limit', ('1/sec', )), ('limit-burst', ('1', )), )), )) policy = conf.get_outbound_firewall() if policy == 'block': return (iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='REJECT', matches=(), target_parameters=((('reject-with', ('icmp-port-unreachable', ))), ), ), log_rule) elif policy == 'monitor': return (log_rule, ) else: raise AssertionError(policy)
def _well_known_rules(conf): # Allow access to certain resources for all services by default. yield iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="PAASTA-COMMON", matches=(), target_parameters=(), ) for dep in conf.get_dependencies() or (): resource = dep.get("well-known") if resource == "internet": yield iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="PAASTA-INTERNET", matches=(), target_parameters=(), ) elif resource is not None: # TODO: handle better raise AssertionError(resource)
def _default_rules(conf, log_prefix): log_rule = iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="LOG", target_parameters=(("log-prefix", (log_prefix, )), ), matches=(("limit", (("limit", ("1/sec", )), ("limit-burst", ("1", )))), ), ) policy = conf.get_outbound_firewall() if policy == "block": return ( iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="REJECT", matches=(), target_parameters=((("reject-with", ("icmp-port-unreachable", ))), ), ), log_rule, ) elif policy == "monitor": return (log_rule, ) else: raise AssertionError(policy)
def _cidr_rules(conf): for dep in conf.get_dependencies() or (): cidr = dep.get('cidr') port_str = dep.get('port') if cidr is None: continue try: network = ipaddress.IPv4Network(cidr) except ipaddress.AddressValueError: log.exception(f'Unable to parse IP network: {cidr}') continue if port_str is not None: # port can be either a single port like "443" or a range like "1024:65535" ports = str(port_str).split(':') if len(ports) > 2: log.error( f'"port" must be either a single value or a range like "1024:65535": {port_str}' ) continue if not _ports_valid(ports): continue # Set up an ip rule if no port, or a tcp/udp rule if there is a port dst = f'{network.network_address.exploded}/{network.netmask}' if port_str is None: yield iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst=dst, target='ACCEPT', matches=(( 'comment', (('comment', (f'allow {network}:*', )), ), ), ), target_parameters=(), ) else: for proto in ('tcp', 'udp'): yield iptables.Rule( protocol=proto, src='0.0.0.0/0.0.0.0', dst=dst, target='ACCEPT', matches=( ( 'comment', (('comment', (f'allow {network}:{port_str}', )), ), ), ( proto, (('dport', (str(port_str), )), ), ), ), target_parameters=(), )
def _cidr_rules(conf): for dep in conf.get_dependencies() or (): cidr = dep.get("cidr") port_str = dep.get("port") if cidr is None: continue try: network = ipaddress.IPv4Network(cidr) except ipaddress.AddressValueError: log.exception(f"Unable to parse IP network: {cidr}") continue if port_str is not None: # port can be either a single port like "443" or a range like "1024:65535" ports = str(port_str).split(":") if len(ports) > 2: log.error( f'"port" must be either a single value or a range like "1024:65535": {port_str}' ) continue if not _ports_valid(ports): continue # Set up an ip rule if no port, or a tcp/udp rule if there is a port dst = f"{network.network_address.exploded}/{network.netmask}" if port_str is None: yield iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst=dst, target="ACCEPT", matches=(("comment", (("comment", (f"allow {network}:*", )), )), ), target_parameters=(), ) else: for proto in ("tcp", "udp"): yield iptables.Rule( protocol=proto, src="0.0.0.0/0.0.0.0", dst=dst, target="ACCEPT", matches=( ("comment", (("comment", (f"allow {network}:{port_str}", )), )), (proto, (("dport", (str(port_str), )), )), ), target_parameters=(), )
def ensure_internet_chain(): iptables.ensure_chain('PAASTA-INTERNET', (iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='ACCEPT', matches=(), ), ) + tuple( iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst=ip_range, target='RETURN', matches=(), ) for ip_range in PRIVATE_IP_RANGES))
def _smartstack_rules(conf, soa_dir, synapse_service_dir): for dep in conf.get_dependencies() or (): namespace = dep.get("smartstack") if namespace is None: continue # TODO: support wildcards # synapse backends try: backends = _synapse_backends(synapse_service_dir, namespace) except (OSError, IOError, ValueError): # Don't fatal if something goes wrong loading the synapse files log.exception(f"Unable to load backend {namespace}") backends = () for backend in backends: yield iptables.Rule( protocol="tcp", src="0.0.0.0/0.0.0.0", dst="{}/255.255.255.255".format(backend["host"]), target="ACCEPT", matches=( ("comment", (("comment", ("backend " + namespace, )), )), ("tcp", (("dport", (str(backend["port"]), )), )), ), target_parameters=(), ) # synapse-haproxy proxy_port service, _ = namespace.split(".", 1) service_namespaces = get_all_namespaces_for_service(service, soa_dir=soa_dir) port = dict(service_namespaces)[namespace]["proxy_port"] yield _yocalhost_rule(port, "proxy_port " + namespace)
def _smartstack_rules(conf, soa_dir, synapse_service_dir): for dep in conf.get_dependencies(): namespace = dep.get('smartstack') if namespace is None: continue # TODO: support wildcards # synapse backends try: backends = _synapse_backends(synapse_service_dir, namespace) except (OSError, IOError, ValueError): # Don't fatal if something goes wrong loading the synapse files log.exception('Unable to load backend {}'.format(namespace)) backends = () for backend in backends: yield iptables.Rule( protocol='tcp', src='0.0.0.0/0.0.0.0', dst='{}/255.255.255.255'.format(backend['host']), target='ACCEPT', matches=( ('comment', (('comment', ('backend ' + namespace, )), )), ('tcp', (('dport', (six.text_type(backend['port']), )), )), ), target_parameters=(), ) # synapse-haproxy proxy_port service, _ = namespace.split('.', 1) service_namespaces = get_all_namespaces_for_service(service, soa_dir=soa_dir) port = dict(service_namespaces)[namespace]['proxy_port'] yield _yocalhost_rule(port, 'proxy_port ' + namespace)
def dispatch_rule(chain, mac): return iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target=chain, matches=(("mac", (("mac-source", (mac.upper(), )), )), ), target_parameters=(), )
def dispatch_rule(chain, mac): return iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target=chain, matches=(('mac', (('mac-source', (mac.upper(), )), )), ), target_parameters=(), )
def ensure_dispatch_chains(service_chains): paasta_rules = set( itertools.chain.from_iterable((iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target=chain, matches=(('mac', (('mac_source', mac.upper()), )), ), ) for mac in macs) for chain, macs in service_chains.items())) iptables.ensure_chain('PAASTA', paasta_rules) jump_to_paasta = iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='PAASTA', matches=(), ) iptables.ensure_rule('INPUT', jump_to_paasta) iptables.ensure_rule('FORWARD', jump_to_paasta)
def _default_rule(conf): policy = conf.get_outbound_firewall() if policy == 'block': return iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='REJECT', matches=(), ) elif policy == 'monitor': # TODO: log-prefix return iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='LOG', matches=(), ) else: raise AssertionError(policy)
def _yocalhost_rule(port, comment, protocol='tcp'): """Return an iptables rule allowing access to a yocalhost port.""" return iptables.Rule( protocol=protocol, src='0.0.0.0/0.0.0.0', dst='169.254.255.254/255.255.255.255', target='ACCEPT', matches=( ('comment', (('comment', (comment, )), )), (protocol, (('dport', (six.text_type(port), )), )), ), target_parameters=(), )
def _yocalhost_rule(port, comment, protocol="tcp"): """Return an iptables rule allowing access to a yocalhost port.""" return iptables.Rule( protocol=protocol, src="0.0.0.0/0.0.0.0", dst="169.254.255.254/255.255.255.255", target="ACCEPT", matches=( ("comment", (("comment", (comment, )), )), (protocol, (("dport", (str(port), )), )), ), target_parameters=(), )
def _well_known_rules(conf): for resource in conf.get_dependencies().get('well-known', ()): if resource == 'internet': yield iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target='PAASTA-INTERNET', matches=(), ) else: # TODO: handle better raise AssertionError(resource)
def _reject_remaining_inbound_traffic_rule(port, protocol="tcp"): """Return an iptables rule denying all other traffic. This should eventually be turned into a deny-allow, but is opt-in at the service level for now.""" return iptables.Rule( protocol=protocol, src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="REJECT", matches=((protocol, (("dport", (str(port), )), )), ), target_parameters=((("reject-with", ("icmp-port-unreachable", ))), ), )
def _ensure_dns_chain(): iptables.ensure_chain( 'PAASTA-DNS', tuple( itertools.chain.from_iterable(( iptables.Rule( protocol='udp', src='0.0.0.0/0.0.0.0', dst='{}/255.255.255.255'.format(dns_server), target='ACCEPT', matches=(('udp', (('dport', ('53', )), )), ), target_parameters=(), ), # DNS goes over TCP sometimes, too! iptables.Rule( protocol='tcp', src='0.0.0.0/0.0.0.0', dst='{}/255.255.255.255'.format(dns_server), target='ACCEPT', matches=(('tcp', (('dport', ('53', )), )), ), target_parameters=(), ), ) for dns_server in _dns_servers())))
def _smartstack_rules(conf, soa_dir): for namespace in conf.get_dependencies().get('smartstack', ()): # TODO: handle non-synapse-haproxy services # TODO: support wildcards? service, _ = namespace.split('.', 1) service_namespaces = get_all_namespaces_for_service(service, soa_dir=soa_dir) port = dict(service_namespaces)[namespace]['proxy_port'] yield iptables.Rule(protocol='tcp', src='0.0.0.0/0.0.0.0', dst='169.254.255.254/255.255.255.255', target='ACCEPT', matches=(('tcp', (('dport', six.text_type(port)), )), ))
def ensure_dispatch_chains(service_chains): paasta_rules = set( itertools.chain.from_iterable( (dispatch_rule(chain, mac) for mac in macs) for chain, macs in service_chains.items())) iptables.ensure_chain("PAASTA", paasta_rules) jump_to_paasta = iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target="PAASTA", matches=(), target_parameters=(), ) iptables.ensure_rule("INPUT", jump_to_paasta) iptables.ensure_rule("FORWARD", jump_to_paasta)
from collections import namedtuple import iptc import mock import pytest from paasta_tools import iptables EMPTY_RULE = iptables.Rule( protocol='ip', src='0.0.0.0/0.0.0.0', dst='0.0.0.0/0.0.0.0', target=None, matches=(), target_parameters=(), ) @pytest.yield_fixture def mock_Table(): with mock.patch.object( iptc, 'Table', autospec=True, ) as m: m.return_value.autocommit = True yield m @pytest.yield_fixture def mock_Chain(): with mock.patch.object(
from collections import namedtuple import iptc import mock import pytest from paasta_tools import iptables EMPTY_RULE = iptables.Rule( protocol="ip", src="0.0.0.0/0.0.0.0", dst="0.0.0.0/0.0.0.0", target=None, matches=(), target_parameters=(), ) @pytest.yield_fixture def mock_Table(): with mock.patch.object(iptc, "Table", autospec=True) as m: m.return_value.autocommit = True yield m @pytest.yield_fixture def mock_Chain(): with mock.patch.object(iptc, "Chain", autospec=True) as m: yield m