From 34bdee40aa61f6f973a76233742d41c20d6c6e0b Mon Sep 17 00:00:00 2001 From: Eric Garver Date: Fri, 4 Oct 2019 15:23:59 -0400 Subject: [PATCH] feat: implement policy objects internally Introduce policy objects internally. There are no user visible changes. Rule layout for zones has not changed. Zones are translated into multiple policies. This is the first step in introducing policy objects to users and therefore supporting output/forward filtering. --- src/firewall-config.in | 4 +- src/firewall/core/base.py | 5 +- src/firewall/core/ebtables.py | 2 +- src/firewall/core/fw.py | 16 +- src/firewall/core/fw_ipset.py | 8 +- src/firewall/core/fw_policy.py | 1552 +++++++++++++++++++++++++++ src/firewall/core/fw_transaction.py | 20 +- src/firewall/core/fw_zone.py | 1880 +++++++-------------------------- src/firewall/core/io/policy.py | 830 +++++++++++++++ src/firewall/core/ipXtables.py | 315 +++--- src/firewall/core/nftables.py | 301 +++--- src/firewall/errors.py | 1 + src/firewall/functions.py | 11 +- src/tests/features/service_include.at | 2 - 14 files changed, 3118 insertions(+), 1829 deletions(-) create mode 100644 src/firewall/core/fw_policy.py create mode 100644 src/firewall/core/io/policy.py diff --git a/src/firewall-config.in b/src/firewall-config.in index f5f69fc..1d88b29 100755 --- a/src/firewall-config.in +++ b/src/firewall-config.in @@ -46,7 +46,7 @@ from firewall import config from firewall import client from firewall import functions from firewall.core.base import DEFAULT_ZONE_TARGET, REJECT_TYPES, \ - ZONE_SOURCE_IPSET_TYPES + SOURCE_IPSET_TYPES from firewall.core.ipset import IPSET_MAXNAMELEN from firewall.core.helper import HELPER_MAXNAMELEN from firewall.core.io.zone import Zone @@ -4188,7 +4188,7 @@ class FirewallConfig(object): continue raise self.activate_exception_handler() - if settings.getType() not in ZONE_SOURCE_IPSET_TYPES: + if settings.getType() not in SOURCE_IPSET_TYPES: continue ipsets[x] = settings else: diff --git a/src/firewall/core/base.py b/src/firewall/core/base.py index e2691b5..45522a9 100644 --- a/src/firewall/core/base.py +++ b/src/firewall/core/base.py @@ -22,10 +22,13 @@ """Base firewall settings""" DEFAULT_ZONE_TARGET = "{chain}_{zone}" +DEFAULT_POLICY_TARGET = "CONTINUE" ZONE_TARGETS = [ "ACCEPT", "%%REJECT%%", "DROP", DEFAULT_ZONE_TARGET, "default" ] +POLICY_TARGETS = [ "ACCEPT", "%%REJECT%%", "DROP", "CONTINUE" ] + SHORTCUTS = { "PREROUTING": "PRE", "POSTROUTING": "POST", @@ -49,7 +52,7 @@ REJECT_TYPES = { # ipset types that can be used as a source in zones # The match-set option will be src or src,src according to the # dimension of the ipset. -ZONE_SOURCE_IPSET_TYPES = [ +SOURCE_IPSET_TYPES = [ "hash:ip", "hash:ip,port", "hash:ip,mark", "hash:net", "hash:net,port", "hash:net,iface", "hash:mac" diff --git a/src/firewall/core/ebtables.py b/src/firewall/core/ebtables.py index a5860bd..8938b75 100644 --- a/src/firewall/core/ebtables.py +++ b/src/firewall/core/ebtables.py @@ -52,7 +52,7 @@ for table in BUILT_IN_CHAINS.keys(): class ebtables(object): ipv = "eb" name = "ebtables" - zones_supported = False # ebtables only supported with direct interface + policies_supported = False # ebtables only supported with direct interface def __init__(self): self._command = COMMANDS[self.ipv] diff --git a/src/firewall/core/fw.py b/src/firewall/core/fw.py index f3f1adf..a9d56a9 100644 --- a/src/firewall/core/fw.py +++ b/src/firewall/core/fw.py @@ -42,6 +42,7 @@ from firewall.core.fw_policies import FirewallPolicies from firewall.core.fw_ipset import FirewallIPSet from firewall.core.fw_transaction import FirewallTransaction from firewall.core.fw_helper import FirewallHelper +from firewall.core.fw_policy import FirewallPolicy from firewall.core.fw_nm import nm_get_bus_name, nm_get_interfaces_in_zone from firewall.core.logger import log from firewall.core.io.firewalld_conf import firewalld_conf @@ -98,6 +99,7 @@ class Firewall(object): self.policies = FirewallPolicies() self.ipset = FirewallIPSet(self) self.helper = FirewallHelper(self) + self.policy = FirewallPolicy(self) self.__init_vars() @@ -634,6 +636,7 @@ class Firewall(object): self.config.cleanup() self.direct.cleanup() self.policies.cleanup() + self.policy.cleanup() self._firewalld_conf.cleanup() self.__init_vars() @@ -876,6 +879,12 @@ class Firewall(object): if self._panic: raise FirewallError(errors.PANIC_MODE) + def check_policy(self, policy): + _policy = policy + if _policy not in self.policy.get_policies(): + raise FirewallError(errors.INVALID_POLICY, _policy) + return _policy + def check_zone(self, zone): _zone = zone if not _zone or _zone == "": @@ -996,8 +1005,11 @@ class Firewall(object): # add interfaces to zones again for zone in self.zone.get_zones(): if zone in _zone_interfaces: - self.zone.set_settings(zone, { "interfaces": - _zone_interfaces[zone] }) + + for interface_id in _zone_interfaces[zone]: + self.zone.change_zone_of_interface(zone, interface_id, + _zone_interfaces[zone][interface_id]["sender"]) + del _zone_interfaces[zone] else: log.info1("New zone '%s'.", zone) diff --git a/src/firewall/core/fw_ipset.py b/src/firewall/core/fw_ipset.py index 6136b47..102c7f6 100644 --- a/src/firewall/core/fw_ipset.py +++ b/src/firewall/core/fw_ipset.py @@ -155,8 +155,8 @@ class FirewallIPSet(object): # TYPE - def get_type(self, name): - return self.get_ipset(name, applied=True).type + def get_type(self, name, applied=True): + return self.get_ipset(name, applied=applied).type # DIMENSION def get_dimension(self, name): @@ -173,8 +173,8 @@ class FirewallIPSet(object): # OPTIONS - def get_family(self, name): - obj = self.get_ipset(name, applied=True) + def get_family(self, name, applied=True): + obj = self.get_ipset(name, applied=applied) if "family" in obj.options: if obj.options["family"] == "inet6": return "ipv6" diff --git a/src/firewall/core/fw_policy.py b/src/firewall/core/fw_policy.py new file mode 100644 index 0000000..d22e126 --- /dev/null +++ b/src/firewall/core/fw_policy.py @@ -0,0 +1,1552 @@ +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: GPL-2.0-or-later + +import time +from firewall.core.logger import log +from firewall.functions import portStr, checkIPnMask, checkIP6nMask, \ + checkProtocol, enable_ip_forwarding, check_single_address, \ + portInPortRange, get_nf_conntrack_short_name, coalescePortRange, breakPortRange +from firewall.core.rich import Rich_Rule, Rich_Accept, \ + Rich_Mark, Rich_Service, Rich_Port, Rich_Protocol, \ + Rich_Masquerade, Rich_ForwardPort, Rich_SourcePort, Rich_IcmpBlock, \ + Rich_IcmpType +from firewall.core.fw_transaction import FirewallTransaction +from firewall import errors +from firewall.errors import FirewallError +from firewall.fw_types import LastUpdatedOrderedDict +from firewall.core.base import SOURCE_IPSET_TYPES + +class FirewallPolicy(object): + def __init__(self, fw): + self._fw = fw + self._chains = { } + self._policies = { } + + def __repr__(self): + return '%s(%r, %r)' % (self.__class__, self._chains, self._policies) + + def cleanup(self): + self._chains.clear() + self._policies.clear() + + # transaction + + def new_transaction(self): + return FirewallTransaction(self._fw) + + # policies + + def get_policies(self): + return sorted(self._policies.keys()) + + def get_policy(self, policy): + p = self._fw.check_policy(policy) + return self._policies[p] + + def _first_except(self, e, f, name, *args, **kwargs): + try: + f(name, *args, **kwargs) + except FirewallError as error: + if not e: + return error + return e + + def add_policy(self, obj): + obj.settings = { x : LastUpdatedOrderedDict() + for x in [ "services", "ports", + "masquerade", "forward_ports", + "source_ports", + "icmp_blocks", "rules", + "protocols", "icmp_block_inversion" ] } + + self._policies[obj.name] = obj + self.copy_permanent_to_runtime(obj.name) + + def remove_policy(self, policy): + obj = self._policies[policy] + if obj.applied: + self.unapply_policy_settings(policy) + obj.settings.clear() + del self._policies[policy] + + def copy_permanent_to_runtime(self, policy): + obj = self._policies[policy] + + if obj.applied: + return + + for args in obj.icmp_blocks: + self.add_icmp_block(policy, args) + for args in obj.forward_ports: + self.add_forward_port(policy, *args) + for args in obj.services: + self.add_service(policy, args) + for args in obj.ports: + self.add_port(policy, *args) + for args in obj.protocols: + self.add_protocol(policy, args) + for args in obj.source_ports: + self.add_source_port(policy, *args) + for args in obj.rules: + self.add_rule(policy, args) + if obj.masquerade: + self.add_masquerade(policy) + + def set_policy_applied(self, policy, applied): + obj = self._policies[policy] + obj.applied = applied + + # settings + + # generate settings record with sender, timeout + def __gen_settings(self, timeout, sender): + ret = { + "date": time.time(), + "sender": sender, + "timeout": timeout, + } + return ret + + def get_settings(self, policy): + return self.get_policy(policy).settings + + def _policy_settings(self, enable, policy, use_transaction=None): + _policy = self._fw.check_policy(policy) + obj = self._policies[_policy] + if (enable and obj.applied) or (not enable and not obj.applied): + return + if enable: + obj.applied = True + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + settings = self.get_settings(policy) + for key in settings: + for args in settings[key]: + if key == "icmp_blocks": + self._icmp_block(enable, _policy, args, transaction) + elif key == "icmp_block_inversion": + continue + elif key == "forward_ports": + self._forward_port(enable, _policy, transaction, + *args) + elif key == "services": + self._service(enable, _policy, args, transaction) + elif key == "ports": + self._port(enable, _policy, args[0], args[1], + transaction) + elif key == "protocols": + self._protocol(enable, _policy, args, transaction) + elif key == "source_ports": + self._source_port(enable, _policy, args[0], args[1], + transaction) + elif key == "masquerade": + self._masquerade(enable, _policy, transaction) + elif key == "rules": + self.__rule(enable, _policy, Rich_Rule(rule_str=args), + transaction) + else: + log.warning("Policy '%s': Unknown setting '%s:%s', " + "unable to apply", policy, key, args) + + if use_transaction is None: + transaction.execute(enable) + + def apply_policy_settings(self, policy, use_transaction=None): + self._policy_settings(True, policy, use_transaction=use_transaction) + + def unapply_policy_settings(self, policy, use_transaction=None): + self._policy_settings(False, policy, use_transaction=use_transaction) + + def get_config_with_settings(self, policy): + """ + :return: exported config updated with runtime settings + """ + conf = list(self.get_policy(policy).export_config()) + conf[5] = self.list_services(policy) + conf[6] = self.list_ports(policy) + conf[7] = self.list_icmp_blocks(policy) + conf[8] = self.query_masquerade(policy) + conf[9] = self.list_forward_ports(policy) + conf[12] = self.list_rules(policy) + conf[13] = self.list_protocols(policy) + conf[14] = self.list_source_ports(policy) + conf[15] = self.query_icmp_block_inversion(policy) + return tuple(conf) + + # RICH LANGUAGE + + def check_rule(self, rule): + rule.check() + + def __rule_id(self, rule): + self.check_rule(rule) + return str(rule) + + def _rule_source_ipv(self, source): + if not source: + return None + + if source.addr: + if checkIPnMask(source.addr): + return "ipv4" + elif checkIP6nMask(source.addr): + return "ipv6" + elif hasattr(source, "mac") and source.mac: + return "" + elif hasattr(source, "ipset") and source.ipset: + self._check_ipset_type_for_source(source.ipset) + self._check_ipset_applied(source.ipset) + return self._ipset_family(source.ipset) + + return None + + def __rule(self, enable, policy, rule, transaction): + self._rule_prepare(enable, policy, rule, transaction) + + def add_rule(self, policy, rule, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + rule_id = self.__rule_id(rule) + if rule_id in _obj.settings["rules"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "'%s' already in '%s'" % (rule, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self.__rule(True, _policy, rule, transaction) + + self.__register_rule(_obj, rule_id, timeout, sender) + transaction.add_fail(self.__unregister_rule, _obj, rule_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_rule(self, _obj, rule_id, timeout, sender): + _obj.settings["rules"][rule_id] = self.__gen_settings( + timeout, sender) + + def remove_rule(self, policy, rule, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + rule_id = self.__rule_id(rule) + if rule_id not in _obj.settings["rules"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "'%s' not in '%s'" % (rule, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self.__rule(False, _policy, rule, transaction) + + transaction.add_post(self.__unregister_rule, _obj, rule_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_rule(self, _obj, rule_id): + if rule_id in _obj.settings["rules"]: + del _obj.settings["rules"][rule_id] + + def query_rule(self, policy, rule): + return self.__rule_id(rule) in self.get_settings(policy)["rules"] + + def list_rules(self, policy): + return list(self.get_settings(policy)["rules"].keys()) + + # SERVICES + + def check_service(self, service): + self._fw.check_service(service) + + def __service_id(self, service): + self.check_service(service) + return service + + def add_service(self, policy, service, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + service_id = self.__service_id(service) + if service_id in _obj.settings["services"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "'%s' already in '%s'" % (service, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._service(True, _policy, service, transaction) + + self.__register_service(_obj, service_id, timeout, sender) + transaction.add_fail(self.__unregister_service, _obj, service_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_service(self, _obj, service_id, timeout, sender): + _obj.settings["services"][service_id] = \ + self.__gen_settings(timeout, sender) + + def remove_service(self, policy, service, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + service_id = self.__service_id(service) + if service_id not in _obj.settings["services"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "'%s' not in '%s'" % (service, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._service(False, _policy, service, transaction) + + transaction.add_post(self.__unregister_service, _obj, service_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_service(self, _obj, service_id): + if service_id in _obj.settings["services"]: + del _obj.settings["services"][service_id] + + def query_service(self, policy, service): + return self.__service_id(service) in self.get_settings(policy)["services"] + + def list_services(self, policy): + return self.get_settings(policy)["services"].keys() + + def get_helpers_for_service_helpers(self, helpers): + _helpers = [ ] + for helper in helpers: + try: + _helper = self._fw.helper.get_helper(helper) + except FirewallError: + raise FirewallError(errors.INVALID_HELPER, helper) + _helpers.append(_helper) + return _helpers + + def get_helpers_for_service_modules(self, modules, enable): + # If automatic helper assignment is turned off, helpers that + # do not have ports defined will be replaced by the helpers + # that the helper.module defines. + _helpers = [ ] + for module in modules: + try: + helper = self._fw.helper.get_helper(module) + except FirewallError: + raise FirewallError(errors.INVALID_HELPER, module) + if len(helper.ports) < 1: + _module_short_name = get_nf_conntrack_short_name(helper.module) + try: + _helper = self._fw.helper.get_helper(_module_short_name) + _helpers.append(_helper) + except FirewallError: + if enable: + log.warning("Helper '%s' is not available" % _module_short_name) + continue + else: + _helpers.append(helper) + return _helpers + + # PORTS + + def check_port(self, port, protocol): + self._fw.check_port(port) + self._fw.check_tcpudp(protocol) + + def __port_id(self, port, protocol): + self.check_port(port, protocol) + return (portStr(port, "-"), protocol) + + def add_port(self, policy, port, protocol, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) + for port_id in existing_port_ids: + if portInPortRange(port, port_id[0]): + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "'%s:%s' already in '%s'" % (port, protocol, _name)) + + added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + for range in added_ranges: + self._port(True, _policy, portStr(range, "-"), protocol, transaction) + for range in removed_ranges: + self._port(False, _policy, portStr(range, "-"), protocol, transaction) + + for range in added_ranges: + port_id = self.__port_id(range, protocol) + self.__register_port(_obj, port_id, timeout, sender) + transaction.add_fail(self.__unregister_port, _obj, port_id) + for range in removed_ranges: + port_id = self.__port_id(range, protocol) + transaction.add_post(self.__unregister_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_port(self, _obj, port_id, timeout, sender): + _obj.settings["ports"][port_id] = \ + self.__gen_settings(timeout, sender) + + def remove_port(self, policy, port, protocol, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) + for port_id in existing_port_ids: + if portInPortRange(port, port_id[0]): + break + else: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "'%s:%s' not in '%s'" % (port, protocol, _name)) + + added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + for range in added_ranges: + self._port(True, _policy, portStr(range, "-"), protocol, transaction) + for range in removed_ranges: + self._port(False, _policy, portStr(range, "-"), protocol, transaction) + + for range in added_ranges: + port_id = self.__port_id(range, protocol) + self.__register_port(_obj, port_id, 0, None) + transaction.add_fail(self.__unregister_port, _obj, port_id) + for range in removed_ranges: + port_id = self.__port_id(range, protocol) + transaction.add_post(self.__unregister_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_port(self, _obj, port_id): + if port_id in _obj.settings["ports"]: + del _obj.settings["ports"][port_id] + + def query_port(self, policy, port, protocol): + for (_port, _protocol) in self.get_settings(policy)["ports"]: + if portInPortRange(port, _port) and protocol == _protocol: + return True + + return False + + def list_ports(self, policy): + return list(self.get_settings(policy)["ports"].keys()) + + # PROTOCOLS + + def check_protocol(self, protocol): + if not checkProtocol(protocol): + raise FirewallError(errors.INVALID_PROTOCOL, protocol) + + def __protocol_id(self, protocol): + self.check_protocol(protocol) + return protocol + + def add_protocol(self, policy, protocol, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + protocol_id = self.__protocol_id(protocol) + if protocol_id in _obj.settings["protocols"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "'%s' already in '%s'" % (protocol, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._protocol(True, _policy, protocol, transaction) + + self.__register_protocol(_obj, protocol_id, timeout, sender) + transaction.add_fail(self.__unregister_protocol, _obj, protocol_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_protocol(self, _obj, protocol_id, timeout, sender): + _obj.settings["protocols"][protocol_id] = \ + self.__gen_settings(timeout, sender) + + def remove_protocol(self, policy, protocol, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + protocol_id = self.__protocol_id(protocol) + if protocol_id not in _obj.settings["protocols"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "'%s' not in '%s'" % (protocol, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._protocol(False, _policy, protocol, transaction) + + transaction.add_post(self.__unregister_protocol, _obj, + protocol_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_protocol(self, _obj, protocol_id): + if protocol_id in _obj.settings["protocols"]: + del _obj.settings["protocols"][protocol_id] + + def query_protocol(self, policy, protocol): + return self.__protocol_id(protocol) in self.get_settings(policy)["protocols"] + + def list_protocols(self, policy): + return list(self.get_settings(policy)["protocols"].keys()) + + # SOURCE PORTS + + def __source_port_id(self, port, protocol): + self.check_port(port, protocol) + return (portStr(port, "-"), protocol) + + def add_source_port(self, policy, port, protocol, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) + for port_id in existing_port_ids: + if portInPortRange(port, port_id[0]): + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "'%s:%s' already in '%s'" % (port, protocol, _name)) + + added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + for range in added_ranges: + self._source_port(True, _policy, portStr(range, "-"), protocol, transaction) + for range in removed_ranges: + self._source_port(False, _policy, portStr(range, "-"), protocol, transaction) + + for range in added_ranges: + port_id = self.__source_port_id(range, protocol) + self.__register_source_port(_obj, port_id, timeout, sender) + transaction.add_fail(self.__unregister_source_port, _obj, port_id) + for range in removed_ranges: + port_id = self.__source_port_id(range, protocol) + transaction.add_post(self.__unregister_source_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_source_port(self, _obj, port_id, timeout, sender): + _obj.settings["source_ports"][port_id] = \ + self.__gen_settings(timeout, sender) + + def remove_source_port(self, policy, port, protocol, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) + for port_id in existing_port_ids: + if portInPortRange(port, port_id[0]): + break + else: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "'%s:%s' not in '%s'" % (port, protocol, _name)) + + added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + for range in added_ranges: + self._source_port(True, _policy, portStr(range, "-"), protocol, transaction) + for range in removed_ranges: + self._source_port(False, _policy, portStr(range, "-"), protocol, transaction) + + for range in added_ranges: + port_id = self.__source_port_id(range, protocol) + self.__register_source_port(_obj, port_id, 0, None) + transaction.add_fail(self.__unregister_source_port, _obj, port_id) + for range in removed_ranges: + port_id = self.__source_port_id(range, protocol) + transaction.add_post(self.__unregister_source_port, _obj, port_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_source_port(self, _obj, port_id): + if port_id in _obj.settings["source_ports"]: + del _obj.settings["source_ports"][port_id] + + def query_source_port(self, policy, port, protocol): + for (_port, _protocol) in self.get_settings(policy)["source_ports"]: + if portInPortRange(port, _port) and protocol == _protocol: + return True + + return False + + def list_source_ports(self, policy): + return list(self.get_settings(policy)["source_ports"].keys()) + + # MASQUERADE + + def __masquerade_id(self): + return True + + def add_masquerade(self, policy, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + masquerade_id = self.__masquerade_id() + if masquerade_id in _obj.settings["masquerade"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "masquerade already enabled in '%s'" % _name) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._masquerade(True, _policy, transaction) + + self.__register_masquerade(_obj, masquerade_id, timeout, sender) + transaction.add_fail(self.__unregister_masquerade, _obj, masquerade_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_masquerade(self, _obj, masquerade_id, timeout, sender): + _obj.settings["masquerade"][masquerade_id] = \ + self.__gen_settings(timeout, sender) + + def remove_masquerade(self, policy, use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + masquerade_id = self.__masquerade_id() + if masquerade_id not in _obj.settings["masquerade"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "masquerade not enabled in '%s'" % _name) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._masquerade(False, _policy, transaction) + + transaction.add_post(self.__unregister_masquerade, _obj, masquerade_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_masquerade(self, _obj, masquerade_id): + if masquerade_id in _obj.settings["masquerade"]: + del _obj.settings["masquerade"][masquerade_id] + + def query_masquerade(self, policy): + return self.__masquerade_id() in self.get_settings(policy)["masquerade"] + + # PORT FORWARDING + + def check_forward_port(self, ipv, port, protocol, toport=None, toaddr=None): + self._fw.check_port(port) + self._fw.check_tcpudp(protocol) + if toport: + self._fw.check_port(toport) + if toaddr: + if not check_single_address(ipv, toaddr): + raise FirewallError(errors.INVALID_ADDR, toaddr) + if not toport and not toaddr: + raise FirewallError( + errors.INVALID_FORWARD, + "port-forwarding is missing to-port AND to-addr") + + def __forward_port_id(self, port, protocol, toport=None, toaddr=None): + if check_single_address("ipv6", toaddr): + self.check_forward_port("ipv6", port, protocol, toport, toaddr) + else: + self.check_forward_port("ipv4", port, protocol, toport, toaddr) + return (portStr(port, "-"), protocol, + portStr(toport, "-"), str(toaddr)) + + def add_forward_port(self, policy, port, protocol, toport=None, + toaddr=None, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + forward_id = self.__forward_port_id(port, protocol, toport, toaddr) + if forward_id in _obj.settings["forward_ports"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "'%s:%s:%s:%s' already in '%s'" % \ + (port, protocol, toport, toaddr, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._forward_port(True, _policy, transaction, port, protocol, + toport, toaddr) + + self.__register_forward_port(_obj, forward_id, timeout, sender) + transaction.add_fail(self.__unregister_forward_port, _obj, forward_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_forward_port(self, _obj, forward_id, timeout, sender): + _obj.settings["forward_ports"][forward_id] = \ + self.__gen_settings(timeout, sender) + + def remove_forward_port(self, policy, port, protocol, toport=None, + toaddr=None, use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + forward_id = self.__forward_port_id(port, protocol, toport, toaddr) + if forward_id not in _obj.settings["forward_ports"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "'%s:%s:%s:%s' not in '%s'" % \ + (port, protocol, toport, toaddr, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._forward_port(False, _policy, transaction, port, protocol, + toport, toaddr) + + transaction.add_post(self.__unregister_forward_port, _obj, forward_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_forward_port(self, _obj, forward_id): + if forward_id in _obj.settings["forward_ports"]: + del _obj.settings["forward_ports"][forward_id] + + def query_forward_port(self, policy, port, protocol, toport=None, + toaddr=None): + forward_id = self.__forward_port_id(port, protocol, toport, toaddr) + return forward_id in self.get_settings(policy)["forward_ports"] + + def list_forward_ports(self, policy): + return list(self.get_settings(policy)["forward_ports"].keys()) + + # ICMP BLOCK + + def check_icmp_block(self, icmp): + self._fw.check_icmptype(icmp) + + def __icmp_block_id(self, icmp): + self.check_icmp_block(icmp) + return icmp + + def add_icmp_block(self, policy, icmp, timeout=0, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_timeout(timeout) + self._fw.check_panic() + _obj = self._policies[_policy] + + icmp_id = self.__icmp_block_id(icmp) + if icmp_id in _obj.settings["icmp_blocks"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.ALREADY_ENABLED, + "'%s' already in '%s'" % (icmp, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._icmp_block(True, _policy, icmp, transaction) + + self.__register_icmp_block(_obj, icmp_id, timeout, sender) + transaction.add_fail(self.__unregister_icmp_block, _obj, icmp_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_icmp_block(self, _obj, icmp_id, timeout, sender): + _obj.settings["icmp_blocks"][icmp_id] = \ + self.__gen_settings(timeout, sender) + + def remove_icmp_block(self, policy, icmp, use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + icmp_id = self.__icmp_block_id(icmp) + if icmp_id not in _obj.settings["icmp_blocks"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError(errors.NOT_ENABLED, + "'%s' not in '%s'" % (icmp, _name)) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + self._icmp_block(False, _policy, icmp, transaction) + + transaction.add_post(self.__unregister_icmp_block, _obj, icmp_id) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_icmp_block(self, _obj, icmp_id): + if icmp_id in _obj.settings["icmp_blocks"]: + del _obj.settings["icmp_blocks"][icmp_id] + + def query_icmp_block(self, policy, icmp): + return self.__icmp_block_id(icmp) in self.get_settings(policy)["icmp_blocks"] + + def list_icmp_blocks(self, policy): + return self.get_settings(policy)["icmp_blocks"].keys() + + # ICMP BLOCK INVERSION + + def __icmp_block_inversion_id(self): + return True + + def add_icmp_block_inversion(self, policy, sender=None, + use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + icmp_block_inversion_id = self.__icmp_block_inversion_id() + if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError( + errors.ALREADY_ENABLED, + "icmp-block-inversion already enabled in '%s'" % _name) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + # undo icmp blocks + for args in self.get_settings(_policy)["icmp_blocks"]: + self._icmp_block(False, _policy, args, transaction) + + self._icmp_block_inversion(False, _policy, transaction) + + self.__register_icmp_block_inversion(_obj, icmp_block_inversion_id, + sender) + transaction.add_fail(self.__undo_icmp_block_inversion, _policy, _obj, + icmp_block_inversion_id) + + # redo icmp blocks + if _obj.applied: + for args in self.get_settings(_policy)["icmp_blocks"]: + self._icmp_block(True, _policy, args, transaction) + + self._icmp_block_inversion(True, _policy, transaction) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __register_icmp_block_inversion(self, _obj, icmp_block_inversion_id, + sender): + _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] = \ + self.__gen_settings(0, sender) + + def __undo_icmp_block_inversion(self, _policy, _obj, icmp_block_inversion_id): + transaction = self.new_transaction() + + # undo icmp blocks + if _obj.applied: + for args in self.get_settings(_policy)["icmp_blocks"]: + self._icmp_block(False, _policy, args, transaction) + + if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: + del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] + + # redo icmp blocks + if _obj.applied: + for args in self.get_settings(_policy)["icmp_blocks"]: + self._icmp_block(True, _policy, args, transaction) + + transaction.execute(True) + + def remove_icmp_block_inversion(self, policy, use_transaction=None): + _policy = self._fw.check_policy(policy) + self._fw.check_panic() + _obj = self._policies[_policy] + + icmp_block_inversion_id = self.__icmp_block_inversion_id() + if icmp_block_inversion_id not in _obj.settings["icmp_block_inversion"]: + _name = _obj.derived_from_zone if _obj.derived_from_zone else _policy + raise FirewallError( + errors.NOT_ENABLED, + "icmp-block-inversion not enabled in '%s'" % _name) + + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + if _obj.applied: + # undo icmp blocks + for args in self.get_settings(_policy)["icmp_blocks"]: + self._icmp_block(False, _policy, args, transaction) + + self._icmp_block_inversion(False, _policy, transaction) + + self.__unregister_icmp_block_inversion(_obj, + icmp_block_inversion_id) + transaction.add_fail(self.__register_icmp_block_inversion, _obj, + icmp_block_inversion_id, None) + + # redo icmp blocks + if _obj.applied: + for args in self.get_settings(_policy)["icmp_blocks"]: + self._icmp_block(True, _policy, args, transaction) + + self._icmp_block_inversion(True, _policy, transaction) + + if use_transaction is None: + transaction.execute(True) + + return _policy + + def __unregister_icmp_block_inversion(self, _obj, icmp_block_inversion_id): + if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: + del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] + + def query_icmp_block_inversion(self, policy): + return self.__icmp_block_inversion_id() in \ + self.get_settings(policy)["icmp_block_inversion"] + + def gen_chain_rules(self, policy, create, table, chain, transaction): + # HACK: iptables backend has to support the (raw, PREROUTING) chain in + # multiple policies derived for zones - this is due to conntrack + # helpers. As such, track it in the policy matching zone --> HOST. + obj = self._fw.policy.get_policy(policy) + if obj.derived_from_zone and table == "raw" and chain == "PREROUTING": + for p in self._fw.zone._zone_policies[obj.derived_from_zone]: + p_obj = self._fw.policy.get_policy(p) + if p_obj.egress_zones[0] == "HOST": + tracking_policy = p + break + else: + tracking_policy = policy + + if create: + if tracking_policy in self._chains and \ + table in self._chains[tracking_policy]: + return + else: + if tracking_policy not in self._chains or \ + table not in self._chains[tracking_policy]: + return + + for backend in self._fw.enabled_backends(): + if backend.policies_supported and \ + table in backend.get_available_tables(): + rules = backend.build_policy_chain_rules(policy, table) + transaction.add_rules(backend, rules) + + self._register_chains(tracking_policy, create, [table]) + transaction.add_fail(self._register_chains, tracking_policy, not create, [table]) + + def _register_chains(self, policy, create, tables): + for table in tables: + if create: + self._chains.setdefault(policy, []).append(table) + else: + self._chains[policy].remove(table) + if len(self._chains[policy]) == 0: + del self._chains[policy] + + # IPSETS + + def _ipset_family(self, name): + if self._fw.ipset.get_type(name) == "hash:mac": + return None + return self._fw.ipset.get_family(name) + + def __ipset_type(self, name): + return self._fw.ipset.get_type(name) + + def _ipset_match_flags(self, name, flag): + return ",".join([flag] * self._fw.ipset.get_dimension(name)) + + def _check_ipset_applied(self, name): + return self._fw.ipset.check_applied(name) + + def _check_ipset_type_for_source(self, name): + _type = self.__ipset_type(name) + if _type not in SOURCE_IPSET_TYPES: + raise FirewallError( + errors.INVALID_IPSET, + "ipset '%s' with type '%s' not usable as source" % \ + (name, _type)) + + def _rule_prepare(self, enable, policy, rule, transaction): + if rule.family is not None: + ipvs = [ rule.family ] + else: + ipvs = [ipv for ipv in ["ipv4", "ipv6"] if self._fw.is_ipv_enabled(ipv)] + + source_ipv = self._rule_source_ipv(rule.source) + if source_ipv is not None and source_ipv != "": + if rule.family is not None: + # rule family is defined by user, no way to change it + if rule.family != source_ipv: + raise FirewallError(errors.INVALID_RULE, + "Source address family '%s' conflicts with rule family '%s'." % (source_ipv, rule.family)) + else: + # use the source family as rule family + ipvs = [ source_ipv ] + + # add an element to object to allow backends to know what ipvs this applies to + rule.ipvs = ipvs + + for backend in set([self._fw.get_backend_by_ipv(x) for x in ipvs]): + # SERVICE + if type(rule.element) == Rich_Service: + svc = self._fw.service.get_service(rule.element.name) + + destinations = [] + if len(svc.destination) > 0: + if rule.destination: + # we can not use two destinations at the same time + raise FirewallError(errors.INVALID_RULE, + "Destination conflict with service.") + for ipv in ipvs: + if ipv in svc.destination and backend.is_ipv_supported(ipv): + destinations.append(svc.destination[ipv]) + else: + # dummy for the following for loop + destinations.append(None) + + for destination in destinations: + if enable: + transaction.add_chain(policy, "filter", "INPUT") + transaction.add_chain(policy, "raw", "PREROUTING") + + if type(rule.action) == Rich_Accept: + # only load modules for accept action + helpers = self.get_helpers_for_service_modules(svc.modules, + enable) + helpers += self.get_helpers_for_service_helpers(svc.helpers) + helpers = sorted(set(helpers), key=lambda x: x.name) + + modules = [ ] + for helper in helpers: + module = helper.module + _module_short_name = get_nf_conntrack_short_name(module) + nat_module = module.replace("conntrack", "nat") + modules.append(nat_module) + if helper.family != "" and not backend.is_ipv_supported(helper.family): + # no support for family ipv, continue + continue + if len(helper.ports) < 1: + modules.append(module) + else: + for (port,proto) in helper.ports: + rules = backend.build_policy_helper_ports_rules( + enable, policy, proto, port, + destination, helper.name, _module_short_name) + transaction.add_rules(backend, rules) + transaction.add_modules(modules) + + # create rules + for (port,proto) in svc.ports: + if enable and type(rule.action) == Rich_Mark: + transaction.add_chain(policy, "mangle", "PREROUTING") + rules = backend.build_policy_ports_rules( + enable, policy, proto, port, destination, rule) + transaction.add_rules(backend, rules) + + for proto in svc.protocols: + if enable and type(rule.action) == Rich_Mark: + transaction.add_chain(policy, "mangle", "PREROUTING") + rules = backend.build_policy_protocol_rules( + enable, policy, proto, destination, rule) + transaction.add_rules(backend, rules) + + # create rules + for (port,proto) in svc.source_ports: + if enable and type(rule.action) == Rich_Mark: + transaction.add_chain(policy, "mangle", "PREROUTING") + rules = backend.build_policy_source_ports_rules( + enable, policy, proto, port, destination, rule) + transaction.add_rules(backend, rules) + + # PORT + elif type(rule.element) == Rich_Port: + port = rule.element.port + protocol = rule.element.protocol + self.check_port(port, protocol) + + if enable: + transaction.add_chain(policy, "filter", "INPUT") + if enable and type(rule.action) == Rich_Mark: + transaction.add_chain(policy, "mangle", "PREROUTING") + + rules = backend.build_policy_ports_rules( + enable, policy, protocol, port, None, rule) + transaction.add_rules(backend, rules) + + # PROTOCOL + elif type(rule.element) == Rich_Protocol: + protocol = rule.element.value + self.check_protocol(protocol) + + if enable: + transaction.add_chain(policy, "filter", "INPUT") + if enable and type(rule.action) == Rich_Mark: + transaction.add_chain(policy, "mangle", "PREROUTING") + + rules = backend.build_policy_protocol_rules( + enable, policy, protocol, None, rule) + transaction.add_rules(backend, rules) + + # MASQUERADE + elif type(rule.element) == Rich_Masquerade: + if enable: + transaction.add_chain(policy, "nat", "POSTROUTING") + transaction.add_chain(policy, "filter", "FORWARD_OUT") + for ipv in ipvs: + if backend.is_ipv_supported(ipv): + transaction.add_post(enable_ip_forwarding, ipv) + + rules = backend.build_policy_masquerade_rules(enable, policy, rule) + transaction.add_rules(backend, rules) + + # FORWARD PORT + elif type(rule.element) == Rich_ForwardPort: + port = rule.element.port + protocol = rule.element.protocol + toport = rule.element.to_port + toaddr = rule.element.to_address + for ipv in ipvs: + if backend.is_ipv_supported(ipv): + self.check_forward_port(ipv, port, protocol, toport, toaddr) + if toaddr and enable: + transaction.add_post(enable_ip_forwarding, ipv) + + if enable: + transaction.add_chain(policy, "nat", "PREROUTING") + + rules = backend.build_policy_forward_port_rules( + enable, policy, port, protocol, toport, + toaddr, rule) + transaction.add_rules(backend, rules) + + # SOURCE PORT + elif type(rule.element) == Rich_SourcePort: + port = rule.element.port + protocol = rule.element.protocol + self.check_port(port, protocol) + + if enable: + transaction.add_chain(policy, "filter", "INPUT") + if enable and type(rule.action) == Rich_Mark: + transaction.add_chain(policy, "mangle", "PREROUTING") + + rules = backend.build_policy_source_ports_rules( + enable, policy, protocol, port, None, rule) + transaction.add_rules(backend, rules) + + # ICMP BLOCK and ICMP TYPE + elif type(rule.element) == Rich_IcmpBlock or \ + type(rule.element) == Rich_IcmpType: + ict = self._fw.icmptype.get_icmptype(rule.element.name) + + if type(rule.element) == Rich_IcmpBlock and \ + rule.action and type(rule.action) == Rich_Accept: + # icmp block might have reject or drop action, but not accept + raise FirewallError(errors.INVALID_RULE, + "IcmpBlock not usable with accept action") + if ict.destination: + for ipv in ipvs: + if ipv in ict.destination \ + and not backend.is_ipv_supported(ipv): + raise FirewallError( + errors.INVALID_RULE, + "Icmp%s %s not usable with %s" % \ + ("Block" if type(rule.element) == \ + Rich_IcmpBlock else "Type", + rule.element.name, backend.name)) + + table = "filter" + if enable: + transaction.add_chain(policy, table, "INPUT") + transaction.add_chain(policy, table, "FORWARD_IN") + + rules = backend.build_policy_icmp_block_rules(enable, policy, ict, rule) + transaction.add_rules(backend, rules) + + elif rule.element is None: + if enable: + transaction.add_chain(policy, "filter", "INPUT") + if enable and type(rule.action) == Rich_Mark: + transaction.add_chain(policy, "mangle", "PREROUTING") + + rules = backend.build_policy_rich_source_destination_rules( + enable, policy, rule) + transaction.add_rules(backend, rules) + + # EVERYTHING ELSE + else: + raise FirewallError(errors.INVALID_RULE, "Unknown element %s" % + type(rule.element)) + + def _service(self, enable, policy, service, transaction, included_services=None): + svc = self._fw.service.get_service(service) + helpers = self.get_helpers_for_service_modules(svc.modules, enable) + helpers += self.get_helpers_for_service_helpers(svc.helpers) + helpers = sorted(set(helpers), key=lambda x: x.name) + + # First apply any services this service may include + if included_services is None: + included_services = [service] + for include in svc.includes: + if include in included_services: + continue + self.check_service(include) + included_services.append(include) + self._service(enable, policy, include, transaction, included_services=included_services) + + if enable: + transaction.add_chain(policy, "raw", "PREROUTING") + transaction.add_chain(policy, "filter", "INPUT") + + # build a list of (backend, destination). The destination may be ipv4, + # ipv6 or None + # + backends_ipv = [] + for ipv in ["ipv4", "ipv6"]: + if not self._fw.is_ipv_enabled(ipv): + continue + backend = self._fw.get_backend_by_ipv(ipv) + if len(svc.destination) > 0: + if ipv in svc.destination: + backends_ipv.append((backend, svc.destination[ipv])) + else: + if (backend, None) not in backends_ipv: + backends_ipv.append((backend, None)) + + for (backend,destination) in backends_ipv: + for helper in helpers: + module = helper.module + _module_short_name = get_nf_conntrack_short_name(module) + nat_module = helper.module.replace("conntrack", "nat") + transaction.add_module(nat_module) + if helper.family != "" and not backend.is_ipv_supported(helper.family): + # no support for family ipv, continue + continue + if len(helper.ports) < 1: + transaction.add_module(module) + else: + for (port,proto) in helper.ports: + rules = backend.build_policy_helper_ports_rules( + enable, policy, proto, port, + destination, helper.name, _module_short_name) + transaction.add_rules(backend, rules) + + for (port,proto) in svc.ports: + rules = backend.build_policy_ports_rules(enable, policy, proto, + port, destination) + transaction.add_rules(backend, rules) + + for protocol in svc.protocols: + rules = backend.build_policy_protocol_rules( + enable, policy, protocol, destination) + transaction.add_rules(backend, rules) + + for (port,proto) in svc.source_ports: + rules = backend.build_policy_source_ports_rules( + enable, policy, proto, port, destination) + transaction.add_rules(backend, rules) + + def _port(self, enable, policy, port, protocol, transaction): + if enable: + transaction.add_chain(policy, "filter", "INPUT") + + for backend in self._fw.enabled_backends(): + if not backend.policies_supported: + continue + + rules = backend.build_policy_ports_rules(enable, policy, protocol, + port) + transaction.add_rules(backend, rules) + + def _protocol(self, enable, policy, protocol, transaction): + if enable: + transaction.add_chain(policy, "filter", "INPUT") + + for backend in self._fw.enabled_backends(): + if not backend.policies_supported: + continue + + rules = backend.build_policy_protocol_rules(enable, policy, protocol) + transaction.add_rules(backend, rules) + + def _source_port(self, enable, policy, port, protocol, transaction): + if enable: + transaction.add_chain(policy, "filter", "INPUT") + + for backend in self._fw.enabled_backends(): + if not backend.policies_supported: + continue + + rules = backend.build_policy_source_ports_rules(enable, policy, protocol, port) + transaction.add_rules(backend, rules) + + def _masquerade(self, enable, policy, transaction): + if enable: + transaction.add_chain(policy, "nat", "POSTROUTING") + transaction.add_chain(policy, "filter", "FORWARD_OUT") + + ipv = "ipv4" + transaction.add_post(enable_ip_forwarding, ipv) + + backend = self._fw.get_backend_by_ipv(ipv) + rules = backend.build_policy_masquerade_rules(enable, policy) + transaction.add_rules(backend, rules) + + def _forward_port(self, enable, policy, transaction, port, protocol, + toport=None, toaddr=None): + if check_single_address("ipv6", toaddr): + ipv = "ipv6" + else: + ipv = "ipv4" + + if enable: + transaction.add_chain(policy, "nat", "PREROUTING") + + if toaddr and enable: + transaction.add_post(enable_ip_forwarding, ipv) + backend = self._fw.get_backend_by_ipv(ipv) + rules = backend.build_policy_forward_port_rules( + enable, policy, port, protocol, toport, + toaddr) + transaction.add_rules(backend, rules) + + def _icmp_block(self, enable, policy, icmp, transaction): + ict = self._fw.icmptype.get_icmptype(icmp) + + if enable: + transaction.add_chain(policy, "filter", "INPUT") + transaction.add_chain(policy, "filter", "FORWARD_IN") + + for backend in self._fw.enabled_backends(): + if not backend.policies_supported: + continue + skip_backend = False + + if ict.destination: + for ipv in ["ipv4", "ipv6"]: + if ipv in ict.destination: + if not backend.is_ipv_supported(ipv): + skip_backend = True + break + + if skip_backend: + continue + + rules = backend.build_policy_icmp_block_rules(enable, policy, ict) + transaction.add_rules(backend, rules) + + def _icmp_block_inversion(self, enable, policy, transaction): + target = self._policies[policy].target + + # Do not add general icmp accept rules into a trusted, block or drop + # policy. + if target in [ "DROP", "%%REJECT%%", "REJECT" ]: + return + if not self.query_icmp_block_inversion(policy) and target == "ACCEPT": + # ibi target and policy target are ACCEPT, no need to add an extra + # rule + return + + transaction.add_chain(policy, "filter", "INPUT") + transaction.add_chain(policy, "filter", "FORWARD_IN") + + for backend in self._fw.enabled_backends(): + if not backend.policies_supported: + continue + + rules = backend.build_policy_icmp_block_inversion_rules(enable, policy) + transaction.add_rules(backend, rules) + + def _get_table_chains_for_zone_dispatch(self, policy): + """Create a list of (table, chain) needed for zone dispatch""" + obj = self._policies[policy] + if obj.egress_zones[0] == "HOST": + # zone --> Host + return [("filter", "INPUT")] + elif obj.egress_zones[0] == "ANY": + # zone --> any + return [("filter", "FORWARD_IN"), ("nat", "PREROUTING"), + ("mangle", "PREROUTING"), ("raw", "PREROUTING")] + elif obj.ingress_zones[0] == "ANY": + # any --> zone + return [("filter", "FORWARD_OUT"), ("nat", "POSTROUTING")] + else: + return FirewallError("Invalid policy: %s" % (policy)) + + def policy_base_chain_name(self, policy, table, policy_prefix): + obj = self._fw.policy.get_policy(policy) + if obj.derived_from_zone: + if obj.egress_zones[0] == "HOST": + # zone --> Host + if table == "filter": + return "IN_" + obj.derived_from_zone + if table == "raw": + # FIXME: nftables doesn't actually use this. Only iptables + return "PRE_" + obj.derived_from_zone + elif obj.egress_zones[0] == "ANY": + # zone --> any + if table == "filter": + return "FWDI_" + obj.derived_from_zone + elif table in ["nat", "mangle", "raw"]: + return "PRE_" + obj.derived_from_zone + elif obj.ingress_zones[0] == "ANY": + # any --> zone + if table == "filter": + return "FWDO_" + obj.derived_from_zone + elif table == "nat": + return "POST_" + obj.derived_from_zone + return FirewallError("Can't convert policy to chain name: %s" % (policy)) + else: + return policy_prefix + policy diff --git a/src/firewall/core/fw_transaction.py b/src/firewall/core/fw_transaction.py index cc8b1e1..7639cdb 100644 --- a/src/firewall/core/fw_transaction.py +++ b/src/firewall/core/fw_transaction.py @@ -36,7 +36,7 @@ class FirewallTransaction(object): self.pre_funcs = [ ] # [ (func, args),.. ] self.post_funcs = [ ] # [ (func, args),.. ] self.fail_funcs = [ ] # [ (func, args),.. ] - self.chains = [ ] # [ (zone, table, chain),.. ] + self.chains = [ ] # [ (table, policy),.. ] self.modules = [ ] # [ module,.. ] def clear(self): @@ -68,16 +68,16 @@ class FirewallTransaction(object): def add_fail(self, func, *args): self.fail_funcs.append((func, args)) - def add_chain(self, zone, table, chain): - ztc = (zone, table, chain) - if ztc not in self.chains: - self.fw.zone.gen_chain_rules(zone, True, table, chain, self) - self.chains.append(ztc) + def add_chain(self, policy, table, chain): + tp = (table, policy) + if tp not in self.chains: + self.fw.policy.gen_chain_rules(policy, True, table, chain, self) + self.chains.append(tp) - def remove_chain(self, zone, table, chain): - ztc = (zone, table, chain) - if ztc in self.chains: - self.chains.remove(ztc) + def remove_chain(self, policy, table, chain): + tp = (table, policy) + if tp in self.chains: + self.chains.remove(tp) def add_module(self, module): if module not in self.modules: diff --git a/src/firewall/core/fw_zone.py b/src/firewall/core/fw_zone.py index d32d7a8..2f47e33 100644 --- a/src/firewall/core/fw_zone.py +++ b/src/firewall/core/fw_zone.py @@ -20,39 +20,39 @@ # import time -from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET, \ - ZONE_SOURCE_IPSET_TYPES -from firewall.core.logger import log -from firewall.functions import portStr, checkIPnMask, checkIP6nMask, \ - checkProtocol, enable_ip_forwarding, check_single_address, check_mac, \ - portInPortRange, get_nf_conntrack_short_name, coalescePortRange, breakPortRange -from firewall.core.rich import Rich_Rule, Rich_Accept, \ - Rich_Mark, Rich_Service, Rich_Port, Rich_Protocol, \ - Rich_Masquerade, Rich_ForwardPort, Rich_SourcePort, Rich_IcmpBlock, \ - Rich_IcmpType +import copy +from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET, SOURCE_IPSET_TYPES from firewall.core.fw_transaction import FirewallTransaction +from firewall.core.io.policy import Policy +from firewall.core.logger import log +from firewall.core.rich import Rich_Service, Rich_Port, Rich_Protocol, Rich_SourcePort, Rich_ForwardPort, \ + Rich_IcmpBlock, Rich_IcmpType, Rich_Masquerade, Rich_Mark +from firewall.functions import checkIPnMask, checkIP6nMask, check_mac from firewall import errors from firewall.errors import FirewallError from firewall.fw_types import LastUpdatedOrderedDict class FirewallZone(object): + ZONE_POLICY_PRIORITY = 0 + def __init__(self, fw): self._fw = fw - self._chains = { } self._zones = { } + self._zone_policies = { } def __repr__(self): - return '%s(%r, %r)' % (self.__class__, self._chains, self._zones) + return '%s(%r)' % (self.__class__, self._zones) def cleanup(self): - self._chains.clear() self._zones.clear() - - # transaction + self._zone_policies.clear() def new_transaction(self): return FirewallTransaction(self._fw) + def policy_name_from_zones(self, fromZone, toZone): + return "zone_{fromZone}_{toZone}".format(fromZone=fromZone, toZone=toZone) + # zones def get_zones(self): @@ -78,24 +78,77 @@ class FirewallZone(object): z = self._fw.check_zone(zone) return self._zones[z] - def _first_except(self, e, f, name, *args, **kwargs): - try: - f(name, *args, **kwargs) - except FirewallError as error: - if not e: - return error - return e + def policy_obj_from_zone_obj(self, z_obj, fromZone, toZone): + p_obj = Policy() + p_obj.derived_from_zone = z_obj.name + p_obj.name = self.policy_name_from_zones(fromZone, toZone) + p_obj.priority = self.ZONE_POLICY_PRIORITY + p_obj.target = z_obj.target + p_obj.ingress_zones = [fromZone] + p_obj.egress_zones = [toZone] + + # copy zone permanent config to policy permanent config + # WARN: This assumes the same attribute names. + # + for setting in ["services", "ports", + "masquerade", "forward_ports", + "source_ports", + "icmp_blocks", "rules", + "protocols"]: + if fromZone == z_obj.name and toZone == "HOST" and \ + setting in ["services", "ports", "source_ports", "icmp_blocks", "protocols"]: + # zone --> HOST + setattr(p_obj, setting, copy.deepcopy(getattr(z_obj, setting))) + elif fromZone == "ANY" and toZone == z_obj.name and setting in ["masquerade"]: + # any zone --> zone + setattr(p_obj, setting, copy.deepcopy(getattr(z_obj, setting))) + elif fromZone == z_obj.name and toZone == "ANY" and \ + setting in ["icmp_blocks", "forward_ports"]: + # zone --> any zone + setattr(p_obj, setting, copy.deepcopy(getattr(z_obj, setting))) + elif setting in ["rules"]: + p_obj.rules = [] + for rule in z_obj.rules: + current_policy = self.policy_name_from_zones(fromZone, toZone) + + if current_policy in self._rich_rule_to_policies(z_obj.name, rule): + p_obj.rules.append(copy.deepcopy(rule)) + + return p_obj def add_zone(self, obj): obj.settings = { x : LastUpdatedOrderedDict() - for x in [ "interfaces", "sources", - "services", "ports", - "masquerade", "forward_ports", - "source_ports", - "icmp_blocks", "rules", - "protocols", "icmp_block_inversion" ] } - + for x in ["interfaces", "sources", + "icmp_block_inversion"] } self._zones[obj.name] = obj + self._zone_policies[obj.name] = [] + + # Create policy objects, will need many: + # - (zone --> HOST) - ports, service, etc + # - (any zone --> zone) - masquerade + # - (zone --> any zone) - ICMP block, icmp block inversion + # - also includes forward-ports because it works on (nat, + # PREROUTING) and therefore applies to redirects to the local + # host or dnat to a different host. + # - also includes rich rule "mark" action for the same reason + # + for fromZone,toZone in [(obj.name, "HOST"), + ("ANY", obj.name), (obj.name, "ANY")]: + p_obj = self.policy_obj_from_zone_obj(obj, fromZone, toZone) + self._fw.policy.add_policy(p_obj) + self._zone_policies[obj.name].append(p_obj.name) + + self.copy_permanent_to_runtime(obj.name) + + def copy_permanent_to_runtime(self, zone): + obj = self._zones[zone] + + for arg in obj.interfaces: + self.add_interface(zone, arg, allow_apply=False) + for arg in obj.sources: + self.add_source(zone, arg, allow_apply=False) + if obj.icmp_block_inversion: + self.add_icmp_block_inversion(zone) def remove_zone(self, zone): obj = self._zones[zone] @@ -103,68 +156,14 @@ class FirewallZone(object): self.unapply_zone_settings(zone) obj.settings.clear() del self._zones[zone] + del self._zone_policies[zone] def apply_zones(self, use_transaction=None): - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - error = None for zone in self.get_zones(): - obj = self._zones[zone] - - # register icmp block inversion setting but don't apply - if obj.icmp_block_inversion: - error = self._first_except(error, self.add_icmp_block_inversion, obj.name, - use_transaction=transaction) - - if len(obj.interfaces) > 0 or len(obj.sources) > 0: - obj.applied = True - - log.debug1("Applying zone '%s'", obj.name) - - # load zone in case of missing services, icmptypes etc. - for args in obj.icmp_blocks: - error = self._first_except(error, self.add_icmp_block, obj.name, args, - use_transaction=transaction) - for args in obj.forward_ports: - error = self._first_except(error, self.add_forward_port, obj.name, *args, - use_transaction=transaction) - for args in obj.services: - error = self._first_except(error, self.add_service, obj.name, args, - use_transaction=transaction) - for args in obj.ports: - error = self._first_except(error, self.add_port, obj.name, *args, - use_transaction=transaction) - for args in obj.protocols: - error = self._first_except(error, self.add_protocol, obj.name, args, - use_transaction=transaction) - for args in obj.source_ports: - error = self._first_except(error, self.add_source_port, obj.name, *args, - use_transaction=transaction) - if obj.masquerade: - error = self._first_except(error, self.add_masquerade, obj.name, - use_transaction=transaction) - for args in obj.rules: - error = self._first_except(error, self.add_rule, obj.name, args, - use_transaction=transaction) - for args in obj.interfaces: - error = self._first_except(error, self.add_interface, obj.name, args, - use_transaction=transaction) - for args in obj.sources: - error = self._first_except(error, self.add_source, obj.name, args, - use_transaction=transaction) - # apply icmp accept/reject rule always - if obj.applied: - error = self._first_except(error, self._icmp_block_inversion, True, - obj.name, transaction) - - if use_transaction is None: - transaction.execute(True) - - if error: - raise error + z_obj = self._zones[zone] + if len(z_obj.interfaces) > 0 or len(z_obj.sources) > 0: + log.debug1("Applying zone '%s'", zone) + self.apply_zone_settings(zone, use_transaction=use_transaction) def set_zone_applied(self, zone, applied): obj = self._zones[zone] @@ -212,19 +211,6 @@ class FirewallZone(object): if use_transaction is None: transaction.execute(True) - # dynamic chain handling - - def _register_chains(self, zone, create, chains): - for (table, chain) in chains: - if create: - self._chains.setdefault(zone, { }).setdefault(table, [ ]).append(chain) - else: - self._chains[zone][table].remove(chain) - if len(self._chains[zone][table]) == 0: - del self._chains[zone][table] - if len(self._chains[zone]) == 0: - del self._chains[zone] - # settings # generate settings record with sender, timeout @@ -239,114 +225,62 @@ class FirewallZone(object): def get_settings(self, zone): return self.get_zone(zone).settings - def set_settings(self, zone, settings): - _obj = self.get_zone(zone) - - try: - for key in settings: - for args in settings[key]: - if args in _obj.settings[key]: - # do not add things, that are already active in the - # zone configuration, also do not restore date, - # sender and timeout - continue - if key == "icmp_blocks": - self.add_icmp_block(zone, args) - elif key == "forward_ports": - self.add_forward_port(zone, *args) - elif key == "services": - self.add_service(zone, args) - elif key == "ports": - self.add_port(zone, *args) - elif key == "protocols": - self.add_protocol(zone, *args) - elif key == "source_ports": - self.add_source_port(zone, *args) - elif key == "masquerade": - self.add_masquerade(zone) - elif key == "rules": - self.add_rule(zone, Rich_Rule(rule_str=args)) - elif key == "interfaces": - self.change_zone_of_interface(zone, args) - elif key == "sources": - self.change_zone_of_source(zone, args) - else: - log.warning("Zone '%s': Unknown setting '%s:%s', " - "unable to restore.", zone, key, args) - # restore old date, sender and timeout - if args in _obj.settings[key]: - _obj.settings[key][args] = settings[key][args] - - except FirewallError as msg: - log.warning(str(msg)) - - def __zone_settings(self, enable, zone, use_transaction=None): + def _zone_settings(self, enable, zone, transaction): + settings = self.get_settings(zone) + for key in settings: + for args in settings[key]: + if key == "interfaces": + self._interface(enable, zone, args, transaction) + elif key == "sources": + self._source(enable, zone, args[0], args[1], transaction) + elif key == "icmp_block_inversion": + continue + else: + log.warning("Zone '%s': Unknown setting '%s:%s', " + "unable to apply", zone, key, args) + # ICMP-block-inversion is always applied + if enable: + self._icmp_block_inversion(enable, zone, transaction) + + def apply_zone_settings(self, zone, use_transaction=None): _zone = self._fw.check_zone(zone) obj = self._zones[_zone] - if (enable and obj.applied) or (not enable and not obj.applied): + if obj.applied: return - if enable: - obj.applied = True + obj.applied = True if use_transaction is None: transaction = self.new_transaction() else: transaction = use_transaction - settings = self.get_settings(zone) - for key in settings: - for args in settings[key]: - try: - if key == "icmp_blocks": - self._icmp_block(enable, _zone, args, transaction) - elif key == "icmp_block_inversion": - continue - elif key == "forward_ports": - self._forward_port(enable, _zone, transaction, - *args) - elif key == "services": - self._service(enable, _zone, args, transaction) - elif key == "ports": - self._port(enable, _zone, args[0], args[1], - transaction) - elif key == "protocols": - self._protocol(enable, _zone, args, transaction) - elif key == "source_ports": - self._source_port(enable, _zone, args[0], args[1], - transaction) - elif key == "masquerade": - self._masquerade(enable, _zone, transaction) - elif key == "rules": - self.__rule(enable, _zone, Rich_Rule(rule_str=args), - transaction) - elif key == "interfaces": - self._interface(enable, _zone, args, transaction) - elif key == "sources": - self._source(enable, _zone, args[0], args[1], - transaction) - else: - log.warning("Zone '%s': Unknown setting '%s:%s', " - "unable to apply", zone, key, args) - except FirewallError as msg: - log.warning(str(msg)) + for policy in self._zone_policies[_zone]: + log.debug1("Applying policy (%s) derived from zone '%s'", policy, zone) + self._fw.policy.apply_policy_settings(policy, use_transaction=transaction) - if enable: - # add icmp rule(s) always - self._icmp_block_inversion(True, obj.name, transaction) + self._zone_settings(True, _zone, transaction) if use_transaction is None: - transaction.execute(enable) - - def apply_zone_settings(self, zone, use_transaction=None): - self.__zone_settings(True, zone, use_transaction) + transaction.execute(True) def unapply_zone_settings(self, zone, use_transaction=None): - self.__zone_settings(False, zone, use_transaction) + _zone = self._fw.check_zone(zone) + obj = self._zones[_zone] + if not obj.applied: + return - def unapply_zone_settings_if_unused(self, zone): - obj = self._zones[zone] - if len(obj.interfaces) == 0 and len(obj.sources) == 0: - self.unapply_zone_settings(zone) + if use_transaction is None: + transaction = self.new_transaction() + else: + transaction = use_transaction + + for policy in self._zone_policies[_zone]: + self._fw.policy.unapply_policy_settings(policy, use_transaction=transaction) + + self._zone_settings(False, _zone, transaction) + + if use_transaction is None: + transaction.execute(True) def get_config_with_settings(self, zone): """ @@ -390,7 +324,7 @@ class FirewallZone(object): return interface def add_interface(self, zone, interface, sender=None, - use_transaction=None): + use_transaction=None, allow_apply=True): self._fw.check_panic() _zone = self._fw.check_zone(zone) _obj = self._zones[_zone] @@ -413,12 +347,13 @@ class FirewallZone(object): else: transaction = use_transaction - if not _obj.applied: + if not _obj.applied and allow_apply: self.apply_zone_settings(zone, use_transaction=transaction) transaction.add_fail(self.set_zone_applied, _zone, False) - self._interface(True, _zone, interface, transaction) + if allow_apply: + self._interface(True, _zone, interface, transaction) self.__register_interface(_obj, interface_id, zone, sender) transaction.add_fail(self.__unregister_interface, _obj, @@ -494,7 +429,6 @@ class FirewallZone(object): if use_transaction is None: transaction.execute(True) -# self.unapply_zone_settings_if_unused(_zone) return _zone def __unregister_interface(self, _obj, interface_id): @@ -509,7 +443,7 @@ class FirewallZone(object): # SOURCES - def check_source(self, source): + def check_source(self, source, applied=False): if checkIPnMask(source): return "ipv4" elif checkIP6nMask(source): @@ -518,16 +452,18 @@ class FirewallZone(object): return "" elif source.startswith("ipset:"): self._check_ipset_type_for_source(source[6:]) - self._check_ipset_applied(source[6:]) + if applied: + self._check_ipset_applied(source[6:]) return self._ipset_family(source[6:]) else: raise FirewallError(errors.INVALID_ADDR, source) - def __source_id(self, source): - ipv = self.check_source(source) + def __source_id(self, source, applied=False): + ipv = self.check_source(source, applied=applied) return (ipv, source) - def add_source(self, zone, source, sender=None, use_transaction=None): + def add_source(self, zone, source, sender=None, use_transaction=None, + allow_apply=True): self._fw.check_panic() _zone = self._fw.check_zone(zone) _obj = self._zones[_zone] @@ -535,7 +471,7 @@ class FirewallZone(object): if check_mac(source): source = source.upper() - source_id = self.__source_id(source) + source_id = self.__source_id(source, applied=allow_apply) if source_id in _obj.settings["sources"]: raise FirewallError(errors.ZONE_ALREADY_SET, @@ -549,12 +485,13 @@ class FirewallZone(object): else: transaction = use_transaction - if not _obj.applied: + if not _obj.applied and allow_apply: self.apply_zone_settings(zone, use_transaction=transaction) transaction.add_fail(self.set_zone_applied, _zone, False) - self._source(True, _zone, source_id[0], source_id[1], transaction) + if allow_apply: + self._source(True, _zone, source_id[0], source_id[1], transaction) self.__register_source(_obj, source_id, zone, sender) transaction.add_fail(self.__unregister_source, _obj, source_id) @@ -617,7 +554,6 @@ class FirewallZone(object): if use_transaction is None: transaction.execute(True) -# self.unapply_zone_settings_if_unused(_zone) return _zone def __unregister_source(self, _obj, source_id): @@ -632,1328 +568,296 @@ class FirewallZone(object): def list_sources(self, zone): return [ k[1] for k in self.get_settings(zone)["sources"].keys() ] - # RICH LANGUAGE + def _interface(self, enable, zone, interface, transaction, append=False): + for backend in self._fw.enabled_backends(): + if not backend.policies_supported: + continue + for policy in self._zone_policies[zone]: + for (table, chain) in self._fw.policy._get_table_chains_for_zone_dispatch(policy): + # create needed chains if not done already + if enable: + transaction.add_chain(policy, table, chain) - def check_rule(self, rule): - rule.check() + rules = backend.build_zone_source_interface_rules(enable, + zone, policy, interface, table, chain, append) + transaction.add_rules(backend, rules) - def __rule_id(self, rule): - self.check_rule(rule) - return str(rule) + # IPSETS - def _rule_source_ipv(self, source): - if not source: + def _ipset_family(self, name): + if self._ipset_type(name) == "hash:mac": return None + return self._fw.ipset.get_family(name, applied=False) - if source.addr: - if checkIPnMask(source.addr): - return "ipv4" - elif checkIP6nMask(source.addr): - return "ipv6" - elif hasattr(source, "mac") and source.mac: - return "" - elif hasattr(source, "ipset") and source.ipset: - self._check_ipset_type_for_source(source.ipset) - self._check_ipset_applied(source.ipset) - return self._ipset_family(source.ipset) - - return None - - def __rule(self, enable, zone, rule, transaction): - self._rule_prepare(enable, zone, rule, transaction) - - def add_rule(self, zone, rule, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] - - rule_id = self.__rule_id(rule) - if rule_id in _obj.settings["rules"]: - raise FirewallError(errors.ALREADY_ENABLED, - "'%s' already in '%s'" % (rule, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self.__rule(True, _zone, rule, transaction) - - self.__register_rule(_obj, rule_id, timeout, sender) - transaction.add_fail(self.__unregister_rule, _obj, rule_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_rule(self, _obj, rule_id, timeout, sender): - _obj.settings["rules"][rule_id] = self.__gen_settings( - timeout, sender) - - def remove_rule(self, zone, rule, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - rule_id = self.__rule_id(rule) - if rule_id not in _obj.settings["rules"]: - raise FirewallError(errors.NOT_ENABLED, - "'%s' not in '%s'" % (rule, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self.__rule(False, _zone, rule, transaction) - - transaction.add_post(self.__unregister_rule, _obj, rule_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __unregister_rule(self, _obj, rule_id): - if rule_id in _obj.settings["rules"]: - del _obj.settings["rules"][rule_id] - - def query_rule(self, zone, rule): - return self.__rule_id(rule) in self.get_settings(zone)["rules"] - - def list_rules(self, zone): - return list(self.get_settings(zone)["rules"].keys()) - - # SERVICES - - def check_service(self, service): - self._fw.check_service(service) - - def __service_id(self, service): - self.check_service(service) - return service - - def add_service(self, zone, service, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] - - service_id = self.__service_id(service) - if service_id in _obj.settings["services"]: - raise FirewallError(errors.ALREADY_ENABLED, - "'%s' already in '%s'" % (service, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self._service(True, _zone, service, transaction) - - self.__register_service(_obj, service_id, timeout, sender) - transaction.add_fail(self.__unregister_service, _obj, service_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_service(self, _obj, service_id, timeout, sender): - _obj.settings["services"][service_id] = \ - self.__gen_settings(timeout, sender) - - def remove_service(self, zone, service, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] + def _ipset_type(self, name): + return self._fw.ipset.get_type(name, applied=False) - service_id = self.__service_id(service) - if service_id not in _obj.settings["services"]: - raise FirewallError(errors.NOT_ENABLED, - "'%s' not in '%s'" % (service, _zone)) + def _ipset_match_flags(self, name, flag): + return ",".join([flag] * self._fw.ipset.get_dimension(name)) - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction + def _check_ipset_applied(self, name): + return self._fw.ipset.check_applied(name) - if _obj.applied: - self._service(False, _zone, service, transaction) + def _check_ipset_type_for_source(self, name): + _type = self._ipset_type(name) + if _type not in SOURCE_IPSET_TYPES: + raise FirewallError( + errors.INVALID_IPSET, + "ipset '%s' with type '%s' not usable as source" % \ + (name, _type)) - transaction.add_post(self.__unregister_service, _obj, service_id) + def _source(self, enable, zone, ipv, source, transaction): + # For mac source bindings ipv is an empty string, the mac source will + # be added for ipv4 and ipv6 + for backend in [self._fw.get_backend_by_ipv(ipv)] if ipv else self._fw.enabled_backends(): + if not backend.policies_supported: + continue + for policy in self._zone_policies[zone]: + for (table, chain) in self._fw.policy._get_table_chains_for_zone_dispatch(policy): + # create needed chains if not done already + if enable: + transaction.add_chain(policy, table, chain) - if use_transaction is None: - transaction.execute(True) + rules = backend.build_zone_source_address_rules(enable, zone, + policy, source, table, chain) + transaction.add_rules(backend, rules) - return _zone + def add_service(self, zone, service, timeout=0, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.add_service(p_name, service, timeout, sender) + return zone - def __unregister_service(self, _obj, service_id): - if service_id in _obj.settings["services"]: - del _obj.settings["services"][service_id] + def remove_service(self, zone, service): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.remove_service(p_name, service) + return zone def query_service(self, zone, service): - return self.__service_id(service) in self.get_settings(zone)["services"] + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.query_service(p_name, service) def list_services(self, zone): - return self.get_settings(zone)["services"].keys() - - def get_helpers_for_service_helpers(self, helpers): - _helpers = [ ] - for helper in helpers: - try: - _helper = self._fw.helper.get_helper(helper) - except FirewallError: - raise FirewallError(errors.INVALID_HELPER, helper) - _helpers.append(_helper) - return _helpers - - def get_helpers_for_service_modules(self, modules, enable): - # If automatic helper assignment is turned off, helpers that - # do not have ports defined will be replaced by the helpers - # that the helper.module defines. - _helpers = [ ] - for module in modules: - try: - helper = self._fw.helper.get_helper(module) - except FirewallError: - raise FirewallError(errors.INVALID_HELPER, module) - if len(helper.ports) < 1: - _module_short_name = get_nf_conntrack_short_name(helper.module) - try: - _helper = self._fw.helper.get_helper(_module_short_name) - _helpers.append(_helper) - except FirewallError: - if enable: - log.warning("Helper '%s' is not available" % _module_short_name) - continue - else: - _helpers.append(helper) - return _helpers - - # PORTS - - def check_port(self, port, protocol): - self._fw.check_port(port) - self._fw.check_tcpudp(protocol) - - def __port_id(self, port, protocol): - self.check_port(port, protocol) - return (portStr(port, "-"), protocol) - - def add_port(self, zone, port, protocol, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] - - existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) - for port_id in existing_port_ids: - if portInPortRange(port, port_id[0]): - raise FirewallError(errors.ALREADY_ENABLED, - "'%s:%s' already in '%s'" % (port, protocol, _zone)) - - added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - for range in added_ranges: - self._port(True, _zone, portStr(range, "-"), protocol, transaction) - for range in removed_ranges: - self._port(False, _zone, portStr(range, "-"), protocol, transaction) - - for range in added_ranges: - port_id = self.__port_id(range, protocol) - self.__register_port(_obj, port_id, timeout, sender) - transaction.add_fail(self.__unregister_port, _obj, port_id) - for range in removed_ranges: - port_id = self.__port_id(range, protocol) - transaction.add_post(self.__unregister_port, _obj, port_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_port(self, _obj, port_id, timeout, sender): - _obj.settings["ports"][port_id] = \ - self.__gen_settings(timeout, sender) - - def remove_port(self, zone, port, protocol, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["ports"])) - for port_id in existing_port_ids: - if portInPortRange(port, port_id[0]): - break - else: - raise FirewallError(errors.NOT_ENABLED, - "'%s:%s' not in '%s'" % (port, protocol, _zone)) - - added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - for range in added_ranges: - self._port(True, _zone, portStr(range, "-"), protocol, transaction) - for range in removed_ranges: - self._port(False, _zone, portStr(range, "-"), protocol, transaction) - - for range in added_ranges: - port_id = self.__port_id(range, protocol) - self.__register_port(_obj, port_id, 0, None) - transaction.add_fail(self.__unregister_port, _obj, port_id) - for range in removed_ranges: - port_id = self.__port_id(range, protocol) - transaction.add_post(self.__unregister_port, _obj, port_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __unregister_port(self, _obj, port_id): - if port_id in _obj.settings["ports"]: - del _obj.settings["ports"][port_id] + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.list_services(p_name) + + def add_port(self, zone, port, protocol, timeout=0, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.add_port(p_name, port, protocol, timeout, sender) + return zone + + def remove_port(self, zone, port, protocol): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.remove_port(p_name, port, protocol) + return zone def query_port(self, zone, port, protocol): - for (_port, _protocol) in self.get_settings(zone)["ports"]: - if portInPortRange(port, _port) and protocol == _protocol: - return True - - return False + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.query_port(p_name, port, protocol) def list_ports(self, zone): - return list(self.get_settings(zone)["ports"].keys()) - - # PROTOCOLS - - def check_protocol(self, protocol): - if not checkProtocol(protocol): - raise FirewallError(errors.INVALID_PROTOCOL, protocol) - - def __protocol_id(self, protocol): - self.check_protocol(protocol) - return protocol - - def add_protocol(self, zone, protocol, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] - - protocol_id = self.__protocol_id(protocol) - if protocol_id in _obj.settings["protocols"]: - raise FirewallError(errors.ALREADY_ENABLED, - "'%s' already in '%s'" % (protocol, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.list_ports(p_name) + + def add_source_port(self, zone, source_port, protocol, timeout=0, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.add_source_port(p_name, source_port, protocol, timeout, sender) + return zone + + def remove_source_port(self, zone, source_port, protocol): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.remove_source_port(p_name, source_port, protocol) + return zone + + def query_source_port(self, zone, source_port, protocol): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.query_source_port(p_name, source_port, protocol) - if _obj.applied: - self._protocol(True, _zone, protocol, transaction) - - self.__register_protocol(_obj, protocol_id, timeout, sender) - transaction.add_fail(self.__unregister_protocol, _obj, protocol_id) + def list_source_ports(self, zone): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.list_source_ports(p_name) + + def _rich_rule_to_policies(self, zone, rule): + zone = self._fw.check_zone(zone) + if type(rule.action) == Rich_Mark: + return [self.policy_name_from_zones(zone, "ANY")] + elif type(rule.element) in [Rich_Service, Rich_Port, Rich_Protocol, + Rich_SourcePort]: + return [self.policy_name_from_zones(zone, "HOST")] + elif type(rule.element) in [Rich_IcmpBlock, Rich_IcmpType]: + return [self.policy_name_from_zones(zone, "HOST"), + self.policy_name_from_zones(zone, "ANY")] + elif type(rule.element) in [Rich_ForwardPort]: + return [self.policy_name_from_zones(zone, "ANY")] + elif type(rule.element) in [Rich_Masquerade]: + return [self.policy_name_from_zones("ANY", zone)] + elif rule.element is None: + return [self.policy_name_from_zones(zone, "HOST")] + else: + raise FirewallError("Rich rule type (%s) not handled." % (type(rule.element))) + + def add_rule(self, zone, rule, timeout=0, sender=None): + for p_name in self._rich_rule_to_policies(zone, rule): + self._fw.policy.add_rule(p_name, rule, timeout, sender) + return zone + + def remove_rule(self, zone, rule): + for p_name in self._rich_rule_to_policies(zone, rule): + self._fw.policy.remove_rule(p_name, rule) + return zone - if use_transaction is None: - transaction.execute(True) + def query_rule(self, zone, rule): + ret = True + for p_name in self._rich_rule_to_policies(zone, rule): + ret = ret and self._fw.policy.query_rule(p_name, rule) + return ret - return _zone + def list_rules(self, zone): + ret = set() + for p_name in [self.policy_name_from_zones(zone, "ANY"), + self.policy_name_from_zones(zone, "HOST"), + self.policy_name_from_zones("ANY", zone)]: + ret.update(set(self._fw.policy.list_rules(p_name))) + return list(ret) + + def add_protocol(self, zone, protocol, timeout=0, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.add_protocol(p_name, protocol, timeout, sender) + return zone + + def remove_protocol(self, zone, protocol): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.remove_protocol(p_name, protocol) + return zone - def __register_protocol(self, _obj, protocol_id, timeout, sender): - _obj.settings["protocols"][protocol_id] = \ - self.__gen_settings(timeout, sender) + def query_protocol(self, zone, protocol): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.query_protocol(p_name, protocol) - def remove_protocol(self, zone, protocol, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] + def list_protocols(self, zone): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + return self._fw.policy.list_protocols(p_name) + + def add_masquerade(self, zone, timeout=0, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones("ANY", zone) + self._fw.policy.add_masquerade(p_name, timeout, sender) + return zone + + def remove_masquerade(self, zone): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones("ANY", zone) + self._fw.policy.remove_masquerade(p_name) + return zone - protocol_id = self.__protocol_id(protocol) - if protocol_id not in _obj.settings["protocols"]: - raise FirewallError(errors.NOT_ENABLED, - "'%s' not in '%s'" % (protocol, _zone)) + def query_masquerade(self, zone): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones("ANY", zone) + return self._fw.policy.query_masquerade(p_name) - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction + def add_forward_port(self, zone, port, protocol, toport=None, + toaddr=None, timeout=0, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "ANY") + self._fw.policy.add_forward_port(p_name, port, protocol, toport, toaddr, + timeout, sender) + return zone - if _obj.applied: - self._protocol(False, _zone, protocol, transaction) + def remove_forward_port(self, zone, port, protocol, toport=None, + toaddr=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "ANY") + self._fw.policy.remove_forward_port(p_name, port, protocol, toport, toaddr) + return zone - transaction.add_post(self.__unregister_protocol, _obj, - protocol_id) + def query_forward_port(self, zone, port, protocol, toport=None, + toaddr=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "ANY") + return self._fw.policy.query_forward_port(p_name, port, protocol, toport, + toaddr) - if use_transaction is None: - transaction.execute(True) + def list_forward_ports(self, zone): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "ANY") + return self._fw.policy.list_forward_ports(p_name) - return _zone + def add_icmp_block(self, zone, icmp, timeout=0, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.add_icmp_block(p_name, icmp, timeout, sender) - def __unregister_protocol(self, _obj, protocol_id): - if protocol_id in _obj.settings["protocols"]: - del _obj.settings["protocols"][protocol_id] + p_name = self.policy_name_from_zones(zone, "ANY") + self._fw.policy.add_icmp_block(p_name, icmp, timeout, sender) + return zone - def query_protocol(self, zone, protocol): - return self.__protocol_id(protocol) in self.get_settings(zone)["protocols"] + def remove_icmp_block(self, zone, icmp): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.remove_icmp_block(p_name, icmp) - def list_protocols(self, zone): - return list(self.get_settings(zone)["protocols"].keys()) + p_name = self.policy_name_from_zones(zone, "ANY") + self._fw.policy.remove_icmp_block(p_name, icmp) + return zone - # SOURCE PORTS + def query_icmp_block(self, zone, icmp): + zone = self._fw.check_zone(zone) + p_name_host = self.policy_name_from_zones(zone, "HOST") + p_name_fwd = self.policy_name_from_zones(zone, "ANY") + return self._fw.policy.query_icmp_block(p_name_host, icmp) and \ + self._fw.policy.query_icmp_block(p_name_fwd, icmp) - def __source_port_id(self, port, protocol): - self.check_port(port, protocol) - return (portStr(port, "-"), protocol) + def list_icmp_blocks(self, zone): + zone = self._fw.check_zone(zone) + p_name_host = self.policy_name_from_zones(zone, "HOST") + p_name_fwd = self.policy_name_from_zones(zone, "ANY") + return sorted(set(self._fw.policy.list_icmp_blocks(p_name_host) + + self._fw.policy.list_icmp_blocks(p_name_fwd))) - def add_source_port(self, zone, port, protocol, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] + def add_icmp_block_inversion(self, zone, sender=None): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.add_icmp_block_inversion(p_name, sender) - existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) - for port_id in existing_port_ids: - if portInPortRange(port, port_id[0]): - raise FirewallError(errors.ALREADY_ENABLED, - "'%s:%s' already in '%s'" % (port, protocol, _zone)) + p_name = self.policy_name_from_zones(zone, "ANY") + self._fw.policy.add_icmp_block_inversion(p_name, sender) + return zone - added_ranges, removed_ranges = coalescePortRange(port, [_port for (_port, _protocol) in existing_port_ids]) + def _icmp_block_inversion(self, enable, zone, transaction): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy._icmp_block_inversion(enable, p_name, transaction) - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction + p_name = self.policy_name_from_zones(zone, "ANY") + self._fw.policy._icmp_block_inversion(enable, p_name, transaction) - if _obj.applied: - for range in added_ranges: - self._source_port(True, _zone, portStr(range, "-"), protocol, transaction) - for range in removed_ranges: - self._source_port(False, _zone, portStr(range, "-"), protocol, transaction) + def remove_icmp_block_inversion(self, zone): + zone = self._fw.check_zone(zone) + p_name = self.policy_name_from_zones(zone, "HOST") + self._fw.policy.remove_icmp_block_inversion(p_name) - for range in added_ranges: - port_id = self.__source_port_id(range, protocol) - self.__register_source_port(_obj, port_id, timeout, sender) - transaction.add_fail(self.__unregister_source_port, _obj, port_id) - for range in removed_ranges: - port_id = self.__source_port_id(range, protocol) - transaction.add_post(self.__unregister_source_port, _obj, port_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_source_port(self, _obj, port_id, timeout, sender): - _obj.settings["source_ports"][port_id] = \ - self.__gen_settings(timeout, sender) - - def remove_source_port(self, zone, port, protocol, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - existing_port_ids = list(filter(lambda x: x[1] == protocol, _obj.settings["source_ports"])) - for port_id in existing_port_ids: - if portInPortRange(port, port_id[0]): - break - else: - raise FirewallError(errors.NOT_ENABLED, - "'%s:%s' not in '%s'" % (port, protocol, _zone)) - - added_ranges, removed_ranges = breakPortRange(port, [_port for (_port, _protocol) in existing_port_ids]) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - for range in added_ranges: - self._source_port(True, _zone, portStr(range, "-"), protocol, transaction) - for range in removed_ranges: - self._source_port(False, _zone, portStr(range, "-"), protocol, transaction) - - for range in added_ranges: - port_id = self.__source_port_id(range, protocol) - self.__register_source_port(_obj, port_id, 0, None) - transaction.add_fail(self.__unregister_source_port, _obj, port_id) - for range in removed_ranges: - port_id = self.__source_port_id(range, protocol) - transaction.add_post(self.__unregister_source_port, _obj, port_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __unregister_source_port(self, _obj, port_id): - if port_id in _obj.settings["source_ports"]: - del _obj.settings["source_ports"][port_id] - - def query_source_port(self, zone, port, protocol): - for (_port, _protocol) in self.get_settings(zone)["source_ports"]: - if portInPortRange(port, _port) and protocol == _protocol: - return True - - return False - - def list_source_ports(self, zone): - return list(self.get_settings(zone)["source_ports"].keys()) - - # MASQUERADE - - def __masquerade_id(self): - return True - - def add_masquerade(self, zone, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] - - masquerade_id = self.__masquerade_id() - if masquerade_id in _obj.settings["masquerade"]: - raise FirewallError(errors.ALREADY_ENABLED, - "masquerade already enabled in '%s'" % _zone) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self._masquerade(True, _zone, transaction) - - self.__register_masquerade(_obj, masquerade_id, timeout, sender) - transaction.add_fail(self.__unregister_masquerade, _obj, masquerade_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_masquerade(self, _obj, masquerade_id, timeout, sender): - _obj.settings["masquerade"][masquerade_id] = \ - self.__gen_settings(timeout, sender) - - def remove_masquerade(self, zone, use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - masquerade_id = self.__masquerade_id() - if masquerade_id not in _obj.settings["masquerade"]: - raise FirewallError(errors.NOT_ENABLED, - "masquerade not enabled in '%s'" % _zone) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self._masquerade(False, _zone, transaction) - - transaction.add_post(self.__unregister_masquerade, _obj, masquerade_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __unregister_masquerade(self, _obj, masquerade_id): - if masquerade_id in _obj.settings["masquerade"]: - del _obj.settings["masquerade"][masquerade_id] - - def query_masquerade(self, zone): - return self.__masquerade_id() in self.get_settings(zone)["masquerade"] - - # PORT FORWARDING - - def check_forward_port(self, ipv, port, protocol, toport=None, toaddr=None): - self._fw.check_port(port) - self._fw.check_tcpudp(protocol) - if toport: - self._fw.check_port(toport) - if toaddr: - if not check_single_address(ipv, toaddr): - raise FirewallError(errors.INVALID_ADDR, toaddr) - if not toport and not toaddr: - raise FirewallError( - errors.INVALID_FORWARD, - "port-forwarding is missing to-port AND to-addr") - - def __forward_port_id(self, port, protocol, toport=None, toaddr=None): - if check_single_address("ipv6", toaddr): - self.check_forward_port("ipv6", port, protocol, toport, toaddr) - else: - self.check_forward_port("ipv4", port, protocol, toport, toaddr) - return (portStr(port, "-"), protocol, - portStr(toport, "-"), str(toaddr)) - - def add_forward_port(self, zone, port, protocol, toport=None, - toaddr=None, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] - - forward_id = self.__forward_port_id(port, protocol, toport, toaddr) - if forward_id in _obj.settings["forward_ports"]: - raise FirewallError(errors.ALREADY_ENABLED, - "'%s:%s:%s:%s' already in '%s'" % \ - (port, protocol, toport, toaddr, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self._forward_port(True, _zone, transaction, port, protocol, - toport, toaddr) - - self.__register_forward_port(_obj, forward_id, timeout, sender) - transaction.add_fail(self.__unregister_forward_port, _obj, forward_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_forward_port(self, _obj, forward_id, timeout, sender): - _obj.settings["forward_ports"][forward_id] = \ - self.__gen_settings(timeout, sender) - - def remove_forward_port(self, zone, port, protocol, toport=None, - toaddr=None, use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - forward_id = self.__forward_port_id(port, protocol, toport, toaddr) - if forward_id not in _obj.settings["forward_ports"]: - raise FirewallError(errors.NOT_ENABLED, - "'%s:%s:%s:%s' not in '%s'" % \ - (port, protocol, toport, toaddr, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self._forward_port(False, _zone, transaction, port, protocol, - toport, toaddr) - - transaction.add_post(self.__unregister_forward_port, _obj, forward_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __unregister_forward_port(self, _obj, forward_id): - if forward_id in _obj.settings["forward_ports"]: - del _obj.settings["forward_ports"][forward_id] - - def query_forward_port(self, zone, port, protocol, toport=None, - toaddr=None): - forward_id = self.__forward_port_id(port, protocol, toport, toaddr) - return forward_id in self.get_settings(zone)["forward_ports"] - - def list_forward_ports(self, zone): - return list(self.get_settings(zone)["forward_ports"].keys()) - - # ICMP BLOCK - - def check_icmp_block(self, icmp): - self._fw.check_icmptype(icmp) - - def __icmp_block_id(self, icmp): - self.check_icmp_block(icmp) - return icmp - - def add_icmp_block(self, zone, icmp, timeout=0, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_timeout(timeout) - self._fw.check_panic() - _obj = self._zones[_zone] - - icmp_id = self.__icmp_block_id(icmp) - if icmp_id in _obj.settings["icmp_blocks"]: - raise FirewallError(errors.ALREADY_ENABLED, - "'%s' already in '%s'" % (icmp, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self._icmp_block(True, _zone, icmp, transaction) - - self.__register_icmp_block(_obj, icmp_id, timeout, sender) - transaction.add_fail(self.__unregister_icmp_block, _obj, icmp_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_icmp_block(self, _obj, icmp_id, timeout, sender): - _obj.settings["icmp_blocks"][icmp_id] = \ - self.__gen_settings(timeout, sender) - - def remove_icmp_block(self, zone, icmp, use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - icmp_id = self.__icmp_block_id(icmp) - if icmp_id not in _obj.settings["icmp_blocks"]: - raise FirewallError(errors.NOT_ENABLED, - "'%s' not in '%s'" % (icmp, _zone)) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - self._icmp_block(False, _zone, icmp, transaction) - - transaction.add_post(self.__unregister_icmp_block, _obj, icmp_id) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __unregister_icmp_block(self, _obj, icmp_id): - if icmp_id in _obj.settings["icmp_blocks"]: - del _obj.settings["icmp_blocks"][icmp_id] - - def query_icmp_block(self, zone, icmp): - return self.__icmp_block_id(icmp) in self.get_settings(zone)["icmp_blocks"] - - def list_icmp_blocks(self, zone): - return self.get_settings(zone)["icmp_blocks"].keys() - - # ICMP BLOCK INVERSION - - def __icmp_block_inversion_id(self): - return True - - def add_icmp_block_inversion(self, zone, sender=None, - use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - icmp_block_inversion_id = self.__icmp_block_inversion_id() - if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: - raise FirewallError( - errors.ALREADY_ENABLED, - "icmp-block-inversion already enabled in '%s'" % _zone) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - # undo icmp blocks - for args in self.get_settings(_zone)["icmp_blocks"]: - self._icmp_block(False, _zone, args, transaction) - - self._icmp_block_inversion(False, _zone, transaction) - - self.__register_icmp_block_inversion(_obj, icmp_block_inversion_id, - sender) - transaction.add_fail(self.__undo_icmp_block_inversion, _zone, _obj, - icmp_block_inversion_id) - - # redo icmp blocks - if _obj.applied: - for args in self.get_settings(_zone)["icmp_blocks"]: - self._icmp_block(True, _zone, args, transaction) - - self._icmp_block_inversion(True, _zone, transaction) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __register_icmp_block_inversion(self, _obj, icmp_block_inversion_id, - sender): - _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] = \ - self.__gen_settings(0, sender) - - def __undo_icmp_block_inversion(self, _zone, _obj, icmp_block_inversion_id): - transaction = self.new_transaction() - - # undo icmp blocks - if _obj.applied: - for args in self.get_settings(_zone)["icmp_blocks"]: - self._icmp_block(False, _zone, args, transaction) - - if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: - del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] - - # redo icmp blocks - if _obj.applied: - for args in self.get_settings(_zone)["icmp_blocks"]: - self._icmp_block(True, _zone, args, transaction) - - transaction.execute(True) - - def remove_icmp_block_inversion(self, zone, use_transaction=None): - _zone = self._fw.check_zone(zone) - self._fw.check_panic() - _obj = self._zones[_zone] - - icmp_block_inversion_id = self.__icmp_block_inversion_id() - if icmp_block_inversion_id not in _obj.settings["icmp_block_inversion"]: - raise FirewallError( - errors.NOT_ENABLED, - "icmp-block-inversion not enabled in '%s'" % _zone) - - if use_transaction is None: - transaction = self.new_transaction() - else: - transaction = use_transaction - - if _obj.applied: - # undo icmp blocks - for args in self.get_settings(_zone)["icmp_blocks"]: - self._icmp_block(False, _zone, args, transaction) - - self._icmp_block_inversion(False, _zone, transaction) - - self.__unregister_icmp_block_inversion(_obj, - icmp_block_inversion_id) - transaction.add_fail(self.__register_icmp_block_inversion, _obj, - icmp_block_inversion_id, None) - - # redo icmp blocks - if _obj.applied: - for args in self.get_settings(_zone)["icmp_blocks"]: - self._icmp_block(True, _zone, args, transaction) - - self._icmp_block_inversion(True, _zone, transaction) - - if use_transaction is None: - transaction.execute(True) - - return _zone - - def __unregister_icmp_block_inversion(self, _obj, icmp_block_inversion_id): - if icmp_block_inversion_id in _obj.settings["icmp_block_inversion"]: - del _obj.settings["icmp_block_inversion"][icmp_block_inversion_id] + p_name = self.policy_name_from_zones(zone, "ANY") + self._fw.policy.remove_icmp_block_inversion(p_name) + return zone def query_icmp_block_inversion(self, zone): - return self.__icmp_block_inversion_id() in \ - self.get_settings(zone)["icmp_block_inversion"] - - # dynamic chain handling - - def gen_chain_rules(self, zone, create, table, chain, transaction): - if create: - if zone in self._chains and \ - table in self._chains[zone] and \ - chain in self._chains[zone][table]: - return - else: - if zone not in self._chains or \ - table not in self._chains[zone] or \ - chain not in self._chains[zone][table]: - return - - for backend in self._fw.enabled_backends(): - if backend.zones_supported and \ - table in backend.get_available_tables(): - rules = backend.build_zone_chain_rules(zone, table, chain) - transaction.add_rules(backend, rules) - - self._register_chains(zone, create, [(table, chain)]) - transaction.add_fail(self._register_chains, zone, create, [(table, chain)]) - - def _interface(self, enable, zone, interface, transaction, - append=False): - for backend in self._fw.enabled_backends(): - if not backend.zones_supported: - continue - for table in backend.get_available_tables(): - for chain in backend.get_zone_table_chains(table): - # create needed chains if not done already - if enable: - transaction.add_chain(zone, table, chain) - - rules = backend.build_zone_source_interface_rules(enable, - zone, interface, table, chain, append) - transaction.add_rules(backend, rules) - - # IPSETS - - def _ipset_family(self, name): - if self._fw.ipset.get_type(name) == "hash:mac": - return None - return self._fw.ipset.get_family(name) - - def __ipset_type(self, name): - return self._fw.ipset.get_type(name) - - def _ipset_match_flags(self, name, flag): - return ",".join([flag] * self._fw.ipset.get_dimension(name)) - - def _check_ipset_applied(self, name): - return self._fw.ipset.check_applied(name) - - def _check_ipset_type_for_source(self, name): - _type = self.__ipset_type(name) - if _type not in ZONE_SOURCE_IPSET_TYPES: - raise FirewallError( - errors.INVALID_IPSET, - "ipset '%s' with type '%s' not usable as source" % \ - (name, _type)) - - def _source(self, enable, zone, ipv, source, transaction): - # For mac source bindings ipv is an empty string, the mac source will - # be added for ipv4 and ipv6 - for backend in [self._fw.get_backend_by_ipv(ipv)] if ipv else self._fw.enabled_backends(): - if not backend.zones_supported: - continue - for table in backend.get_available_tables(): - for chain in backend.get_zone_table_chains(table): - # create needed chains if not done already - if enable: - transaction.add_chain(zone, table, chain) - - rules = backend.build_zone_source_address_rules(enable, zone, - source, table, chain) - transaction.add_rules(backend, rules) - - def _rule_prepare(self, enable, zone, rule, transaction): - if rule.family is not None: - ipvs = [ rule.family ] - else: - ipvs = [ipv for ipv in ["ipv4", "ipv6"] if self._fw.is_ipv_enabled(ipv)] - - source_ipv = self._rule_source_ipv(rule.source) - if source_ipv is not None and source_ipv != "": - if rule.family is not None: - # rule family is defined by user, no way to change it - if rule.family != source_ipv: - raise FirewallError(errors.INVALID_RULE, - "Source address family '%s' conflicts with rule family '%s'." % (source_ipv, rule.family)) - else: - # use the source family as rule family - ipvs = [ source_ipv ] - - # add an element to object to allow backends to know what ipvs this applies to - rule.ipvs = ipvs - - for backend in set([self._fw.get_backend_by_ipv(x) for x in ipvs]): - # SERVICE - if type(rule.element) == Rich_Service: - svc = self._fw.service.get_service(rule.element.name) - - destinations = [] - if len(svc.destination) > 0: - if rule.destination: - # we can not use two destinations at the same time - raise FirewallError(errors.INVALID_RULE, - "Destination conflict with service.") - for ipv in ipvs: - if ipv in svc.destination and backend.is_ipv_supported(ipv): - destinations.append(svc.destination[ipv]) - else: - # dummy for the following for loop - destinations.append(None) - - for destination in destinations: - if enable: - transaction.add_chain(zone, "filter", "INPUT") - transaction.add_chain(zone, "raw", "PREROUTING") - - if type(rule.action) == Rich_Accept: - # only load modules for accept action - helpers = self.get_helpers_for_service_modules(svc.modules, - enable) - helpers += self.get_helpers_for_service_helpers(svc.helpers) - helpers = sorted(set(helpers), key=lambda x: x.name) - - modules = [ ] - for helper in helpers: - module = helper.module - _module_short_name = get_nf_conntrack_short_name(module) - nat_module = module.replace("conntrack", "nat") - modules.append(nat_module) - if helper.family != "" and not backend.is_ipv_supported(helper.family): - # no support for family ipv, continue - continue - if len(helper.ports) < 1: - modules.append(module) - else: - for (port,proto) in helper.ports: - rules = backend.build_zone_helper_ports_rules( - enable, zone, proto, port, - destination, helper.name, _module_short_name) - transaction.add_rules(backend, rules) - transaction.add_modules(modules) - - # create rules - for (port,proto) in svc.ports: - if enable and type(rule.action) == Rich_Mark: - transaction.add_chain(zone, "mangle", "PREROUTING") - rules = backend.build_zone_ports_rules( - enable, zone, proto, port, destination, rule) - transaction.add_rules(backend, rules) - - for proto in svc.protocols: - if enable and type(rule.action) == Rich_Mark: - transaction.add_chain(zone, "mangle", "PREROUTING") - rules = backend.build_zone_protocol_rules( - enable, zone, proto, destination, rule) - transaction.add_rules(backend, rules) - - # create rules - for (port,proto) in svc.source_ports: - if enable and type(rule.action) == Rich_Mark: - transaction.add_chain(zone, "mangle", "PREROUTING") - rules = backend.build_zone_source_ports_rules( - enable, zone, proto, port, destination, rule) - transaction.add_rules(backend, rules) - - # PORT - elif type(rule.element) == Rich_Port: - port = rule.element.port - protocol = rule.element.protocol - self.check_port(port, protocol) - - if enable: - transaction.add_chain(zone, "filter", "INPUT") - if enable and type(rule.action) == Rich_Mark: - transaction.add_chain(zone, "mangle", "PREROUTING") - - rules = backend.build_zone_ports_rules( - enable, zone, protocol, port, None, rule) - transaction.add_rules(backend, rules) - - # PROTOCOL - elif type(rule.element) == Rich_Protocol: - protocol = rule.element.value - self.check_protocol(protocol) - - if enable: - transaction.add_chain(zone, "filter", "INPUT") - if enable and type(rule.action) == Rich_Mark: - transaction.add_chain(zone, "mangle", "PREROUTING") - - rules = backend.build_zone_protocol_rules( - enable, zone, protocol, None, rule) - transaction.add_rules(backend, rules) - - # MASQUERADE - elif type(rule.element) == Rich_Masquerade: - if enable: - transaction.add_chain(zone, "nat", "POSTROUTING") - transaction.add_chain(zone, "filter", "FORWARD_OUT") - for ipv in ipvs: - if backend.is_ipv_supported(ipv): - transaction.add_post(enable_ip_forwarding, ipv) - - rules = backend.build_zone_masquerade_rules(enable, zone, rule) - transaction.add_rules(backend, rules) - - # FORWARD PORT - elif type(rule.element) == Rich_ForwardPort: - port = rule.element.port - protocol = rule.element.protocol - toport = rule.element.to_port - toaddr = rule.element.to_address - for ipv in ipvs: - if backend.is_ipv_supported(ipv): - self.check_forward_port(ipv, port, protocol, toport, toaddr) - if toaddr and enable: - transaction.add_post(enable_ip_forwarding, ipv) - - if enable: - transaction.add_chain(zone, "nat", "PREROUTING") - - rules = backend.build_zone_forward_port_rules( - enable, zone, port, protocol, toport, - toaddr, rule) - transaction.add_rules(backend, rules) - - # SOURCE PORT - elif type(rule.element) == Rich_SourcePort: - port = rule.element.port - protocol = rule.element.protocol - self.check_port(port, protocol) - - if enable: - transaction.add_chain(zone, "filter", "INPUT") - if enable and type(rule.action) == Rich_Mark: - transaction.add_chain(zone, "mangle", "PREROUTING") - - rules = backend.build_zone_source_ports_rules( - enable, zone, protocol, port, None, rule) - transaction.add_rules(backend, rules) - - # ICMP BLOCK and ICMP TYPE - elif type(rule.element) == Rich_IcmpBlock or \ - type(rule.element) == Rich_IcmpType: - ict = self._fw.icmptype.get_icmptype(rule.element.name) - - if type(rule.element) == Rich_IcmpBlock and \ - rule.action and type(rule.action) == Rich_Accept: - # icmp block might have reject or drop action, but not accept - raise FirewallError(errors.INVALID_RULE, - "IcmpBlock not usable with accept action") - if ict.destination: - for ipv in ipvs: - if ipv in ict.destination \ - and not backend.is_ipv_supported(ipv): - raise FirewallError( - errors.INVALID_RULE, - "Icmp%s %s not usable with %s" % \ - ("Block" if type(rule.element) == \ - Rich_IcmpBlock else "Type", - rule.element.name, backend.name)) - - table = "filter" - if enable: - transaction.add_chain(zone, table, "INPUT") - transaction.add_chain(zone, table, "FORWARD_IN") - - rules = backend.build_zone_icmp_block_rules(enable, zone, ict, rule) - transaction.add_rules(backend, rules) - - elif rule.element is None: - if enable: - transaction.add_chain(zone, "filter", "INPUT") - if enable and type(rule.action) == Rich_Mark: - transaction.add_chain(zone, "mangle", "PREROUTING") - - rules = backend.build_zone_rich_source_destination_rules( - enable, zone, rule) - transaction.add_rules(backend, rules) - - # EVERYTHING ELSE - else: - raise FirewallError(errors.INVALID_RULE, "Unknown element %s" % - type(rule.element)) - - def _service(self, enable, zone, service, transaction, included_services=None): - svc = self._fw.service.get_service(service) - helpers = self.get_helpers_for_service_modules(svc.modules, enable) - helpers += self.get_helpers_for_service_helpers(svc.helpers) - helpers = sorted(set(helpers), key=lambda x: x.name) - - # First apply any services this service may include - if included_services is None: - included_services = [service] - for include in svc.includes: - if include in included_services: - continue - self.check_service(include) - included_services.append(include) - self._service(enable, zone, include, transaction, included_services=included_services) - - if enable: - transaction.add_chain(zone, "raw", "PREROUTING") - transaction.add_chain(zone, "filter", "INPUT") - - # build a list of (backend, destination). The destination may be ipv4, - # ipv6 or None - # - backends_ipv = [] - for ipv in ["ipv4", "ipv6"]: - if not self._fw.is_ipv_enabled(ipv): - continue - backend = self._fw.get_backend_by_ipv(ipv) - if len(svc.destination) > 0: - if ipv in svc.destination: - backends_ipv.append((backend, svc.destination[ipv])) - else: - if (backend, None) not in backends_ipv: - backends_ipv.append((backend, None)) - - for (backend,destination) in backends_ipv: - for helper in helpers: - module = helper.module - _module_short_name = get_nf_conntrack_short_name(module) - nat_module = helper.module.replace("conntrack", "nat") - transaction.add_module(nat_module) - if helper.family != "" and not backend.is_ipv_supported(helper.family): - # no support for family ipv, continue - continue - if len(helper.ports) < 1: - transaction.add_module(module) - else: - for (port,proto) in helper.ports: - rules = backend.build_zone_helper_ports_rules( - enable, zone, proto, port, - destination, helper.name, _module_short_name) - transaction.add_rules(backend, rules) - - for (port,proto) in svc.ports: - rules = backend.build_zone_ports_rules(enable, zone, proto, - port, destination) - transaction.add_rules(backend, rules) - - for protocol in svc.protocols: - rules = backend.build_zone_protocol_rules( - enable, zone, protocol, destination) - transaction.add_rules(backend, rules) - - for (port,proto) in svc.source_ports: - rules = backend.build_zone_source_ports_rules( - enable, zone, proto, port, destination) - transaction.add_rules(backend, rules) - - def _port(self, enable, zone, port, protocol, transaction): - if enable: - transaction.add_chain(zone, "filter", "INPUT") - - for backend in self._fw.enabled_backends(): - if not backend.zones_supported: - continue - - rules = backend.build_zone_ports_rules(enable, zone, protocol, - port) - transaction.add_rules(backend, rules) - - def _protocol(self, enable, zone, protocol, transaction): - if enable: - transaction.add_chain(zone, "filter", "INPUT") - - for backend in self._fw.enabled_backends(): - if not backend.zones_supported: - continue - - rules = backend.build_zone_protocol_rules(enable, zone, protocol) - transaction.add_rules(backend, rules) - - def _source_port(self, enable, zone, port, protocol, transaction): - if enable: - transaction.add_chain(zone, "filter", "INPUT") - - for backend in self._fw.enabled_backends(): - if not backend.zones_supported: - continue - - rules = backend.build_zone_source_ports_rules(enable, zone, protocol, port) - transaction.add_rules(backend, rules) - - def _masquerade(self, enable, zone, transaction): - if enable: - transaction.add_chain(zone, "nat", "POSTROUTING") - transaction.add_chain(zone, "filter", "FORWARD_OUT") - - ipv = "ipv4" - transaction.add_post(enable_ip_forwarding, ipv) - - backend = self._fw.get_backend_by_ipv(ipv) - rules = backend.build_zone_masquerade_rules(enable, zone) - transaction.add_rules(backend, rules) - - def _forward_port(self, enable, zone, transaction, port, protocol, - toport=None, toaddr=None): - if check_single_address("ipv6", toaddr): - ipv = "ipv6" - else: - ipv = "ipv4" - - if enable: - transaction.add_chain(zone, "nat", "PREROUTING") - - if toaddr and enable: - transaction.add_post(enable_ip_forwarding, ipv) - backend = self._fw.get_backend_by_ipv(ipv) - rules = backend.build_zone_forward_port_rules( - enable, zone, port, protocol, toport, - toaddr) - transaction.add_rules(backend, rules) - - def _icmp_block(self, enable, zone, icmp, transaction): - ict = self._fw.icmptype.get_icmptype(icmp) - - if enable: - transaction.add_chain(zone, "filter", "INPUT") - transaction.add_chain(zone, "filter", "FORWARD_IN") - - for backend in self._fw.enabled_backends(): - if not backend.zones_supported: - continue - skip_backend = False - - if ict.destination: - for ipv in ["ipv4", "ipv6"]: - if ipv in ict.destination: - if not backend.is_ipv_supported(ipv): - skip_backend = True - break - - if skip_backend: - continue - - rules = backend.build_zone_icmp_block_rules(enable, zone, ict) - transaction.add_rules(backend, rules) - - def _icmp_block_inversion(self, enable, zone, transaction): - target = self._zones[zone].target - - # Do not add general icmp accept rules into a trusted, block or drop - # zone. - if target in [ "DROP", "%%REJECT%%", "REJECT" ]: - return - if not self.query_icmp_block_inversion(zone) and target == "ACCEPT": - # ibi target and zone target are ACCEPT, no need to add an extra - # rule - return - - transaction.add_chain(zone, "filter", "INPUT") - transaction.add_chain(zone, "filter", "FORWARD_IN") - - for backend in self._fw.enabled_backends(): - if not backend.zones_supported: - continue - - rules = backend.build_zone_icmp_block_inversion_rules(enable, zone) - transaction.add_rules(backend, rules) + zone = self._fw.check_zone(zone) + p_name_host = self.policy_name_from_zones(zone, "HOST") + p_name_fwd = self.policy_name_from_zones(zone, "ANY") + return self._fw.policy.query_icmp_block_inversion(p_name_host) and \ + self._fw.policy.query_icmp_block_inversion(p_name_fwd) diff --git a/src/firewall/core/io/policy.py b/src/firewall/core/io/policy.py new file mode 100644 index 0000000..c0171a6 --- /dev/null +++ b/src/firewall/core/io/policy.py @@ -0,0 +1,830 @@ +# -*- coding: utf-8 -*- +# +# SPDX-License-Identifier: GPL-2.0-or-later + +__all__ = [ "Policy", "policy_reader", "policy_writer" ] + +import xml.sax as sax +import os +import io +import shutil +import copy +from collections import OrderedDict + +from firewall import config +from firewall.functions import checkIP, checkIP6 +from firewall.functions import uniqify, max_policy_name_len, portStr +from firewall.core.base import DEFAULT_POLICY_TARGET, POLICY_TARGETS +from firewall.core.io.io_object import IO_Object, \ + IO_Object_ContentHandler, IO_Object_XMLGenerator, check_port, \ + check_tcpudp, check_protocol +from firewall.core import rich +from firewall.core.logger import log +from firewall import errors +from firewall.errors import FirewallError + +class Policy(IO_Object): + priority_min = -32768 + priority_max = 32767 + priority_default = -1 + ADDITIONAL_ALNUM_CHARS = [ "_", "-", "/" ] + PARSER_REQUIRED_ELEMENT_ATTRS = { + "short": None, + "description": None, + "policy": ["target"], + "service": [ "name" ], + "port": [ "port", "protocol" ], + "icmp-block": [ "name" ], + "icmp-type": [ "name" ], + "forward-port": [ "port", "protocol" ], + "rule": None, + "destination": [ "address" ], + "protocol": [ "value" ], + "source-port": [ "port", "protocol" ], + "log": None, + "audit": None, + "accept": None, + "reject": None, + "drop": None, + "mark": [ "set" ], + "limit": [ "value" ], + "ingress-zone": None, + "egress-zone": None, + } + PARSER_OPTIONAL_ELEMENT_ATTRS = { + "policy": [ "version", "priority" ], + "forward-port": [ "to-port", "to-addr" ], + "rule": [ "family", "priority" ], + "destination": [ "invert" ], + "log": [ "prefix", "level" ], + "reject": [ "type" ], + } + + def __init__(self): + super(Policy, self).__init__() + self.version = "" + self.short = "" + self.description = "" + self.target = DEFAULT_POLICY_TARGET + self.services = [ ] + self.ports = [ ] + self.protocols = [ ] + self.icmp_blocks = [ ] + self.masquerade = False + self.forward_ports = [ ] + self.source_ports = [ ] + self.fw_config = None # to be able to check services and a icmp_blocks + self.rules = [ ] + self.combined = False + self.applied = False + self.priority = self.priority_default + self.derived_from_zone = None + self.ingress_zones = [] + self.egress_zones = [] + + def cleanup(self): + self.version = "" + self.short = "" + self.description = "" + self.target = DEFAULT_POLICY_TARGET + del self.services[:] + del self.ports[:] + del self.protocols[:] + del self.icmp_blocks[:] + self.masquerade = False + del self.forward_ports[:] + del self.source_ports[:] + self.fw_config = None # to be able to check services and a icmp_blocks + del self.rules[:] + self.combined = False + self.applied = False + self.priority = self.priority_default + del self.ingress_zones[:] + del self.egress_zones[:] + + def __getattr__(self, name): + if name == "rules_str": + rules_str = [str(rule) for rule in self.rules] + return rules_str + else: + return getattr(super(Policy, self), name) + + def __setattr__(self, name, value): + if name == "rules_str": + self.rules = [rich.Rich_Rule(rule_str=s) for s in value] + else: + super(Policy, self).__setattr__(name, value) + + def _check_config(self, config, item): + if item == "services" and self.fw_config: + existing_services = self.fw_config.get_services() + for service in config: + if service not in existing_services: + raise FirewallError(errors.INVALID_SERVICE, + "'%s' not among existing services" % \ + service) + elif item == "ports": + for port in config: + check_port(port[0]) + check_tcpudp(port[1]) + elif item == "protocols": + for proto in config: + check_protocol(proto) + elif item == "icmp_blocks" and self.fw_config: + existing_icmptypes = self.fw_config.get_icmptypes() + for icmptype in config: + if icmptype not in existing_icmptypes: + raise FirewallError(errors.INVALID_ICMPTYPE, + "'%s' not among existing icmp types" % \ + icmptype) + elif item == "forward_ports": + for fwd_port in config: + check_port(fwd_port[0]) + check_tcpudp(fwd_port[1]) + if not fwd_port[2] and not fwd_port[3]: + raise FirewallError( + errors.INVALID_FORWARD, + "'%s' is missing to-port AND to-addr " % fwd_port) + if fwd_port[2]: + check_port(fwd_port[2]) + if fwd_port[3]: + if not checkIP(fwd_port[3]) and not checkIP6(fwd_port[3]): + raise FirewallError( + errors.INVALID_ADDR, + "to-addr '%s' is not a valid address" % fwd_port[3]) + elif item == "source_ports": + for port in config: + check_port(port[0]) + check_tcpudp(port[1]) + elif item == "target": + if config not in POLICY_TARGETS: + raise FirewallError(errors.INVALID_TARGET, config) + elif item == "rules_str": + for rule in config: + rich.Rich_Rule(rule_str=rule) + elif item in ["ingress-zone", "egress-zone"] and self.fw_config: + existing_zones = self.fw_config.get_zones() + for zone in config: + if zone not in existing_zones: + raise FirewallError(errors.INVALID_SERVICE, + "'%s' not among existing zones" % zone) + + def check_name(self, name): + super(Policy, self).check_name(name) + if name.startswith('/'): + raise FirewallError(errors.INVALID_NAME, + "'%s' can't start with '/'" % name) + elif name.endswith('/'): + raise FirewallError(errors.INVALID_NAME, + "'%s' can't end with '/'" % name) + elif name.count('/') > 1: + raise FirewallError(errors.INVALID_NAME, + "more than one '/' in '%s'" % name) + else: + if "/" in name: + checked_name = name[:name.find('/')] + else: + checked_name = name + if len(checked_name) > max_policy_name_len(): + raise FirewallError(errors.INVALID_NAME, + "Policy of '%s' has %d chars, max is %d %s" % ( + name, len(checked_name), + max_policy_name_len(), + self.combined)) + + def import_config(self, conf): + self.check_config(conf) + + # FIXME: + for key in conf: + if not hasattr(self, key): + raise FirewallError(errors.UNKNOWN_ERROR, "Internal error. '{}' is not a valid attribute".format(key)) + if isinstance(conf[key], list): + # maintain list order while removing duplicates + setattr(self, key, list(OrderedDict.fromkeys(copy.deepcopy(conf[key])))) + else: + setattr(self, key, copy.deepcopy(conf[key])) + + def export_config(self): + conf = {} + # FIXME + type_formats = dict([(x[0], x[1]) for x in self.IMPORT_EXPORT_STRUCTURE]) + for key in type_formats: + if getattr(self, key): + conf[key] = copy.deepcopy(getattr(self, key)) + return conf + + def check_config(self, conf): + # FIXME + type_formats = dict([(x[0], x[1]) for x in self.IMPORT_EXPORT_STRUCTURE]) + for key in conf: + if key not in [x for (x,y) in self.IMPORT_EXPORT_STRUCTURE]: + raise FirewallError(errors.INVALID_OPTION, "policy option '{}' is not valid".format(key)) + self._check_config_structure(conf[key], type_formats[key]) + self._check_config(conf[key], key) + +# PARSER + +class policy_ContentHandler(IO_Object_ContentHandler): + def __init__(self, item): + IO_Object_ContentHandler.__init__(self, item) + self._rule = None + self._rule_error = False + self._limit_ok = None + + def startElement(self, name, attrs): + IO_Object_ContentHandler.startElement(self, name, attrs) + if self._rule_error: + return + + self.item.parser_check_element_attrs(name, attrs) + + if name == "policy": + if "version" in attrs: + self.item.version = attrs["version"] + if "priority" in attrs: + self.item.priority = int(attrs["priority"]) + target = attrs["target"] + if target not in POLICY_TARGETS: + raise FirewallError(errors.INVALID_TARGET, target) + + elif name == "short": + pass + elif name == "description": + pass + elif name == "service": + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_Service(attrs["name"]) + return + if attrs["name"] not in self.item.services: + self.item.services.append(attrs["name"]) + else: + log.warning("Service '%s' already set, ignoring.", + attrs["name"]) + elif name == "ingress-zone": + if attrs["name"] not in self.item.ingress_zones: + self.item.ingress_zones.append(attrs["name"]) + else: + log.warning("Ingress zone '%s' already set, ignoring.", attrs["name"]) + elif name == "egress-zone": + if attrs["name"] not in self.item.egress_zones: + self.item.egress_zones.append(attrs["name"]) + else: + log.warning("Egress zone '%s' already set, ignoring.", attrs["name"]) + + elif name == "port": + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_Port(attrs["port"], + attrs["protocol"]) + return + check_port(attrs["port"]) + check_tcpudp(attrs["protocol"]) + entry = (portStr(attrs["port"], "-"), attrs["protocol"]) + if entry not in self.item.ports: + self.item.ports.append(entry) + else: + log.warning("Port '%s/%s' already set, ignoring.", + attrs["port"], attrs["protocol"]) + + elif name == "protocol": + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_Protocol(attrs["value"]) + else: + check_protocol(attrs["value"]) + if attrs["value"] not in self.item.protocols: + self.item.protocols.append(attrs["value"]) + else: + log.warning("Protocol '%s' already set, ignoring.", + attrs["value"]) + elif name == "icmp-block": + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_IcmpBlock(attrs["name"]) + return + if attrs["name"] not in self.item.icmp_blocks: + self.item.icmp_blocks.append(attrs["name"]) + else: + log.warning("icmp-block '%s' already set, ignoring.", + attrs["name"]) + + elif name == "icmp-type": + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_IcmpType(attrs["name"]) + return + else: + log.warning("Invalid rule: icmp-block '%s' outside of rule", + attrs["name"]) + + elif name == "masquerade": + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_Masquerade() + else: + if self.item.masquerade: + log.warning("Masquerade already set, ignoring.") + else: + self.item.masquerade = True + + elif name == "forward-port": + to_port = "" + if "to-port" in attrs: + to_port = attrs["to-port"] + to_addr = "" + if "to-addr" in attrs: + to_addr = attrs["to-addr"] + + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_ForwardPort(attrs["port"], + attrs["protocol"], + to_port, to_addr) + return + + check_port(attrs["port"]) + check_tcpudp(attrs["protocol"]) + if to_port: + check_port(to_port) + if to_addr: + if not checkIP(to_addr) and not checkIP6(to_addr): + raise FirewallError(errors.INVALID_ADDR, + "to-addr '%s' is not a valid address" \ + % to_addr) + entry = (portStr(attrs["port"], "-"), attrs["protocol"], + portStr(to_port, "-"), str(to_addr)) + if entry not in self.item.forward_ports: + self.item.forward_ports.append(entry) + else: + log.warning("Forward port %s/%s%s%s already set, ignoring.", + attrs["port"], attrs["protocol"], + " >%s" % to_port if to_port else "", + " @%s" % to_addr if to_addr else "") + + elif name == "source-port": + if self._rule: + if self._rule.element: + log.warning("Invalid rule: More than one element in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.element = rich.Rich_SourcePort(attrs["port"], + attrs["protocol"]) + return + check_port(attrs["port"]) + check_tcpudp(attrs["protocol"]) + entry = (portStr(attrs["port"], "-"), attrs["protocol"]) + if entry not in self.item.source_ports: + self.item.source_ports.append(entry) + else: + log.warning("Source port '%s/%s' already set, ignoring.", + attrs["port"], attrs["protocol"]) + + elif name == "destination": + if not self._rule: + log.warning('Invalid rule: Destination outside of rule') + self._rule_error = True + return + if self._rule.destination: + log.warning("Invalid rule: More than one destination in rule '%s', ignoring.", + str(self._rule)) + return + invert = False + if "invert" in attrs and \ + attrs["invert"].lower() in [ "yes", "true" ]: + invert = True + self._rule.destination = rich.Rich_Destination(attrs["address"], + invert) + + elif name in [ "accept", "reject", "drop", "mark" ]: + if not self._rule: + log.warning('Invalid rule: Action outside of rule') + self._rule_error = True + return + if self._rule.action: + log.warning('Invalid rule: More than one action') + self._rule_error = True + return + if name == "accept": + self._rule.action = rich.Rich_Accept() + elif name == "reject": + _type = None + if "type" in attrs: + _type = attrs["type"] + self._rule.action = rich.Rich_Reject(_type) + elif name == "drop": + self._rule.action = rich.Rich_Drop() + elif name == "mark": + _set = attrs["set"] + self._rule.action = rich.Rich_Mark(_set) + self._limit_ok = self._rule.action + + elif name == "log": + if not self._rule: + log.warning('Invalid rule: Log outside of rule') + return + if self._rule.log: + log.warning('Invalid rule: More than one log') + return + level = None + if "level" in attrs: + level = attrs["level"] + if level not in [ "emerg", "alert", "crit", "error", + "warning", "notice", "info", "debug" ]: + log.warning('Invalid rule: Invalid log level') + self._rule_error = True + return + prefix = attrs["prefix"] if "prefix" in attrs else None + self._rule.log = rich.Rich_Log(prefix, level) + self._limit_ok = self._rule.log + + elif name == "audit": + if not self._rule: + log.warning('Invalid rule: Audit outside of rule') + return + if self._rule.audit: + log.warning("Invalid rule: More than one audit in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + self._rule.audit = rich.Rich_Audit() + self._limit_ok = self._rule.audit + + elif name == "rule": + family = None + priority = 0 + if "family" in attrs: + family = attrs["family"] + if family not in [ "ipv4", "ipv6" ]: + log.warning('Invalid rule: Rule family "%s" invalid', + attrs["family"]) + self._rule_error = True + return + if "priority" in attrs: + priority = int(attrs["priority"]) + self._rule = rich.Rich_Rule(family=family, priority=priority) + + elif name == "limit": + if not self._limit_ok: + log.warning('Invalid rule: Limit outside of action, log and audit') + self._rule_error = True + return + if self._limit_ok.limit: + log.warning("Invalid rule: More than one limit in rule '%s', ignoring.", + str(self._rule)) + self._rule_error = True + return + value = attrs["value"] + self._limit_ok.limit = rich.Rich_Limit(value) + + else: + log.warning("Unknown XML element '%s'", name) + return + + def endElement(self, name): + IO_Object_ContentHandler.endElement(self, name) + + if name == "rule": + if not self._rule_error: + try: + self._rule.check() + except Exception as e: + log.warning("%s: %s", e, str(self._rule)) + else: + if str(self._rule) not in \ + [ str(x) for x in self.item.rules ]: + self.item.rules.append(self._rule) + else: + log.warning("Rule '%s' already set, ignoring.", + str(self._rule)) + self._rule = None + self._rule_error = False + elif name in [ "accept", "reject", "drop", "mark", "log", "audit" ]: + self._limit_ok = None + +def policy_reader(filename, path, no_check_name=False): + policy = Policy() + if not filename.endswith(".xml"): + raise FirewallError(errors.INVALID_NAME, + "'%s' is missing .xml suffix" % filename) + policy.name = filename[:-4] + if not no_check_name: + policy.check_name(policy.name) + policy.filename = filename + policy.path = path + policy.builtin = False if path.startswith(config.ETC_FIREWALLD) else True + policy.default = policy.builtin + handler = policy_ContentHandler(policy) + parser = sax.make_parser() + parser.setContentHandler(handler) + name = "%s/%s" % (path, filename) + with open(name, "rb") as f: + source = sax.InputSource(None) + source.setByteStream(f) + try: + parser.parse(source) + except sax.SAXParseException as msg: + raise FirewallError(errors.INVALID_ZONE, + "not a valid policy file: %s" % \ + msg.getException()) + del handler + del parser + return policy + +def policy_writer(policy, path=None): + _path = path if path else policy.path + + if policy.filename: + name = "%s/%s" % (_path, policy.filename) + else: + name = "%s/%s.xml" % (_path, policy.name) + + if os.path.exists(name): + try: + shutil.copy2(name, "%s.old" % name) + except Exception as msg: + log.error("Backup of file '%s' failed: %s", name, msg) + + dirpath = os.path.dirname(name) + if dirpath.startswith(config.ETC_FIREWALLD) and not os.path.exists(dirpath): + if not os.path.exists(config.ETC_FIREWALLD): + os.mkdir(config.ETC_FIREWALLD, 0o750) + os.mkdir(dirpath, 0o750) + + f = io.open(name, mode='wt', encoding='UTF-8') + handler = IO_Object_XMLGenerator(f) + handler.startDocument() + + # start policy element + attrs = {} + if policy.version and policy.version != "": + attrs["version"] = policy.version + if policy.priority != policy.priority_default: + attrs["priority"] = str(policy.priority) + attrs["target"] = policy.target + handler.startElement("policy", attrs) + handler.ignorableWhitespace("\n") + + # short + if policy.short and policy.short != "": + handler.ignorableWhitespace(" ") + handler.startElement("short", { }) + handler.characters(policy.short) + handler.endElement("short") + handler.ignorableWhitespace("\n") + + # description + if policy.description and policy.description != "": + handler.ignorableWhitespace(" ") + handler.startElement("description", { }) + handler.characters(policy.description) + handler.endElement("description") + handler.ignorableWhitespace("\n") + + # services + for service in uniqify(policy.services): + handler.ignorableWhitespace(" ") + handler.simpleElement("service", { "name": service }) + handler.ignorableWhitespace("\n") + + # ingress-zones + for zone in uniqify(policy.ingress_zones): + handler.ignorableWhitespace(" ") + handler.simpleElement("ingress-zone", { "name": zone }) + handler.ignorableWhitespace("\n") + + # egress-zones + for zone in uniqify(policy.ingress_zones): + handler.ignorableWhitespace(" ") + handler.simpleElement("egress-zone", { "name": zone }) + handler.ignorableWhitespace("\n") + + # ports + for port in uniqify(policy.ports): + handler.ignorableWhitespace(" ") + handler.simpleElement("port", { "port": port[0], "protocol": port[1] }) + handler.ignorableWhitespace("\n") + + # protocols + for protocol in uniqify(policy.protocols): + handler.ignorableWhitespace(" ") + handler.simpleElement("protocol", { "value": protocol }) + handler.ignorableWhitespace("\n") + + # icmp-blocks + for icmp in uniqify(policy.icmp_blocks): + handler.ignorableWhitespace(" ") + handler.simpleElement("icmp-block", { "name": icmp }) + handler.ignorableWhitespace("\n") + + # masquerade + if policy.masquerade: + handler.ignorableWhitespace(" ") + handler.simpleElement("masquerade", { }) + handler.ignorableWhitespace("\n") + + # forward-ports + for forward in uniqify(policy.forward_ports): + handler.ignorableWhitespace(" ") + attrs = { "port": forward[0], "protocol": forward[1] } + if forward[2] and forward[2] != "" : + attrs["to-port"] = forward[2] + if forward[3] and forward[3] != "" : + attrs["to-addr"] = forward[3] + handler.simpleElement("forward-port", attrs) + handler.ignorableWhitespace("\n") + + # source-ports + for port in uniqify(policy.source_ports): + handler.ignorableWhitespace(" ") + handler.simpleElement("source-port", { "port": port[0], + "protocol": port[1] }) + handler.ignorableWhitespace("\n") + + # rules + for rule in policy.rules: + attrs = { } + if rule.family: + attrs["family"] = rule.family + if rule.priority != 0: + attrs["priority"] = str(rule.priority) + handler.ignorableWhitespace(" ") + handler.startElement("rule", attrs) + handler.ignorableWhitespace("\n") + + # source + if rule.source: + attrs = { } + if rule.source.addr: + attrs["address"] = rule.source.addr + if rule.source.mac: + attrs["mac"] = rule.source.mac + if rule.source.ipset: + attrs["ipset"] = rule.source.ipset + if rule.source.invert: + attrs["invert"] = "True" + handler.ignorableWhitespace(" ") + handler.simpleElement("source", attrs) + handler.ignorableWhitespace("\n") + + # destination + if rule.destination: + attrs = { "address": rule.destination.addr } + if rule.destination.invert: + attrs["invert"] = "True" + handler.ignorableWhitespace(" ") + handler.simpleElement("destination", attrs) + handler.ignorableWhitespace("\n") + + # element + if rule.element: + element = "" + attrs = { } + + if type(rule.element) == rich.Rich_Service: + element = "service" + attrs["name"] = rule.element.name + elif type(rule.element) == rich.Rich_Port: + element = "port" + attrs["port"] = rule.element.port + attrs["protocol"] = rule.element.protocol + elif type(rule.element) == rich.Rich_Protocol: + element = "protocol" + attrs["value"] = rule.element.value + elif type(rule.element) == rich.Rich_Masquerade: + element = "masquerade" + elif type(rule.element) == rich.Rich_IcmpBlock: + element = "icmp-block" + attrs["name"] = rule.element.name + elif type(rule.element) == rich.Rich_IcmpType: + element = "icmp-type" + attrs["name"] = rule.element.name + elif type(rule.element) == rich.Rich_ForwardPort: + element = "forward-port" + attrs["port"] = rule.element.port + attrs["protocol"] = rule.element.protocol + if rule.element.to_port != "": + attrs["to-port"] = rule.element.to_port + if rule.element.to_address != "": + attrs["to-addr"] = rule.element.to_address + elif type(rule.element) == rich.Rich_SourcePort: + element = "source-port" + attrs["port"] = rule.element.port + attrs["protocol"] = rule.element.protocol + else: + raise FirewallError( + errors.INVALID_OBJECT, + "Unknown element '%s' in policy_writer" % type(rule.element)) + + handler.ignorableWhitespace(" ") + handler.simpleElement(element, attrs) + handler.ignorableWhitespace("\n") + + # rule.element + + # log + if rule.log: + attrs = { } + if rule.log.prefix: + attrs["prefix"] = rule.log.prefix + if rule.log.level: + attrs["level"] = rule.log.level + if rule.log.limit: + handler.ignorableWhitespace(" ") + handler.startElement("log", attrs) + handler.ignorableWhitespace("\n ") + handler.simpleElement("limit", + { "value": rule.log.limit.value }) + handler.ignorableWhitespace("\n ") + handler.endElement("log") + else: + handler.ignorableWhitespace(" ") + handler.simpleElement("log", attrs) + handler.ignorableWhitespace("\n") + + # audit + if rule.audit: + attrs = {} + if rule.audit.limit: + handler.ignorableWhitespace(" ") + handler.startElement("audit", { }) + handler.ignorableWhitespace("\n ") + handler.simpleElement("limit", + { "value": rule.audit.limit.value }) + handler.ignorableWhitespace("\n ") + handler.endElement("audit") + else: + handler.ignorableWhitespace(" ") + handler.simpleElement("audit", attrs) + handler.ignorableWhitespace("\n") + + # action + if rule.action: + action = "" + attrs = { } + if type(rule.action) == rich.Rich_Accept: + action = "accept" + elif type(rule.action) == rich.Rich_Reject: + action = "reject" + if rule.action.type: + attrs["type"] = rule.action.type + elif type(rule.action) == rich.Rich_Drop: + action = "drop" + elif type(rule.action) == rich.Rich_Mark: + action = "mark" + attrs["set"] = rule.action.set + else: + log.warning("Unknown action '%s'", type(rule.action)) + if rule.action.limit: + handler.ignorableWhitespace(" ") + handler.startElement(action, attrs) + handler.ignorableWhitespace("\n ") + handler.simpleElement("limit", + { "value": rule.action.limit.value }) + handler.ignorableWhitespace("\n ") + handler.endElement(action) + else: + handler.ignorableWhitespace(" ") + handler.simpleElement(action, attrs) + handler.ignorableWhitespace("\n") + + handler.ignorableWhitespace(" ") + handler.endElement("rule") + handler.ignorableWhitespace("\n") + + # end policy element + handler.endElement("policy") + handler.ignorableWhitespace("\n") + handler.endDocument() + f.close() + del handler diff --git a/src/firewall/core/ipXtables.py b/src/firewall/core/ipXtables.py index b1d6c20..3d05d7b 100644 --- a/src/firewall/core/ipXtables.py +++ b/src/firewall/core/ipXtables.py @@ -22,7 +22,6 @@ import os.path import copy -from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET from firewall.core.prog import runProg from firewall.core.logger import log from firewall.functions import tempFile, readfile, splitArgs, check_mac, portStr, \ @@ -33,6 +32,8 @@ from firewall.core.rich import Rich_Accept, Rich_Reject, Rich_Drop, Rich_Mark, \ Rich_Masquerade, Rich_ForwardPort, Rich_IcmpBlock import string +POLICY_CHAIN_PREFIX = "pol_" + BUILT_IN_CHAINS = { "security": [ "INPUT", "OUTPUT", "FORWARD" ], "raw": [ "PREROUTING", "OUTPUT" ], @@ -167,7 +168,7 @@ def common_check_passthrough(args): class ip4tables(object): ipv = "ipv4" name = "ip4tables" - zones_supported = True + policies_supported = True def __init__(self, fw): self._fw = fw @@ -769,10 +770,9 @@ class ip4tables(object): return {} - def build_zone_source_interface_rules(self, enable, zone, interface, + def build_zone_source_interface_rules(self, enable, zone, policy, interface, table, chain, append=False): - # handle all zones in the same way here, now - # trust and block zone targets are handled now in __chain + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) opt = { "PREROUTING": "-i", "POSTROUTING": "-o", @@ -782,7 +782,6 @@ class ip4tables(object): "OUTPUT": "-o", }[chain] - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) action = "-g" if enable and not append: @@ -793,13 +792,14 @@ class ip4tables(object): rule = [ "-D", "%s_ZONES" % chain ] if not append: rule += ["%%ZONE_INTERFACE%%"] - rule += [ "-t", table, opt, interface, action, target ] + rule += [ "-t", table, opt, interface, action, _policy ] return [rule] - def build_zone_source_address_rules(self, enable, zone, + def build_zone_source_address_rules(self, enable, zone, policy, address, table, chain): add_del = { True: "-I", False: "-D" }[enable] + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) opt = { "PREROUTING": "-s", "POSTROUTING": "-d", @@ -814,7 +814,6 @@ class ip4tables(object): else: zone_dispatch_chain = "%s_ZONES" % (chain) - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) action = "-g" if address.startswith("ipset:"): @@ -828,7 +827,7 @@ class ip4tables(object): "%%ZONE_SOURCE%%", zone, "-t", table, "-m", "set", "--match-set", name, - flags, action, target ] + flags, action, _policy ] else: if check_mac(address): # outgoing can not be set @@ -838,7 +837,7 @@ class ip4tables(object): "%%ZONE_SOURCE%%", zone, "-t", table, "-m", "mac", "--mac-source", address.upper(), - action, target ] + action, _policy ] else: if check_single_address("ipv6", address): address = normalizeIP6(address) @@ -848,56 +847,48 @@ class ip4tables(object): rule = [ add_del, zone_dispatch_chain, "%%ZONE_SOURCE%%", zone, "-t", table, - opt, address, action, target ] + opt, address, action, _policy ] return [rule] - def build_zone_chain_rules(self, zone, table, chain): - _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) + def build_policy_chain_rules(self, policy, table): + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) - self.our_chains[table].update(set([_zone, - "%s_log" % _zone, - "%s_deny" % _zone, - "%s_pre" % _zone, - "%s_post" % _zone, - "%s_allow" % _zone])) + self.our_chains[table].update(set([_policy, + "%s_log" % _policy, + "%s_deny" % _policy, + "%s_pre" % _policy, + "%s_post" % _policy, + "%s_allow" % _policy])) rules = [] - rules.append([ "-N", _zone, "-t", table ]) - rules.append([ "-N", "%s_pre" % _zone, "-t", table ]) - rules.append([ "-N", "%s_log" % _zone, "-t", table ]) - rules.append([ "-N", "%s_deny" % _zone, "-t", table ]) - rules.append([ "-N", "%s_allow" % _zone, "-t", table ]) - rules.append([ "-N", "%s_post" % _zone, "-t", table ]) - rules.append([ "-A", _zone, "-t", table, "-j", "%s_pre" % _zone ]) - rules.append([ "-A", _zone, "-t", table, "-j", "%s_log" % _zone ]) - rules.append([ "-A", _zone, "-t", table, "-j", "%s_deny" % _zone ]) - rules.append([ "-A", _zone, "-t", table, "-j", "%s_allow" % _zone ]) - rules.append([ "-A", _zone, "-t", table, "-j", "%s_post" % _zone ]) - - target = self._fw.zone._zones[zone].target + rules.append([ "-N", _policy, "-t", table ]) + rules.append([ "-N", "%s_pre" % _policy, "-t", table ]) + rules.append([ "-N", "%s_log" % _policy, "-t", table ]) + rules.append([ "-N", "%s_deny" % _policy, "-t", table ]) + rules.append([ "-N", "%s_allow" % _policy, "-t", table ]) + rules.append([ "-N", "%s_post" % _policy, "-t", table ]) + rules.append([ "-A", _policy, "-t", table, "-j", "%s_pre" % _policy ]) + rules.append([ "-A", _policy, "-t", table, "-j", "%s_log" % _policy ]) + rules.append([ "-A", _policy, "-t", table, "-j", "%s_deny" % _policy ]) + rules.append([ "-A", _policy, "-t", table, "-j", "%s_allow" % _policy ]) + rules.append([ "-A", _policy, "-t", table, "-j", "%s_post" % _policy ]) + + target = self._fw.policy._policies[policy].target if self._fw.get_log_denied() != "off": - if table == "filter" and \ - chain in [ "INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT" ]: + if table == "filter": if target in [ "REJECT", "%%REJECT%%" ]: - rules.append([ "-A", _zone, "-t", table, "%%LOGTYPE%%", + rules.append([ "-A", _policy, "-t", table, "%%LOGTYPE%%", "-j", "LOG", "--log-prefix", - "\"%s_REJECT: \"" % _zone ]) + "\"%s_REJECT: \"" % _policy ]) if target == "DROP": - rules.append([ "-A", _zone, "-t", table, "%%LOGTYPE%%", + rules.append([ "-A", _policy, "-t", table, "%%LOGTYPE%%", "-j", "LOG", "--log-prefix", - "\"%s_DROP: \"" % _zone ]) - - # Handle trust, block and drop zones: - # Add an additional rule with the zone target (accept, reject - # or drop) to the base zone only in the filter table. - # Otherwise it is not be possible to have a zone with drop - # target, that is allowing traffic that is locally initiated - # or that adds additional rules. (RHBZ#1055190) + "\"%s_DROP: \"" % _policy ]) + if table == "filter" and \ - target in [ "ACCEPT", "REJECT", "%%REJECT%%", "DROP" ] and \ - chain in [ "INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT" ]: - rules.append([ "-A", _zone, "-t", table, "-j", target ]) + target in [ "ACCEPT", "REJECT", "%%REJECT%%", "DROP" ]: + rules.append([ "-A", _policy, "-t", table, "-j", target ]) return rules @@ -944,14 +935,16 @@ class ip4tables(object): return [] return ["%%RICH_RULE_PRIORITY%%", rich_rule.priority] - def _rich_rule_log(self, rich_rule, enable, table, target, rule_fragment): + def _rich_rule_log(self, policy, rich_rule, enable, table, rule_fragment): if not rich_rule.log: return [] + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "-A", False: "-D" }[enable] chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) - rule = ["-t", table, add_del, "%s_%s" % (target, chain_suffix)] + rule = ["-t", table, add_del, "%s_%s" % (_policy, chain_suffix)] rule += self._rich_rule_priority_fragment(rich_rule) rule += rule_fragment + [ "-j", "LOG" ] if rich_rule.log.prefix: @@ -962,14 +955,16 @@ class ip4tables(object): return rule - def _rich_rule_audit(self, rich_rule, enable, table, target, rule_fragment): + def _rich_rule_audit(self, policy, rich_rule, enable, table, rule_fragment): if not rich_rule.audit: return [] add_del = { True: "-A", False: "-D" }[enable] + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) - rule = ["-t", table, add_del, "%s_%s" % (target, chain_suffix)] + rule = ["-t", table, add_del, "%s_%s" % (_policy, chain_suffix)] rule += self._rich_rule_priority_fragment(rich_rule) rule += rule_fragment if type(rich_rule.action) == Rich_Accept: @@ -985,14 +980,16 @@ class ip4tables(object): return rule - def _rich_rule_action(self, zone, rich_rule, enable, table, target, rule_fragment): + def _rich_rule_action(self, policy, rich_rule, enable, table, rule_fragment): if not rich_rule.action: return [] add_del = { True: "-A", False: "-D" }[enable] + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + chain_suffix = self._rich_rule_chain_suffix(rich_rule) - chain = "%s_%s" % (target, chain_suffix) + chain = "%s_%s" % (_policy, chain_suffix) if type(rich_rule.action) == Rich_Accept: rule_action = [ "-j", "ACCEPT" ] elif type(rich_rule.action) == Rich_Reject: @@ -1002,10 +999,9 @@ class ip4tables(object): elif type(rich_rule.action) == Rich_Drop: rule_action = [ "-j", "DROP" ] elif type(rich_rule.action) == Rich_Mark: - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], - zone=zone) table = "mangle" - chain = "%s_%s" % (target, chain_suffix) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + chain = "%s_%s" % (_policy, chain_suffix) rule_action = [ "-j", "MARK", "--set-xmark", rich_rule.action.set ] else: raise FirewallError(INVALID_RULE, @@ -1064,11 +1060,10 @@ class ip4tables(object): return rule_fragment - def build_zone_ports_rules(self, enable, zone, proto, port, destination=None, rich_rule=None): + def build_policy_ports_rules(self, enable, policy, proto, port, destination=None, rich_rule=None): add_del = { True: "-A", False: "-D" }[enable] table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], - zone=zone) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) rule_fragment = [ "-p", proto ] if port: @@ -1083,19 +1078,19 @@ class ip4tables(object): rules = [] if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) else: - rules.append([add_del, "%s_allow" % (target), "-t", table] + + rules.append([add_del, "%s_allow" % (_policy), "-t", table] + rule_fragment + [ "-j", "ACCEPT" ]) return rules - def build_zone_protocol_rules(self, enable, zone, protocol, destination=None, rich_rule=None): + def build_policy_protocol_rules(self, enable, policy, protocol, destination=None, rich_rule=None): add_del = { True: "-A", False: "-D" }[enable] table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) rule_fragment = [ "-p", protocol ] if destination: @@ -1108,20 +1103,20 @@ class ip4tables(object): rules = [] if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) else: - rules.append([add_del, "%s_allow" % (target), "-t", table] + + rules.append([add_del, "%s_allow" % (_policy), "-t", table] + rule_fragment + [ "-j", "ACCEPT" ]) return rules - def build_zone_source_ports_rules(self, enable, zone, proto, port, + def build_policy_source_ports_rules(self, enable, policy, proto, port, destination=None, rich_rule=None): add_del = { True: "-A", False: "-D" }[enable] table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) rule_fragment = [ "-p", proto ] if port: @@ -1136,21 +1131,22 @@ class ip4tables(object): rules = [] if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) else: - rules.append([add_del, "%s_allow" % (target), "-t", table] + + rules.append([add_del, "%s_allow" % (_policy), "-t", table] + rule_fragment + [ "-j", "ACCEPT" ]) return rules - def build_zone_helper_ports_rules(self, enable, zone, proto, port, + def build_policy_helper_ports_rules(self, enable, policy, proto, port, destination, helper_name, module_short_name): + table = "raw" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "-A", False: "-D" }[enable] - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], - zone=zone) - rule = [ add_del, "%s_allow" % (target), "-t", "raw", "-p", proto ] + + rule = [ add_del, "%s_allow" % (_policy), "-t", "raw", "-p", proto ] if port: rule += [ "--dport", "%s" % portStr(port) ] if destination: @@ -1159,10 +1155,11 @@ class ip4tables(object): return [rule] - def build_zone_masquerade_rules(self, enable, zone, rich_rule=None): + def build_policy_masquerade_rules(self, enable, policy, rich_rule=None): + table = "nat" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "-A", False: "-D" }[enable] - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["POSTROUTING"], - zone=zone) + rule_fragment = [] if rich_rule: chain_suffix = self._rich_rule_chain_suffix(rich_rule) @@ -1173,12 +1170,10 @@ class ip4tables(object): chain_suffix = "allow" rules = [] - rules.append(["-t", "nat", add_del, "%s_%s" % (target, chain_suffix)] + rules.append(["-t", "nat", add_del, "%s_%s" % (_policy, chain_suffix)] + rule_fragment + [ "!", "-o", "lo", "-j", "MASQUERADE" ]) - # FORWARD_OUT - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["FORWARD_OUT"], - zone=zone) + rule_fragment = [] if rich_rule: chain_suffix = self._rich_rule_chain_suffix(rich_rule) @@ -1188,14 +1183,18 @@ class ip4tables(object): else: chain_suffix = "allow" - rules.append(["-t", "filter", add_del, "%s_%s" % (target, chain_suffix)] + table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + rules.append(["-t", "filter", add_del, "%s_%s" % (_policy, chain_suffix)] + rule_fragment + ["-m", "conntrack", "--ctstate", "NEW,UNTRACKED", "-j", "ACCEPT" ]) return rules - def build_zone_forward_port_rules(self, enable, zone, port, + def build_policy_forward_port_rules(self, enable, policy, port, protocol, toport, toaddr, rich_rule=None): + table = "nat" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "-A", False: "-D" }[enable] to = "" @@ -1207,9 +1206,6 @@ class ip4tables(object): if toport and toport != "": to += ":%s" % portStr(toport, "-") - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], - zone=zone) - rule_fragment = [] if rich_rule: chain_suffix = self._rich_rule_chain_suffix(rich_rule) @@ -1221,16 +1217,17 @@ class ip4tables(object): rules = [] if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, "nat", target, rule_fragment)) - rules.append(["-t", "nat", add_del, "%s_%s" % (target, chain_suffix)] + rules.append(self._rich_rule_log(policy, rich_rule, enable, "nat", rule_fragment)) + rules.append(["-t", "nat", add_del, "%s_%s" % (_policy, chain_suffix)] + rule_fragment + ["-p", protocol, "--dport", portStr(port), "-j", "DNAT", "--to-destination", to]) return rules - def build_zone_icmp_block_rules(self, enable, zone, ict, rich_rule=None): + def build_policy_icmp_block_rules(self, enable, policy, ict, rich_rule=None): table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "-A", False: "-D" }[enable] if self.ipv == "ipv4": @@ -1241,93 +1238,87 @@ class ip4tables(object): match = [ "-m", "icmp6", "--icmpv6-type", ict.name ] rules = [] - for chain in ["INPUT", "FORWARD_IN"]: - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], - zone=zone) - if self._fw.zone.query_icmp_block_inversion(zone): - final_chain = "%s_allow" % target - final_target = "ACCEPT" - else: - final_chain = "%s_deny" % target - final_target = "%%REJECT%%" - - rule_fragment = [] - if rich_rule: - rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination) - rule_fragment += self._rich_rule_source_fragment(rich_rule.source) - rule_fragment += proto + match - - if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) - if rich_rule.action: - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) - else: - chain_suffix = self._rich_rule_chain_suffix(rich_rule) - rules.append(["-t", table, add_del, "%s_%s" % (target, chain_suffix)] - + self._rich_rule_priority_fragment(rich_rule) - + rule_fragment + - [ "-j", "%%REJECT%%" ]) + if self._fw.policy.query_icmp_block_inversion(policy): + final_chain = "%s_allow" % (_policy) + final_target = "ACCEPT" + else: + final_chain = "%s_deny" % (_policy) + final_target = "%%REJECT%%" + + rule_fragment = [] + if rich_rule: + rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination) + rule_fragment += self._rich_rule_source_fragment(rich_rule.source) + rule_fragment += proto + match + + if rich_rule: + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) + if rich_rule.action: + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) else: - if self._fw.get_log_denied() != "off" and final_target != "ACCEPT": - rules.append([ add_del, final_chain, "-t", table ] - + rule_fragment + - [ "%%LOGTYPE%%", "-j", "LOG", - "--log-prefix", "\"%s_ICMP_BLOCK: \"" % zone ]) + chain_suffix = self._rich_rule_chain_suffix(rich_rule) + rules.append(["-t", table, add_del, "%s_%s" % (_policy, chain_suffix)] + + self._rich_rule_priority_fragment(rich_rule) + + rule_fragment + + [ "-j", "%%REJECT%%" ]) + else: + if self._fw.get_log_denied() != "off" and final_target != "ACCEPT": rules.append([ add_del, final_chain, "-t", table ] + rule_fragment + - [ "-j", final_target ]) + [ "%%LOGTYPE%%", "-j", "LOG", + "--log-prefix", "\"%s_ICMP_BLOCK: \"" % policy ]) + rules.append([ add_del, final_chain, "-t", table ] + + rule_fragment + + [ "-j", final_target ]) return rules - def build_zone_icmp_block_inversion_rules(self, enable, zone): + def build_policy_icmp_block_inversion_rules(self, enable, policy): table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + rules = [] - for chain in [ "INPUT", "FORWARD_IN" ]: - rule_idx = 6 - _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], - zone=zone) + rule_idx = 6 - if self._fw.zone.query_icmp_block_inversion(zone): - ibi_target = "%%REJECT%%" + if self._fw.policy.query_icmp_block_inversion(policy): + ibi_target = "%%REJECT%%" - if self._fw.get_log_denied() != "off": - if enable: - rule = [ "-I", _zone, str(rule_idx) ] - else: - rule = [ "-D", _zone ] - - rule = rule + [ "-t", table, "-p", "%%ICMP%%", - "%%LOGTYPE%%", - "-j", "LOG", "--log-prefix", - "\"%s_ICMP_BLOCK: \"" % _zone ] - rules.append(rule) - rule_idx += 1 - else: - ibi_target = "ACCEPT" + if self._fw.get_log_denied() != "off": + if enable: + rule = [ "-I", _policy, str(rule_idx) ] + else: + rule = [ "-D", _policy ] + + rule = rule + [ "-t", table, "-p", "%%ICMP%%", + "%%LOGTYPE%%", + "-j", "LOG", "--log-prefix", + "\"%s_ICMP_BLOCK: \"" % _policy ] + rules.append(rule) + rule_idx += 1 + else: + ibi_target = "ACCEPT" - if enable: - rule = [ "-I", _zone, str(rule_idx) ] - else: - rule = [ "-D", _zone ] - rule = rule + [ "-t", table, "-p", "%%ICMP%%", "-j", ibi_target ] - rules.append(rule) + if enable: + rule = [ "-I", _policy, str(rule_idx) ] + else: + rule = [ "-D", _policy ] + rule = rule + [ "-t", table, "-p", "%%ICMP%%", "-j", ibi_target ] + rules.append(rule) return rules - def build_zone_rich_source_destination_rules(self, enable, zone, rich_rule): + def build_policy_rich_source_destination_rules(self, enable, policy, rich_rule): table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], - zone=zone) rule_fragment = [] rule_fragment += self._rich_rule_destination_fragment(rich_rule.destination) rule_fragment += self._rich_rule_source_fragment(rich_rule.source) rules = [] - rules.append(self._rich_rule_log(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, rule_fragment)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, rule_fragment)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, rule_fragment)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, rule_fragment)) return rules diff --git a/src/firewall/core/nftables.py b/src/firewall/core/nftables.py index a6ad5f7..f422556 100644 --- a/src/firewall/core/nftables.py +++ b/src/firewall/core/nftables.py @@ -23,7 +23,6 @@ from __future__ import absolute_import import copy import json -from firewall.core.base import SHORTCUTS, DEFAULT_ZONE_TARGET from firewall.core.logger import log from firewall.functions import check_mac, getPortRange, normalizeIP6, \ check_single_address, check_address @@ -36,6 +35,7 @@ from nftables.nftables import Nftables TABLE_NAME = "firewalld" TABLE_NAME_POLICY = TABLE_NAME + "_" + "policy_drop" +POLICY_CHAIN_PREFIX = "policy_" # Map iptables (table, chain) to hooks and priorities. # These are well defined by NF_IP_PRI_* defines in netfilter. @@ -158,7 +158,7 @@ ICMP_TYPES_FRAGMENTS = { class nftables(object): name = "nftables" - zones_supported = True + policies_supported = True def __init__(self, fw): self._fw = fw @@ -174,7 +174,6 @@ class nftables(object): self.nftables.set_echo_output(True) self.nftables.set_handle_output(True) - def _run_replace_zone_source(self, rule, zone_source_index_cache): for verb in ["add", "insert", "delete"]: if verb in rule: @@ -698,18 +697,19 @@ class nftables(object): return [] - def build_zone_source_interface_rules(self, enable, zone, interface, + def build_zone_source_interface_rules(self, enable, zone, policy, interface, table, chain, append=False, family="inet"): # nat tables needs to use ip/ip6 family if table == "nat" and family == "inet": rules = [] - rules.extend(self.build_zone_source_interface_rules(enable, zone, + rules.extend(self.build_zone_source_interface_rules(enable, zone, policy, interface, table, chain, append, "ip")) - rules.extend(self.build_zone_source_interface_rules(enable, zone, + rules.extend(self.build_zone_source_interface_rules(enable, zone, policy, interface, table, chain, append, "ip6")) return rules + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) opt = { "PREROUTING": "iifname", "POSTROUTING": "oifname", @@ -722,16 +722,15 @@ class nftables(object): if interface[len(interface)-1] == "+": interface = interface[:len(interface)-1] + "*" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) action = "goto" if interface == "*": - expr_fragments = [{action: {"target": "%s_%s" % (table, target)}}] + expr_fragments = [{action: {"target": "%s_%s" % (table, _policy)}}] else: expr_fragments = [{"match": {"left": {"meta": {"key": opt}}, "op": "==", "right": interface}}, - {action: {"target": "%s_%s" % (table, target)}}] + {action: {"target": "%s_%s" % (table, _policy)}}] if enable and not append: verb = "insert" @@ -757,7 +756,7 @@ class nftables(object): return [{verb: {"rule": rule}}] - def build_zone_source_address_rules(self, enable, zone, + def build_zone_source_address_rules(self, enable, zone, policy, address, table, chain, family="inet"): # nat tables needs to use ip/ip6 family if table == "nat" and family == "inet": @@ -768,13 +767,14 @@ class nftables(object): ipset_family = None if check_address("ipv4", address) or check_mac(address) or ipset_family == "ip": - rules.extend(self.build_zone_source_address_rules(enable, zone, + rules.extend(self.build_zone_source_address_rules(enable, zone, policy, address, table, chain, "ip")) if check_address("ipv6", address) or check_mac(address) or ipset_family == "ip6": - rules.extend(self.build_zone_source_address_rules(enable, zone, + rules.extend(self.build_zone_source_address_rules(enable, zone, policy, address, table, chain, "ip6")) return rules + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "insert", False: "delete" }[enable] opt = { @@ -791,73 +791,64 @@ class nftables(object): else: zone_dispatch_chain = "%s_%s_ZONES" % (table, chain) - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) action = "goto" rule = {"family": family, "table": TABLE_NAME, "chain": zone_dispatch_chain, "expr": [self._rule_addr_fragment(opt, address), - {action: {"target": "%s_%s" % (table, target)}}]} + {action: {"target": "%s_%s" % (table, _policy)}}]} rule.update(self._zone_source_fragment(zone, address)) return [{add_del: {"rule": rule}}] - def build_zone_chain_rules(self, zone, table, chain, family="inet"): + def build_policy_chain_rules(self, policy, table, family="inet"): # nat tables needs to use ip/ip6 family if table == "nat" and family == "inet": rules = [] - rules.extend(self.build_zone_chain_rules(zone, table, chain, "ip")) - rules.extend(self.build_zone_chain_rules(zone, table, chain, "ip6")) + rules.extend(self.build_policy_chain_rules(policy, table, "ip")) + rules.extend(self.build_policy_chain_rules(policy, table, "ip6")) return rules - _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], zone=zone) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) rules = [] rules.append({"add": {"chain": {"family": family, "table": TABLE_NAME, - "name": "%s_%s" % (table, _zone)}}}) + "name": "%s_%s" % (table, _policy)}}}) for chain_suffix in ["pre", "log", "deny", "allow", "post"]: rules.append({"add": {"chain": {"family": family, "table": TABLE_NAME, - "name": "%s_%s_%s" % (table, _zone, chain_suffix)}}}) + "name": "%s_%s_%s" % (table, _policy, chain_suffix)}}}) for chain_suffix in ["pre", "log", "deny", "allow", "post"]: rules.append({"add": {"rule": {"family": family, "table": TABLE_NAME, - "chain": "%s_%s" % (table, _zone), - "expr": [{"jump": {"target": "%s_%s_%s" % (table, _zone, chain_suffix)}}]}}}) + "chain": "%s_%s" % (table, _policy), + "expr": [{"jump": {"target": "%s_%s_%s" % (table, _policy, chain_suffix)}}]}}}) - target = self._fw.zone._zones[zone].target + target = self._fw.policy._policies[policy].target if self._fw.get_log_denied() != "off": - if table == "filter" and \ - chain in ["INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT"]: + if table == "filter": if target in ["REJECT", "%%REJECT%%", "DROP"]: log_suffix = target if target == "%%REJECT%%": log_suffix = "REJECT" rules.append({"add": {"rule": {"family": family, "table": TABLE_NAME, - "chain": "%s_%s" % (table, _zone), + "chain": "%s_%s" % (table, _policy), "expr": [self._pkttype_match_fragment(self._fw.get_log_denied()), - {"log": {"prefix": "\"filter_%s_%s: \"" % (_zone, log_suffix)}}]}}}) - - # Handle trust, block and drop zones: - # Add an additional rule with the zone target (accept, reject - # or drop) to the base zone only in the filter table. - # Otherwise it is not be possible to have a zone with drop - # target, that is allowing traffic that is locally initiated - # or that adds additional rules. (RHBZ#1055190) + {"log": {"prefix": "\"filter_%s_%s: \"" % (_policy, log_suffix)}}]}}}) + if table == "filter" and \ - target in ["ACCEPT", "REJECT", "%%REJECT%%", "DROP"] and \ - chain in ["INPUT", "FORWARD_IN", "FORWARD_OUT", "OUTPUT"]: + target in ["ACCEPT", "REJECT", "%%REJECT%%", "DROP"]: if target == "%%REJECT%%": target_fragment = self._reject_fragment() else: target_fragment = {target.lower(): None} rules.append({"add": {"rule": {"family": family, "table": TABLE_NAME, - "chain": "%s_%s" % (table, _zone), + "chain": "%s_%s" % (table, _policy), "expr": [target_fragment]}}}) return rules @@ -981,10 +972,12 @@ class nftables(object): return {} return {"%%RICH_RULE_PRIORITY%%": rich_rule.priority} - def _rich_rule_log(self, rich_rule, enable, table, target, expr_fragments): + def _rich_rule_log(self, policy, rich_rule, enable, table, expr_fragments): if not rich_rule.log: return {} + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) @@ -997,37 +990,41 @@ class nftables(object): rule = {"family": "inet", "table": TABLE_NAME, - "chain": "%s_%s_%s" % (table, target, chain_suffix), + "chain": "%s_%s_%s" % (table, _policy, chain_suffix), "expr": expr_fragments + [{"log": log_options}, self._rich_rule_limit_fragment(rich_rule.log.limit)]} rule.update(self._rich_rule_priority_fragment(rich_rule)) return {add_del: {"rule": rule}} - def _rich_rule_audit(self, rich_rule, enable, table, target, expr_fragments): + def _rich_rule_audit(self, policy, rich_rule, enable, table, expr_fragments): if not rich_rule.audit: return {} + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] chain_suffix = self._rich_rule_chain_suffix_from_log(rich_rule) rule = {"family": "inet", "table": TABLE_NAME, - "chain": "%s_%s_%s" % (table, target, chain_suffix), + "chain": "%s_%s_%s" % (table, _policy, chain_suffix), "expr": expr_fragments + [{"log": {"level": "audit"}}, self._rich_rule_limit_fragment(rich_rule.audit.limit)]} rule.update(self._rich_rule_priority_fragment(rich_rule)) return {add_del: {"rule": rule}} - def _rich_rule_action(self, zone, rich_rule, enable, table, target, expr_fragments): + def _rich_rule_action(self, policy, rich_rule, enable, table, expr_fragments): if not rich_rule.action: return {} + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + add_del = { True: "add", False: "delete" }[enable] chain_suffix = self._rich_rule_chain_suffix(rich_rule) - chain = "%s_%s_%s" % (table, target, chain_suffix) + chain = "%s_%s_%s" % (table, _policy, chain_suffix) if type(rich_rule.action) == Rich_Accept: rule_action = {"accept": None} elif type(rich_rule.action) == Rich_Reject: @@ -1038,10 +1035,9 @@ class nftables(object): elif type(rich_rule.action) == Rich_Drop: rule_action = {"drop": None} elif type(rich_rule.action) == Rich_Mark: - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], - zone=zone) table = "mangle" - chain = "%s_%s_%s" % (table, target, chain_suffix) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) + chain = "%s_%s_%s" % (table, _policy, chain_suffix) rule_action = {"mangle": {"key": {"meta": {"key": "mark"}}, "value": rich_rule.action.set}} else: @@ -1121,10 +1117,10 @@ class nftables(object): else: return {"range": [range[0], range[1]]} - def build_zone_ports_rules(self, enable, zone, proto, port, destination=None, rich_rule=None): + def build_policy_ports_rules(self, enable, policy, proto, port, destination=None, rich_rule=None): add_del = { True: "add", False: "delete" }[enable] table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) expr_fragments = [] if rich_rule: @@ -1146,21 +1142,21 @@ class nftables(object): rules = [] if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) else: rules.append({add_del: {"rule": {"family": "inet", "table": TABLE_NAME, - "chain": "%s_%s_allow" % (table, target), + "chain": "%s_%s_allow" % (table, _policy), "expr": expr_fragments + [{"accept": None}]}}}) return rules - def build_zone_protocol_rules(self, enable, zone, protocol, destination=None, rich_rule=None): + def build_policy_protocol_rules(self, enable, policy, protocol, destination=None, rich_rule=None): add_del = { True: "add", False: "delete" }[enable] table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) expr_fragments = [] if rich_rule: @@ -1181,22 +1177,22 @@ class nftables(object): rules = [] if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) else: rules.append({add_del: {"rule": {"family": "inet", "table": TABLE_NAME, - "chain": "%s_%s_allow" % (table, target), + "chain": "%s_%s_allow" % (table, _policy), "expr": expr_fragments + [{"accept": None}]}}}) return rules - def build_zone_source_ports_rules(self, enable, zone, proto, port, + def build_policy_source_ports_rules(self, enable, policy, proto, port, destination=None, rich_rule=None): add_del = { True: "add", False: "delete" }[enable] table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], zone=zone) + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) expr_fragments = [] if rich_rule: @@ -1218,19 +1214,21 @@ class nftables(object): rules = [] if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) else: rules.append({add_del: {"rule": {"family": "inet", "table": TABLE_NAME, - "chain": "%s_%s_allow" % (table, target), + "chain": "%s_%s_allow" % (table, _policy), "expr": expr_fragments + [{"accept": None}]}}}) return rules - def build_zone_helper_ports_rules(self, enable, zone, proto, port, - destination, helper_name, module_short_name): + def build_policy_helper_ports_rules(self, enable, policy, proto, port, + destination, helper_name, module_short_name): + table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "add", False: "delete" }[enable] rules = [] @@ -1241,8 +1239,6 @@ class nftables(object): "type": module_short_name, "protocol": proto}}}) - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], - zone=zone) expr_fragments = [] if destination: expr_fragments.append(self._rule_addr_fragment("daddr", destination)) @@ -1253,15 +1249,15 @@ class nftables(object): expr_fragments.append({"ct helper": "helper-%s-%s" % (helper_name, proto)}) rules.append({add_del: {"rule": {"family": "inet", "table": TABLE_NAME, - "chain": "filter_%s_allow" % (target), + "chain": "filter_%s_allow" % (_policy), "expr": expr_fragments}}}) return rules - def _build_zone_masquerade_nat_rules(self, enable, zone, family, rich_rule=None): + def _build_policy_masquerade_nat_rules(self, enable, policy, family, rich_rule=None): + table = "nat" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "add", False: "delete" }[enable] - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["POSTROUTING"], - zone=zone) expr_fragments = [] if rich_rule: @@ -1273,7 +1269,7 @@ class nftables(object): rule = {"family": family, "table": TABLE_NAME, - "chain": "nat_%s_%s" % (target, chain_suffix), + "chain": "nat_%s_%s" % (_policy, chain_suffix), "expr": expr_fragments + [{"match": {"left": {"meta": {"key": "oifname"}}, "op": "!=", @@ -1282,21 +1278,21 @@ class nftables(object): rule.update(self._rich_rule_priority_fragment(rich_rule)) return [{add_del: {"rule": rule}}] - def build_zone_masquerade_rules(self, enable, zone, rich_rule=None): + def build_policy_masquerade_rules(self, enable, policy, rich_rule=None): # nat tables needs to use ip/ip6 family rules = [] if rich_rule and (rich_rule.family and rich_rule.family == "ipv6" or rich_rule.source and check_address("ipv6", rich_rule.source.addr)): - rules.extend(self._build_zone_masquerade_nat_rules(enable, zone, "ip6", rich_rule)) + rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip6", rich_rule)) elif rich_rule and (rich_rule.family and rich_rule.family == "ipv4" or rich_rule.source and check_address("ipv4", rich_rule.source.addr)): - rules.extend(self._build_zone_masquerade_nat_rules(enable, zone, "ip", rich_rule)) + rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip", rich_rule)) else: - rules.extend(self._build_zone_masquerade_nat_rules(enable, zone, "ip", rich_rule)) + rules.extend(self._build_policy_masquerade_nat_rules(enable, policy, "ip", rich_rule)) + table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "add", False: "delete" }[enable] - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["FORWARD_OUT"], - zone=zone) expr_fragments = [] if rich_rule: @@ -1308,7 +1304,7 @@ class nftables(object): rule = {"family": "inet", "table": TABLE_NAME, - "chain": "filter_%s_%s" % (target, chain_suffix), + "chain": "filter_%s_%s" % (_policy, chain_suffix), "expr": expr_fragments + [{"match": {"left": {"ct": {"key": "state"}}, "op": "in", @@ -1319,12 +1315,12 @@ class nftables(object): return rules - def _build_zone_forward_port_nat_rules(self, enable, zone, port, protocol, + def _build_policy_forward_port_nat_rules(self, enable, policy, port, protocol, toaddr, toport, family, rich_rule=None): + table = "nat" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "add", False: "delete" }[enable] - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["PREROUTING"], - zone=zone) expr_fragments = [] if rich_rule: @@ -1351,28 +1347,28 @@ class nftables(object): rule = {"family": family, "table": TABLE_NAME, - "chain": "nat_%s_%s" % (target, chain_suffix), + "chain": "nat_%s_%s" % (_policy, chain_suffix), "expr": expr_fragments} rule.update(self._rich_rule_priority_fragment(rich_rule)) return [{add_del: {"rule": rule}}] - def build_zone_forward_port_rules(self, enable, zone, port, + def build_policy_forward_port_rules(self, enable, policy, port, protocol, toport, toaddr, rich_rule=None): rules = [] if rich_rule and (rich_rule.family and rich_rule.family == "ipv6" or toaddr and check_single_address("ipv6", toaddr)): - rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, + rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, port, protocol, toaddr, toport, "ip6", rich_rule)) elif rich_rule and (rich_rule.family and rich_rule.family == "ipv4" or toaddr and check_single_address("ipv4", toaddr)): - rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, + rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, port, protocol, toaddr, toport, "ip", rich_rule)) else: if toaddr and check_single_address("ipv6", toaddr): - rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, + rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, port, protocol, toaddr, toport, "ip6", rich_rule)) else: - rules.extend(self._build_zone_forward_port_nat_rules(enable, zone, + rules.extend(self._build_policy_forward_port_nat_rules(enable, policy, port, protocol, toaddr, toport, "ip", rich_rule)) return rules @@ -1384,8 +1380,9 @@ class nftables(object): raise FirewallError(INVALID_ICMPTYPE, "ICMP type '%s' not supported by %s" % (icmp_type, self.name)) - def build_zone_icmp_block_rules(self, enable, zone, ict, rich_rule=None): + def build_policy_icmp_block_rules(self, enable, policy, ict, rich_rule=None): table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) add_del = { True: "add", False: "delete" }[enable] if rich_rule and rich_rule.ipvs: @@ -1401,83 +1398,77 @@ class nftables(object): rules = [] for ipv in ipvs: - for chain in ["INPUT", "FORWARD_IN"]: - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], - zone=zone) - if self._fw.zone.query_icmp_block_inversion(zone): - final_chain = "%s_%s_allow" % (table, target) - target_fragment = {"accept": None} - else: - final_chain = "%s_%s_deny" % (table, target) - target_fragment = self._reject_fragment() - - expr_fragments = [] - if rich_rule: - expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family)) - expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination)) - expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source)) - expr_fragments.extend(self._icmp_types_to_nft_fragments(ipv, ict.name)) - - if rich_rule: - rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) - if rich_rule.action: - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) - else: - chain_suffix = self._rich_rule_chain_suffix(rich_rule) - rule = {"family": "inet", - "table": TABLE_NAME, - "chain": "%s_%s_%s" % (table, target, chain_suffix), - "expr": expr_fragments + [self._reject_fragment()]} - rule.update(self._rich_rule_priority_fragment(rich_rule)) - rules.append({add_del: {"rule": rule}}) + if self._fw.policy.query_icmp_block_inversion(policy): + final_chain = "%s_%s_allow" % (table, _policy) + target_fragment = {"accept": None} + else: + final_chain = "%s_%s_deny" % (table, _policy) + target_fragment = self._reject_fragment() + + expr_fragments = [] + if rich_rule: + expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family)) + expr_fragments.append(self._rich_rule_destination_fragment(rich_rule.destination)) + expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source)) + expr_fragments.extend(self._icmp_types_to_nft_fragments(ipv, ict.name)) + + if rich_rule: + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) + if rich_rule.action: + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) else: - if self._fw.get_log_denied() != "off" and self._fw.zone.query_icmp_block_inversion(zone): - rules.append({add_del: {"rule": {"family": "inet", - "table": TABLE_NAME, - "chain": final_chain, - "expr": (expr_fragments + - [self._pkttype_match_fragment(self._fw.get_log_denied()), - {"log": {"prefix": "\"%s_%s_ICMP_BLOCK: \"" % (table, zone)}}])}}}) + chain_suffix = self._rich_rule_chain_suffix(rich_rule) + rule = {"family": "inet", + "table": TABLE_NAME, + "chain": "%s_%s_%s" % (table, _policy, chain_suffix), + "expr": expr_fragments + [self._reject_fragment()]} + rule.update(self._rich_rule_priority_fragment(rich_rule)) + rules.append({add_del: {"rule": rule}}) + else: + if self._fw.get_log_denied() != "off" and self._fw.policy.query_icmp_block_inversion(policy): rules.append({add_del: {"rule": {"family": "inet", "table": TABLE_NAME, "chain": final_chain, - "expr": expr_fragments + [target_fragment]}}}) + "expr": (expr_fragments + + [self._pkttype_match_fragment(self._fw.get_log_denied()), + {"log": {"prefix": "\"%s_%s_ICMP_BLOCK: \"" % (table, policy)}}])}}}) + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, + "chain": final_chain, + "expr": expr_fragments + [target_fragment]}}}) return rules - def build_zone_icmp_block_inversion_rules(self, enable, zone): + def build_policy_icmp_block_inversion_rules(self, enable, policy): table = "filter" + _policy = self._fw.policy.policy_base_chain_name(policy, table, POLICY_CHAIN_PREFIX) rules = [] add_del = { True: "add", False: "delete" }[enable] - for chain in ["INPUT", "FORWARD_IN"]: - _zone = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS[chain], - zone=zone) + if self._fw.policy.query_icmp_block_inversion(policy): + target_fragment = self._reject_fragment() + else: + target_fragment = {"accept": None} - if self._fw.zone.query_icmp_block_inversion(zone): - target_fragment = self._reject_fragment() - else: - target_fragment = {"accept": None} + # WARN: The "index" used here must be kept in sync with + # build_policy_chain_rules() + # + rules.append({add_del: {"rule": {"family": "inet", + "table": TABLE_NAME, + "chain": "%s_%s" % (table, _policy), + "index": 4, + "expr": [self._icmp_match_fragment(), + target_fragment]}}}) - # WARN: The "index" used here must be kept in sync with - # build_zone_chain_rules() - # + if self._fw.get_log_denied() != "off" and self._fw.policy.query_icmp_block_inversion(policy): rules.append({add_del: {"rule": {"family": "inet", "table": TABLE_NAME, - "chain": "%s_%s" % (table, _zone), + "chain": "%s_%s" % (table, _policy), "index": 4, "expr": [self._icmp_match_fragment(), - target_fragment]}}}) - - if self._fw.get_log_denied() != "off" and self._fw.zone.query_icmp_block_inversion(zone): - rules.append({add_del: {"rule": {"family": "inet", - "table": TABLE_NAME, - "chain": "%s_%s" % (table, _zone), - "index": 4, - "expr": [self._icmp_match_fragment(), - self._pkttype_match_fragment(self._fw.get_log_denied()), - {"log": {"prefix": "%s_%s_ICMP_BLOCK: " % (table, _zone)}}]}}}) + self._pkttype_match_fragment(self._fw.get_log_denied()), + {"log": {"prefix": "%s_%s_ICMP_BLOCK: " % (table, policy)}}]}}}) return rules def build_rpfilter_rules(self, log_denied=False): @@ -1543,10 +1534,8 @@ class nftables(object): "expr": expr_fragments}}}) return rules - def build_zone_rich_source_destination_rules(self, enable, zone, rich_rule): + def build_policy_rich_source_destination_rules(self, enable, policy, rich_rule): table = "filter" - target = DEFAULT_ZONE_TARGET.format(chain=SHORTCUTS["INPUT"], - zone=zone) expr_fragments = [] expr_fragments.append(self._rich_rule_family_fragment(rich_rule.family)) @@ -1554,9 +1543,9 @@ class nftables(object): expr_fragments.append(self._rich_rule_source_fragment(rich_rule.source)) rules = [] - rules.append(self._rich_rule_log(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_audit(rich_rule, enable, table, target, expr_fragments)) - rules.append(self._rich_rule_action(zone, rich_rule, enable, table, target, expr_fragments)) + rules.append(self._rich_rule_log(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_audit(policy, rich_rule, enable, table, expr_fragments)) + rules.append(self._rich_rule_action(policy, rich_rule, enable, table, expr_fragments)) return rules diff --git a/src/firewall/errors.py b/src/firewall/errors.py index 4589f60..2ad46c2 100644 --- a/src/firewall/errors.py +++ b/src/firewall/errors.py @@ -88,6 +88,7 @@ INVALID_ENTRY = 136 INVALID_OPTION = 137 INVALID_HELPER = 138 INVALID_PRIORITY = 139 +INVALID_POLICY = 140 MISSING_TABLE = 200 MISSING_CHAIN = 201 diff --git a/src/firewall/functions.py b/src/firewall/functions.py index 6bc52d9..d4c5e90 100644 --- a/src/firewall/functions.py +++ b/src/firewall/functions.py @@ -27,7 +27,7 @@ __all__ = [ "PY2", "getPortID", "getPortRange", "portStr", "getServiceName", "check_single_address", "check_mac", "uniqify", "ppid_of_pid", "max_zone_name_len", "checkUser", "checkUid", "checkCommand", "checkContext", "joinArgs", "splitArgs", - "b2u", "u2b", "u2b_if_py2" ] + "b2u", "u2b", "u2b_if_py2", "max_policy_name_len"] import socket import os @@ -505,6 +505,15 @@ def ppid_of_pid(pid): return None return pid +def max_policy_name_len(): + """ + iptables limits length of chain to (currently) 28 chars. + The longest chain we create is pol__allow, + which leaves 28 - 10 = 18 chars for . + """ + from firewall.core.ipXtables import POLICY_CHAIN_PREFIX + return 28 - (len(POLICY_CHAIN_PREFIX) + len("_allow")) + def max_zone_name_len(): """ Netfilter limits length of chain to (currently) 28 chars. diff --git a/src/tests/features/service_include.at b/src/tests/features/service_include.at index 7f02701..1c86900 100644 --- a/src/tests/features/service_include.at +++ b/src/tests/features/service_include.at @@ -116,9 +116,7 @@ FWD_CHECK([-q --permanent --zone=drop --add-interface=foobar0]) FWD_CHECK([-q --permanent --zone=drop --add-service=my-service-with-include]) FWD_CHECK([-q --permanent --service=my-service-with-include --add-include=does-not-exist]) FWD_RELOAD(101, [ignore], [ignore], 251) -FWD_CHECK([--zone=drop --list-services], 0, [dnl -]) FWD_CHECK([--zone=public --list-services], 0, [dnl dhcpv6-client ssh ]) -- 1.8.3.1