From 7e487160867d2c2059d556d0d3af50bc8e8838fa Mon Sep 17 00:00:00 2001 From: Rajesh Mohan Date: Fri, 21 Jun 2013 17:55:08 -0700 Subject: [PATCH] Firewall as a Service (FWaaS) Iptables Driver blueprint quantum-fwaas-iptables-driver This is IPTables Driver for "Firewall As A Service" feature. This implements - Fwaas rules are mapped to IPTables - The rules are installed in the network namespace of quantum-routers Change-Id: I157182f2c86fbcf8c141b9ad3cfc71168153ebf8 --- .../firewall/agents/firewall_agent_api.py | 24 +- neutron/services/firewall/drivers/__init__.py | 16 + .../services/firewall/drivers/fwaas_base.py | 119 ++++++++ .../firewall/drivers/linux/__init__.py | 16 + .../firewall/drivers/linux/iptables_fwaas.py | 279 ++++++++++++++++++ .../services/firewall/drivers/__init__.py | 15 + .../firewall/drivers/linux/__init__.py | 15 + .../drivers/linux/test_iptables_fwaas.py | 199 +++++++++++++ 8 files changed, 660 insertions(+), 23 deletions(-) create mode 100644 neutron/services/firewall/drivers/__init__.py create mode 100644 neutron/services/firewall/drivers/fwaas_base.py create mode 100644 neutron/services/firewall/drivers/linux/__init__.py create mode 100644 neutron/services/firewall/drivers/linux/iptables_fwaas.py create mode 100644 neutron/tests/unit/services/firewall/drivers/__init__.py create mode 100644 neutron/tests/unit/services/firewall/drivers/linux/__init__.py create mode 100644 neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py diff --git a/neutron/services/firewall/agents/firewall_agent_api.py b/neutron/services/firewall/agents/firewall_agent_api.py index dcfb0d072..6d06a51dd 100644 --- a/neutron/services/firewall/agents/firewall_agent_api.py +++ b/neutron/services/firewall/agents/firewall_agent_api.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) FWaaSOpts = [ cfg.StrOpt( 'driver', - default=('neutron.services.firewall.agents.firewall_agent_api.' + default=('neutron.services.firewall.drivers.fwaas_base.' 'NoopFwaasDriver'), help=_("Name of the FWaaS Driver")), cfg.BoolOpt( @@ -84,25 +84,3 @@ class FWaaSAgentRpcCallbackMixin(object): def delete_firewall(self, context, firewall, host): """Handle RPC cast from plugin to delete a firewall.""" pass - - -class NoopFwaasDriver(object): - """Noop Fwaas Driver. - - Firewall driver which does nothing. - This driver is for disabling the firewall functionality. - Put in temporarily until Driver changes are integrated when - this will come in from there. - """ - - def create_firewall(self, apply_list, firewall): - pass - - def delete_firewall(self, apply_list, firewall): - pass - - def update_firewall(self, apply_list, firewall): - pass - - def apply_default_policy(self, apply_list): - pass diff --git a/neutron/services/firewall/drivers/__init__.py b/neutron/services/firewall/drivers/__init__.py new file mode 100644 index 000000000..5e8da711f --- /dev/null +++ b/neutron/services/firewall/drivers/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/neutron/services/firewall/drivers/fwaas_base.py b/neutron/services/firewall/drivers/fwaas_base.py new file mode 100644 index 000000000..66bf867c1 --- /dev/null +++ b/neutron/services/firewall/drivers/fwaas_base.py @@ -0,0 +1,119 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Dell Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc. + +import abc + + +class FwaasDriverBase(object): + """Firewall as a Service Driver base class. + + Using FwaasDriver Class, an instance of L3 perimeter Firewall + can be created. The firewall co-exists with the L3 agent. + + One instance is created for each tenant. One firewall policy + is associated with each tenant (in the Havana release). + + The Firewall can be visualized as having two zones (in Havana + release), trusted and untrusted. + + All the 'internal' interfaces of Neutron Router is treated as trusted. The + interface connected to 'external network' is treated as untrusted. + + The policy is applied on traffic ingressing/egressing interfaces on + the trusted zone. This implies that policy will be applied for traffic + passing from + - trusted to untrusted zones + - untrusted to trusted zones + - trusted to trusted zones + + Policy WILL NOT be applied for traffic from untrusted to untrusted zones. + This is not a problem in Havana release as there is only one interface + connected to external network. + + Since the policy is applied on the internal interfaces, the traffic + will be not be NATed to floating IP. For incoming traffic, the + traffic will get NATed to internal IP address before it hits + the firewall rules. So, while writing the rules, care should be + taken if using rules based on floating IP. + + The firewall rule addition/deletion/insertion/update are done by the + management console. When the policy is sent to the driver, the complete + policy is sent and the whole policy has to be applied atomically. The + firewall rules will not get updated individually. This is to avoid problems + related to out-of-order notifications or inconsistent behaviour by partial + application of rules. + """ + + __metaclass__ = abc.ABCMeta + + @abc.abstractmethod + def create_firewall(self, apply_list, firewall): + """Create the Firewall with default (drop all) policy. + + The default policy will be applied on all the interfaces of + trusted zone. + """ + pass + + @abc.abstractmethod + def delete_firewall(self, apply_list, firewall): + """Delete firewall. + + Removes all policies created by this instance and frees up + all the resources. + """ + pass + + @abc.abstractmethod + def update_firewall(self, apply_list, firewall): + """Apply the policy on all trusted interfaces. + + Remove previous policy and apply the new policy on all trusted + interfaces. + """ + pass + + @abc.abstractmethod + def apply_default_policy(self, apply_list, firewall): + """Apply the default policy on all trusted interfaces. + + Remove current policy and apply the default policy on all trusted + interfaces. + """ + pass + + +class NoopFwaasDriver(FwaasDriverBase): + """Noop Fwaas Driver. + + Firewall driver which does nothing. + This driver is for disabling Fwaas functionality. + """ + + def create_firewall(self, apply_list, firewall): + pass + + def delete_firewall(self, apply_list, firewall): + pass + + def update_firewall(self, apply_list, firewall): + pass + + def apply_default_policy(self, apply_list, firewall): + pass diff --git a/neutron/services/firewall/drivers/linux/__init__.py b/neutron/services/firewall/drivers/linux/__init__.py new file mode 100644 index 000000000..5e8da711f --- /dev/null +++ b/neutron/services/firewall/drivers/linux/__init__.py @@ -0,0 +1,16 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack Foundation. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/neutron/services/firewall/drivers/linux/iptables_fwaas.py b/neutron/services/firewall/drivers/linux/iptables_fwaas.py new file mode 100644 index 000000000..eaed8b004 --- /dev/null +++ b/neutron/services/firewall/drivers/linux/iptables_fwaas.py @@ -0,0 +1,279 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Dell Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc. + +from neutron.agent.linux import iptables_manager +from neutron.extensions import firewall as fw_ext +from neutron.openstack.common import log as logging +from neutron.services.firewall.drivers import fwaas_base + +LOG = logging.getLogger(__name__) +FWAAS_DRIVER_NAME = 'Fwaas iptables driver' +FWAAS_CHAIN = 'fwaas' +FWAAS_DEFAULT_CHAIN = 'fwaas-default-policy' +INGRESS_DIRECTION = 'ingress' +EGRESS_DIRECTION = 'egress' +CHAIN_NAME_PREFIX = {INGRESS_DIRECTION: 'i', + EGRESS_DIRECTION: 'o'} + +""" Firewall rules are applied on internal-interfaces of Neutron router. + The packets ingressing tenant's network will be on the output + direction on internal-interfaces. +""" +IPTABLES_DIR = {INGRESS_DIRECTION: '-o', + EGRESS_DIRECTION: '-i'} +IPV4 = 'ipv4' +IPV6 = 'ipv6' +IP_VER_TAG = {IPV4: 'v4', + IPV6: 'v6'} + + +class IptablesFwaasDriver(fwaas_base.FwaasDriverBase): + """IPTables driver for Firewall As A Service.""" + + def __init__(self): + LOG.debug(_("Initializing fwaas iptables driver")) + + def create_firewall(self, apply_list, firewall): + LOG.debug(_('Creating firewall %(fw_id)s for tenant %(tid)s)'), + {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) + try: + if firewall['admin_state_up']: + self._setup_firewall(apply_list, firewall) + else: + self.apply_default_policy(apply_list, firewall) + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception(_("Failed to create firewall: %s"), firewall['id']) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def delete_firewall(self, apply_list, firewall): + LOG.debug(_('Deleting firewall %(fw_id)s for tenant %(tid)s)'), + {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) + fwid = firewall['id'] + try: + for router_info in apply_list: + ipt_mgr = router_info.iptables_manager + self._remove_chains(fwid, ipt_mgr) + self._remove_default_chains(ipt_mgr) + ipt_mgr.apply() + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception(_("Failed to delete firewall: %s"), fwid) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def update_firewall(self, apply_list, firewall): + LOG.debug(_('Updating firewall %(fw_id)s for tenant %(tid)s)'), + {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) + try: + if firewall['admin_state_up']: + self._setup_firewall(apply_list, firewall) + else: + self.apply_default_policy(apply_list, firewall) + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception(_("Failed to update firewall: %s"), firewall['id']) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def apply_default_policy(self, apply_list, firewall): + LOG.debug(_('Applying firewall %(fw_id)s for tenant %(tid)s)'), + {'fw_id': firewall['id'], 'tid': firewall['tenant_id']}) + fwid = firewall['id'] + try: + for router_info in apply_list: + ipt_mgr = router_info.iptables_manager + + # the following only updates local memory; no hole in FW + self._remove_chains(fwid, ipt_mgr) + self._remove_default_chains(ipt_mgr) + + # create default 'DROP ALL' policy chain + self._add_default_policy_chain_v4v6(ipt_mgr) + self._enable_policy_chain(fwid, ipt_mgr) + + # apply the changes + ipt_mgr.apply() + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception(_("Failed to apply default policy on firewall: %s"), + fwid) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def _setup_firewall(self, apply_list, firewall): + fwid = firewall['id'] + for router_info in apply_list: + ipt_mgr = router_info.iptables_manager + + # the following only updates local memory; no hole in FW + self._remove_chains(fwid, ipt_mgr) + self._remove_default_chains(ipt_mgr) + + # create default 'DROP ALL' policy chain + self._add_default_policy_chain_v4v6(ipt_mgr) + #create chain based on configured policy + self._setup_chains(firewall, ipt_mgr) + + # apply the changes + ipt_mgr.apply() + + def _get_chain_name(self, fwid, ver, direction): + return '%s%s%s' % (CHAIN_NAME_PREFIX[direction], + IP_VER_TAG[ver], + fwid) + + def _setup_chains(self, firewall, ipt_mgr): + """Create Fwaas chain using the rules in the policy + """ + fw_rules_list = firewall['firewall_rule_list'] + fwid = firewall['id'] + + #default rules for invalid packets and established sessions + invalid_rule = self._drop_invalid_packets_rule() + est_rule = self._allow_established_rule() + + for ver in [IPV4, IPV6]: + if ver == IPV4: + table = ipt_mgr.ipv4['filter'] + else: + table = ipt_mgr.ipv6['filter'] + ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION) + ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION) + for name in [ichain_name, ochain_name]: + table.add_chain(name) + table.add_rule(name, invalid_rule) + table.add_rule(name, est_rule) + + for rule in fw_rules_list: + if not rule['enabled']: + continue + iptbl_rule = self._convert_fwaas_to_iptables_rule(rule) + if rule['ip_version'] == 4: + ver = IPV4 + table = ipt_mgr.ipv4['filter'] + else: + ver = IPV6 + table = ipt_mgr.ipv6['filter'] + ichain_name = self._get_chain_name(fwid, ver, INGRESS_DIRECTION) + ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION) + table.add_rule(ichain_name, iptbl_rule) + table.add_rule(ochain_name, iptbl_rule) + self._enable_policy_chain(fwid, ipt_mgr) + + def _remove_default_chains(self, nsid): + """Remove fwaas default policy chain.""" + self._remove_chain_by_name(IPV4, FWAAS_DEFAULT_CHAIN, nsid) + self._remove_chain_by_name(IPV6, FWAAS_DEFAULT_CHAIN, nsid) + + def _remove_chains(self, fwid, ipt_mgr): + """Remove fwaas policy chain.""" + for ver in [IPV4, IPV6]: + for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]: + chain_name = self._get_chain_name(fwid, ver, direction) + self._remove_chain_by_name(ver, chain_name, ipt_mgr) + + def _add_default_policy_chain_v4v6(self, ipt_mgr): + ipt_mgr.ipv4['filter'].add_chain(FWAAS_DEFAULT_CHAIN) + ipt_mgr.ipv4['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP') + ipt_mgr.ipv6['filter'].add_chain(FWAAS_DEFAULT_CHAIN) + ipt_mgr.ipv6['filter'].add_rule(FWAAS_DEFAULT_CHAIN, '-j DROP') + + def _remove_chain_by_name(self, ver, chain_name, ipt_mgr): + if ver == IPV4: + ipt_mgr.ipv4['filter'].ensure_remove_chain(chain_name) + else: + ipt_mgr.ipv6['filter'].ensure_remove_chain(chain_name) + + def _add_rules_to_chain(self, ipt_mgr, ver, chain_name, rules): + if ver == IPV4: + table = ipt_mgr.ipv4['filter'] + else: + table = ipt_mgr.ipv6['filter'] + for rule in rules: + table.add_rule(chain_name, rule) + + def _enable_policy_chain(self, fwid, ipt_mgr): + bname = iptables_manager.binary_name + + for (ver, tbl) in [(IPV4, ipt_mgr.ipv4['filter']), + (IPV6, ipt_mgr.ipv4['filter'])]: + for direction in [INGRESS_DIRECTION, EGRESS_DIRECTION]: + chain_name = self._get_chain_name(fwid, ver, direction) + chain_name = iptables_manager.get_chain_name(chain_name) + if chain_name in tbl.chains: + jump_rule = ['%s qr-+ -j %s-%s' % (IPTABLES_DIR[direction], + bname, chain_name)] + self._add_rules_to_chain(ipt_mgr, ver, 'FORWARD', + jump_rule) + + #jump to DROP_ALL policy + chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN) + jump_rule = ['-o qr-+ -j %s-%s' % (bname, chain_name)] + self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule) + self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule) + + #jump to DROP_ALL policy + chain_name = iptables_manager.get_chain_name(FWAAS_DEFAULT_CHAIN) + jump_rule = ['-i qr-+ -j %s-%s' % (bname, chain_name)] + self._add_rules_to_chain(ipt_mgr, IPV4, 'FORWARD', jump_rule) + self._add_rules_to_chain(ipt_mgr, IPV6, 'FORWARD', jump_rule) + + def _convert_fwaas_to_iptables_rule(self, rule): + if rule.get('action') == 'allow': + rule['action'] = 'ACCEPT' + else: + rule['action'] = 'DROP' + + args = [self._protocol_arg(rule.get('protocol')), + self._port_arg('dport', + rule.get('protocol'), + rule.get('destination_port')), + self._port_arg('sport', + rule.get('protocol'), + rule.get('source_port')), + self._ip_prefix_arg('s', rule.get('source_ip_address')), + self._ip_prefix_arg('d', rule.get('destination_ip_address')), + self._action_arg(rule.get('action'))] + + iptables_rule = ' '.join(args) + return iptables_rule + + def _drop_invalid_packets_rule(self): + return '-m state --state INVALID -j DROP' + + def _allow_established_rule(self): + return '-m state --state ESTABLISHED,RELATED -j ACCEPT' + + def _action_arg(self, action): + if action: + return '-j %s' % action + return '' + + def _protocol_arg(self, protocol): + if protocol: + return '-p %s' % protocol + return '' + + def _port_arg(self, direction, protocol, port): + if not (protocol in ['udp', 'tcp'] and port): + return '' + return '--%s %s' % (direction, port) + + def _ip_prefix_arg(self, direction, ip_prefix): + if ip_prefix: + return '-%s %s' % (direction, ip_prefix) + return '' diff --git a/neutron/tests/unit/services/firewall/drivers/__init__.py b/neutron/tests/unit/services/firewall/drivers/__init__.py new file mode 100644 index 000000000..cae279d0a --- /dev/null +++ b/neutron/tests/unit/services/firewall/drivers/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/neutron/tests/unit/services/firewall/drivers/linux/__init__.py b/neutron/tests/unit/services/firewall/drivers/linux/__init__.py new file mode 100644 index 000000000..cae279d0a --- /dev/null +++ b/neutron/tests/unit/services/firewall/drivers/linux/__init__.py @@ -0,0 +1,15 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2013 OpenStack Foundation +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. diff --git a/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py b/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py new file mode 100644 index 000000000..2550bf171 --- /dev/null +++ b/neutron/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas.py @@ -0,0 +1,199 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2013 Dell Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +# @author: Rajesh Mohan, Rajesh_Mohan3@Dell.com, DELL Inc. + +import mock +from mock import call +from oslo.config import cfg + +from neutron.agent.common import config as a_cfg +import neutron.services.firewall.drivers.linux.iptables_fwaas as fwaas +from neutron.tests import base +from neutron.tests.unit import test_api_v2 + + +_uuid = test_api_v2._uuid +FAKE_SRC_PREFIX = '10.0.0.0/24' +FAKE_DST_PREFIX = '20.0.0.0/24' +FAKE_PROTOCOL = 'tcp' +FAKE_SRC_PORT = 5000 +FAKE_DST_PORT = 22 +FAKE_FW_ID = 'fake-fw-uuid' + + +class IptablesFwaasTestCase(base.BaseTestCase): + def setUp(self): + super(IptablesFwaasTestCase, self).setUp() + cfg.CONF.register_opts(a_cfg.ROOT_HELPER_OPTS, 'AGENT') + self.utils_exec_p = mock.patch( + 'neutron.agent.linux.utils.execute') + self.utils_exec = self.utils_exec_p.start() + self.addCleanup(self.utils_exec_p.stop) + self.iptables_cls_p = mock.patch( + 'neutron.agent.linux.iptables_manager.IptablesManager') + iptables_cls = self.iptables_cls_p.start() + self.addCleanup(self.iptables_cls_p.stop) + self.iptables_inst = mock.Mock() + self.v4filter_inst = mock.Mock() + self.v6filter_inst = mock.Mock() + self.v4filter_inst.chains = [] + self.v6filter_inst.chains = [] + self.iptables_inst.ipv4 = {'filter': self.v4filter_inst} + self.iptables_inst.ipv6 = {'filter': self.v6filter_inst} + iptables_cls.return_value = self.iptables_inst + + self.router_info_inst = mock.Mock() + self.router_info_inst.iptables_manager = self.iptables_inst + + self.firewall = fwaas.IptablesFwaasDriver() + + def _fake_rules_v4(self, fwid): + rule_list = [] + rule1 = {'enabled': True, + 'action': 'allow', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '80', + 'source_ip_address': '10.24.4.2'} + rule2 = {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '22'} + ingress_chain = ('iv4%s' % fwid)[:11] + self.v4filter_inst.chains.append(ingress_chain) + egress_chain = ('ov4%s' % fwid)[:11] + self.v4filter_inst.chains.append(egress_chain) + rule_list.append(rule1) + rule_list.append(rule2) + return rule_list + + def _fake_firewall_no_rule(self): + rule_list = [] + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': True, + 'tenant_id': 'tenant-uuid', + 'firewall_rule_list': rule_list} + return fw_inst + + def _fake_firewall(self, rule_list): + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': True, + 'tenant_id': 'tenant-uuid', + 'firewall_rule_list': rule_list} + return fw_inst + + def _fake_firewall_with_admin_down(self, rule_list): + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': False, + 'tenant_id': 'tenant-uuid', + 'firewall_rule_list': rule_list} + return fw_inst + + def _fake_apply_list(self): + apply_list = [] + apply_list.append(self.router_info_inst) + return apply_list + + def _setup_firewall_with_rules(self, func): + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4(FAKE_FW_ID) + firewall = self._fake_firewall(rule_list) + func(apply_list, firewall) + invalid_rule = '-m state --state INVALID -j DROP' + est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT' + rule1 = '-p tcp --dport 80 -s 10.24.4.2 -j ACCEPT' + rule2 = '-p tcp --dport 22 -j DROP' + ingress_chain = 'iv4%s' % firewall['id'] + egress_chain = 'ov4%s' % firewall['id'] + bname = fwaas.iptables_manager.binary_name + ipt_mgr_ichain = '%s-%s' % (bname, ingress_chain[:11]) + ipt_mgr_echain = '%s-%s' % (bname, egress_chain[:11]) + calls = [call.ensure_remove_chain('iv4fake-fw-uuid'), + call.ensure_remove_chain('ov4fake-fw-uuid'), + call.ensure_remove_chain('fwaas-default-policy'), + call.add_chain('fwaas-default-policy'), + call.add_rule('fwaas-default-policy', '-j DROP'), + call.add_chain(ingress_chain), + call.add_rule(ingress_chain, invalid_rule), + call.add_rule(ingress_chain, est_rule), + call.add_chain(egress_chain), + call.add_rule(egress_chain, invalid_rule), + call.add_rule(egress_chain, est_rule), + call.add_rule(ingress_chain, rule1), + call.add_rule(egress_chain, rule1), + call.add_rule(ingress_chain, rule2), + call.add_rule(egress_chain, rule2), + call.add_rule('FORWARD', '-o qr-+ -j %s' % ipt_mgr_ichain), + call.add_rule('FORWARD', '-i qr-+ -j %s' % ipt_mgr_echain), + call.add_rule('FORWARD', '-o qr-+ -j %s-fwaas-defau' % bname), + call.add_rule('FORWARD', '-i qr-+ -j %s-fwaas-defau' % bname)] + self.v4filter_inst.assert_has_calls(calls) + + def test_create_firewall_no_rules(self): + apply_list = self._fake_apply_list() + firewall = self._fake_firewall_no_rule() + self.firewall.create_firewall(apply_list, firewall) + invalid_rule = '-m state --state INVALID -j DROP' + est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT' + ingress_chain = ('iv4%s' % firewall['id']) + egress_chain = ('ov4%s' % firewall['id']) + bname = fwaas.iptables_manager.binary_name + calls = [call.ensure_remove_chain('iv4fake-fw-uuid'), + call.ensure_remove_chain('ov4fake-fw-uuid'), + call.ensure_remove_chain('fwaas-default-policy'), + call.add_chain('fwaas-default-policy'), + call.add_rule('fwaas-default-policy', '-j DROP'), + call.add_chain(ingress_chain), + call.add_rule(ingress_chain, invalid_rule), + call.add_rule(ingress_chain, est_rule), + call.add_chain(egress_chain), + call.add_rule(egress_chain, invalid_rule), + call.add_rule(egress_chain, est_rule), + call.add_rule('FORWARD', '-o qr-+ -j %s-fwaas-defau' % bname), + call.add_rule('FORWARD', '-i qr-+ -j %s-fwaas-defau' % bname)] + self.v4filter_inst.assert_has_calls(calls) + + def test_create_firewall_with_rules(self): + self._setup_firewall_with_rules(self.firewall.create_firewall) + + def test_update_firewall_with_rules(self): + self._setup_firewall_with_rules(self.firewall.update_firewall) + + def test_delete_firewall(self): + apply_list = self._fake_apply_list() + firewall = self._fake_firewall_no_rule() + self.firewall.delete_firewall(apply_list, firewall) + ingress_chain = 'iv4%s' % firewall['id'] + egress_chain = 'ov4%s' % firewall['id'] + calls = [call.ensure_remove_chain(ingress_chain), + call.ensure_remove_chain(egress_chain), + call.ensure_remove_chain('fwaas-default-policy')] + self.v4filter_inst.assert_has_calls(calls) + + def test_create_firewall_with_admin_down(self): + rule_list = self._fake_rules_v4(FAKE_FW_ID) + apply_list = self._fake_apply_list() + firewall = self._fake_firewall_with_admin_down(rule_list) + self.firewall.create_firewall(apply_list, firewall) + calls = [call.ensure_remove_chain('iv4fake-fw-uuid'), + call.ensure_remove_chain('ov4fake-fw-uuid'), + call.ensure_remove_chain('fwaas-default-policy'), + call.add_chain('fwaas-default-policy'), + call.add_rule('fwaas-default-policy', '-j DROP')] + self.v4filter_inst.assert_has_calls(calls) -- 2.45.2