From fd8d6d27f046973686dc9c618f9db7498e220116 Mon Sep 17 00:00:00 2001 From: Margaret Frances Date: Tue, 5 Jul 2016 10:34:25 -0400 Subject: [PATCH] FWaaS v2 L3 Agent Extension FWaaS v2 agent extension supporting L3 ports and interfaces with the FWaaS v2 plugin and the FWaaS v2 driver. Partial-Implements: blueprint fwaas-api-2.0 Co-Authored-By: Chandan Dutta Chowdhury Co-Authored-By: Nate Johnston Depends-on: Ia923cf1c1abb2dae18370215e16a6f896671392b Change-Id: Iff506bd11b83d396305e631f3dd95d44cf38fd63 --- neutron_fwaas/cmd/eventlet/agents/fw.py | 12 +- .../firewall/agents/firewall_agent_api.py | 8 + .../l3reference/firewall_l3_agent_v2.py | 461 ++++++++++++++++++ .../firewall/agents/varmour/varmour_router.py | 2 +- .../firewall/drivers/fwaas_base_v2.py | 96 ++++ .../drivers/linux/iptables_fwaas_v2.py | 456 +++++++++++++++++ .../l3reference/test_firewall_l3_agent.py | 6 +- .../l3reference/test_firewall_l3_agent_v2.py | 308 ++++++++++++ .../agents/test_firewall_agent_api.py | 27 +- .../agents/varmour/test_varmour_router.py | 5 +- .../drivers/linux/test_iptables_fwaas_v2.py | 386 +++++++++++++++ .../drivers/varmour/test_varmour_fwaas.py | 4 +- 12 files changed, 1753 insertions(+), 18 deletions(-) create mode 100644 neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py create mode 100644 neutron_fwaas/services/firewall/drivers/fwaas_base_v2.py create mode 100644 neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py create mode 100644 neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py create mode 100644 neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py diff --git a/neutron_fwaas/cmd/eventlet/agents/fw.py b/neutron_fwaas/cmd/eventlet/agents/fw.py index b48c0cf19..e110858ab 100644 --- a/neutron_fwaas/cmd/eventlet/agents/fw.py +++ b/neutron_fwaas/cmd/eventlet/agents/fw.py @@ -26,12 +26,13 @@ from neutron.common import config as common_config from neutron.common import topics from neutron.conf.agent.l3 import config as l3_config from neutron import service as neutron_service +from neutron_fwaas.services.firewall.agents import firewall_agent_api as fwa -FWAAS_AGENT = ( - 'neutron_fwaas.services.firewall.agents.' - 'l3reference.firewall_l3_agent.L3WithFWaaS' -) +FWAAS_AGENTS = {fwa.FWAAS_V1: ('neutron_fwaas.services.firewall.agents.' + 'l3reference.firewall_l3_agent.L3WithFWaaS'), + fwa.FWAAS_V2: ('neutron_fwaas.services.firewall.agents.' + 'l3reference.firewall_l3_agent_v2.L3WithFWaaS')} def register_opts(conf): @@ -48,10 +49,11 @@ def register_opts(conf): config.register_availability_zone_opts_helper(conf) -def main(manager=FWAAS_AGENT): +def main(): register_opts(cfg.CONF) common_config.init(sys.argv[1:]) config.setup_logging() + manager = FWAAS_AGENTS[cfg.CONF.fwaas.agent_version] server = neutron_service.Service.create( binary='neutron-l3-agent', topic=topics.L3_AGENT, diff --git a/neutron_fwaas/services/firewall/agents/firewall_agent_api.py b/neutron_fwaas/services/firewall/agents/firewall_agent_api.py index c61f4e256..68e28d7b7 100644 --- a/neutron_fwaas/services/firewall/agents/firewall_agent_api.py +++ b/neutron_fwaas/services/firewall/agents/firewall_agent_api.py @@ -20,6 +20,10 @@ import oslo_messaging from neutron_fwaas._i18n import _ +FWAAS_V1 = "v1" +FWAAS_V2 = "v2" + + FWaaSOpts = [ cfg.StrOpt( 'driver', @@ -29,6 +33,10 @@ FWaaSOpts = [ 'enabled', default=False, help=_("Enable FWaaS")), + cfg.StrOpt( + 'agent_version', + default=FWAAS_V1, + help=_("Firewall agent class")), ] cfg.CONF.register_opts(FWaaSOpts, 'fwaas') diff --git a/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py b/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py new file mode 100644 index 000000000..6345c0eb6 --- /dev/null +++ b/neutron_fwaas/services/firewall/agents/l3reference/firewall_l3_agent_v2.py @@ -0,0 +1,461 @@ +# Copyright (c) 2016 +# 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. + +from neutron.agent.l3 import agent +from neutron.agent.linux import ip_lib +from neutron import context +from neutron.plugins.common import constants as n_const +from neutron_fwaas.common import fwaas_constants as f_const +from oslo_config import cfg +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from neutron_fwaas._i18n import _, _LE +from neutron_fwaas.extensions import firewall as fw_ext +from neutron_fwaas.services.firewall.agents import firewall_agent_api as api +from neutron_fwaas.services.firewall.agents import firewall_service + +LOG = logging.getLogger(__name__) + + +class FWaaSL3PluginApi(api.FWaaSPluginApiMixin): + """Agent side of the FWaaS agent-to-plugin RPC API.""" + def __init__(self, topic, host): + super(FWaaSL3PluginApi, self).__init__(topic, host) + + def get_firewall_groups_for_project(self, context, **kwargs): + """Fetches a project's firewall groups from the plugin.""" + LOG.debug("Fetch firewall groups from plugin") + cctxt = self.client.prepare() + return cctxt.call(context, 'get_firewall_groups_for_project', + host=self.host) + + def get_projects_with_firewall_groups(self, context, **kwargs): + """Fetches from the plugin all projects that have firewall groups + configured. + """ + LOG.debug("Fetch from plugin projects that have firewall groups " + "configured") + cctxt = self.client.prepare() + return cctxt.call(context, + 'get_projects_with_firewall_groups', host=self.host) + + def firewall_group_deleted(self, context, fwg_id, **kwargs): + """Notifies the plugin that a firewall group has been deleted.""" + LOG.debug("Notify plugin that firewall group has been deleted") + cctxt = self.client.prepare() + return cctxt.call(context, 'firewall_group_deleted', fwg_id=fwg_id, + host=self.host) + + def set_firewall_group_status(self, context, fwg_id, status, **kwargs): + """Sets firewall group's status on the plugin.""" + LOG.debug("Set firewall groups from plugin") + cctxt = self.client.prepare() + return cctxt.call(context, 'set_firewall_group_status', + fwg_id=fwg_id, status=status, host=self.host) + + +class FWaaSL3AgentRpcCallback(api.FWaaSAgentRpcCallbackMixin): + """FWaaS agent support to be used by neutron's L3 agent.""" + + def __init__(self, host, conf): + LOG.debug("Initializing firewall group agent") + self.neutron_service_plugins = None + self.conf = conf + self.fwaas_enabled = cfg.CONF.fwaas.enabled + + # None means l3-agent has no information on the server + # configuration due to the lack of RPC support. + if self.neutron_service_plugins is not None: + fwaas_plugin_configured = (n_const.FIREWALL + in self.neutron_service_plugins) + if fwaas_plugin_configured and not self.fwaas_enabled: + msg = _("FWaaS plugin is configured in the server side, but " + "FWaaS is disabled in L3-agent.") + LOG.error(msg) + raise SystemExit(1) + self.fwaas_enabled = self.fwaas_enabled and fwaas_plugin_configured + + if self.fwaas_enabled: + # NOTE: Temp location for creating service and loading driver + self.fw_service = firewall_service.FirewallService() + self.fwaas_driver = self.fw_service.load_device_drivers() + + self.services_sync_needed = False + self.fwplugin_rpc = FWaaSL3PluginApi(f_const.FIREWALL_PLUGIN, + host) + super(FWaaSL3AgentRpcCallback, self).__init__(host=host) + + @property + def _local_namespaces(self): + root_ip = ip_lib.IPWrapper() + local_ns_list = root_ip.get_namespaces() + return local_ns_list + + def _has_port_insertion_fields(self, firewall_group): + """The presence of the 'add-port-ids' key in the firewall group dict + shows we are using the current version of the plugin. If this key + is absent, we are in an upgrade and message is from an older + version of the plugin. + """ + return 'add-port-ids' in firewall_group + + def _get_firewall_group_ports(self, context, firewall_group, + to_delete=False, require_new_plugin=False): + """Returns in-namespace ports, either from firewall group dict if newer + version of plugin or from project routers otherwise. + + NOTE: Vernacular move from "tenant" to "project" doesn't yet appear + as a key in router or firewall group objects. + """ + fwg_port_ids = [] + if self._has_port_insertion_fields(firewall_group): + if to_delete: + fwg_port_ids = firewall_group['del-port-ids'] + else: + fwg_port_ids = firewall_group['add-port-ids'] + elif not require_new_plugin: + routers = [self.router_info[rid] for rid in self.router_info] + for router in routers: + if router.router['tenant_id'] == firewall_group['tenant_id']: + fwg_port_ids.extend([p['id'] for p in + router.internal_ports]) + + # Return in-namespace port objects. + return self._get_in_ns_ports(fwg_port_ids) + + def _get_in_ns_ports(self, port_ids): + """Returns port objects in the local namespace, along with their + router_info. + """ + in_ns_ports = [] + if port_ids: + for router_id in self.router_info: + # For routers without an interface - get_routers returns + # the router - but this is not yet populated in router_info + router_info = self.router_info[router_id] + if router_info.ns_name not in self._local_namespaces: + continue + in_ns_router_port_ids = [] + for port in router_info.internal_ports: + if port['id'] in port_ids: + in_ns_router_port_ids.append(port['id']) + if in_ns_router_port_ids: + in_ns_ports.append((router_info, in_ns_router_port_ids)) + return in_ns_ports + + def _invoke_driver_for_sync_from_plugin(self, ctx, port, firewall_group): + """Calls the FWaaS driver's delete_firewall_group method if firewall + group has status of PENDING_DELETE; calls driver's + update_firewall_group method for all other statuses. Both of these + methods are idempotent. + """ + if firewall_group['status'] == n_const.PENDING_DELETE: + try: + self.fwaas_driver.delete_firewall_group( + self.conf.agent_mode, [port], firewall_group) + self.fwplugin_rpc.firewall_group_deleted( + ctx, firewall_group['id']) + except fw_ext.FirewallInternalDriverError: + msg = _LE("FWaaS driver error on %(status)s " + "for firewall group: %(fwg_id)s") + LOG.exception(msg, {'status': firewall_group['status'], + 'fwg_id': firewall_group['id']}) + self.fwplugin_rpc.set_firewall_group_status( + ctx, firewall_group['id'], n_const.ERROR) + else: # PENDING_UPDATE, PENDING_CREATE, ... + + # Prepare firewall group status to return to plugin; may be + # overwritten if call to driver fails. + if firewall_group['admin_state_up']: + status = n_const.ACTIVE + else: + status = n_const.DOWN + + # Call the driver. + try: + self.fwaas_driver.update_firewall_group( + self.conf.agent_mode, [port], firewall_group) + except fw_ext.FirewallInternalDriverError: + msg = _LE("FWaaS driver error on %(status)s for firewall " + "group: " + "%(fwg_id)s") + LOG.exception(msg, {'status': firewall_group['status'], + 'fwg_id': firewall_group['id']}) + status = n_const.ERROR + + # Notify the plugin of firewall group's status. + self.fwplugin_rpc.set_firewall_group_status( + ctx, firewall_group['id'], status) + + def _process_router_add(self, new_router): + """If the new router is in the local namespace, queries the plugin to + get the firewall groups for the project in question and then sees if + the router has any ports for any firewall group that is configured + for that project. If so, installs firewall group rules on the + requested ports on this router. + """ + LOG.debug("Process router add, router_id: %s.", + new_router.router['id']) + router_id = new_router.router['id'] + if router_id not in self.router_info or \ + self.router_info[router_id].ns_name not in \ + self._local_namespaces: + return + + # Get the firewall groups for the new router's project. + # NOTE: Vernacular move from "tenant" to "project" doesn't yet appear + # as a key in router or firewall group objects. + ctx = context.Context('', new_router.router['tenant_id']) + fwg_list = self.fwplugin_rpc.get_firewall_groups_for_project(ctx) + + # Apply a firewall group, as requested, to ports on the new router. + for port in new_router.router.internal_ports: + for firewall_group in fwg_list: + if (self._has_port_insertion_fields(firewall_group) and + (port['id'] in firewall_group['add-port-ids'] or + port['id'] in firewall_group['del-port-ids'])): + self._invoke_driver_for_sync_from_plugin(ctx, port, + firewall_group) + # A port can have at most one firewall group. + break + + def process_router_add(self, new_router): + """Handles agent restart and router add. Fetches firewall groups from + plugin and updates driver. + """ + if not self.fwaas_enabled: + return + + try: + self._process_router_add(new_router) + except Exception: + LOG.exception(_LE("FWaaS RPC info call failed for %s"), + new_router.router['id']) + self.services_sync_needed = True + + def process_services_sync(self, ctx): + """Syncs with plugin and applies the sync data. + """ + + if not self.services_sync_needed or not self.fwaas_enabled: + return + + try: + # Fetch from the plugin the list of projects with firewall groups. + project_ids = \ + self.fwplugin_rpc.get_projects_with_firewall_groups(ctx) + LOG.debug("Projects with firewall groups: %s", + ', '.join(project_ids)) + for project_id in project_ids: + ctx = context.Context('', project_id) + fwg_list = \ + self.fwplugin_rpc.get_firewall_groups_for_project(ctx) + for firewall_group in fwg_list: + if firewall_group['status'] == n_const.PENDING_DELETE: + self.delete_firewall_group(ctx, firewall_group, + self.host) + # No need to apply sync data for ACTIVE firewall group. + elif firewall_group['status'] != n_const.ACTIVE: + self.update_firewall_group(ctx, firewall_group, + self.host) + self.services_sync_needed = False + except Exception: + LOG.exception(_LE("Failed FWaaS process services sync.")) + self.services_sync_needed = True + + @log_helpers.log_method_call + def create_firewall_group(self, context, firewall_group, host): + """Handles RPC from plugin to create a firewall group. + """ + + # Get the in-namespace ports to which to add the firewall group. + ports_for_fwg = self._get_firewall_group_ports(context, firewall_group) + + if not ports_for_fwg: + return + + LOG.debug("Create firewall group %(fwg_id)s on ports: %(ports)s" + % {'fwg_id': firewall_group['id'], + 'ports': ', '.join([p for ri_ports in ports_for_fwg + for p in ri_ports[1]])}) + + # Set firewall group status; will be overwritten if call to driver + # fails. + if firewall_group['admin_state_up']: + status = n_const.ACTIVE + else: + status = n_const.DOWN + + # Call the driver. + try: + self.fwaas_driver.create_firewall_group(self.conf.agent_mode, + ports_for_fwg, + firewall_group) + except fw_ext.FirewallInternalDriverError: + msg = _LE("FWaaS driver error in create_firewall_group " + "for firewall group: %(fwg_id)s") + LOG.exception(msg, {'fwg_id': firewall_group['id']}) + status = n_const.ERROR + + # Send firewall group's status to plugin. + try: + self.fwplugin_rpc.set_firewall_group_status(context, + firewall_group['id'], status) + except Exception: + msg = _LE("FWaaS RPC failure in create_firewall_group " + "for firewall group: %(fwg_id)s") + LOG.exception(msg, {'fwg_id': firewall_group['id']}) + self.services_sync_needed = True + + @log_helpers.log_method_call + def update_firewall_group(self, context, firewall_group, host): + """Handles RPC from plugin to update a firewall group. + """ + + # Initialize firewall group status. + status = "" + + # Get the list of in-namespace ports from which to delete the firewall + # group. + ports_for_fwg = self._get_firewall_group_ports(context, firewall_group, + to_delete=True, require_new_plugin=True) + + # Remove firewall group from ports if requested. + if ports_for_fwg: + fw_ports = [p for ri_ports in ports_for_fwg for p in ri_ports[1]] + LOG.debug("Update (delete) firewall group %(fwg_id)s on ports: " + "%(ports)s" % {'fwg_id': firewall_group['id'], + 'ports': ', '.join(fw_ports)}) + + # Set firewall group's status; will be overwritten if call to + # driver fails. + + if firewall_group['admin_state_up']: + status = n_const.ACTIVE + if firewall_group['last-port']: + status = n_const.INACTIVE + else: + status = n_const.DOWN + + # Call the driver. + try: + self.fwaas_driver.delete_firewall_group(self.conf.agent_mode, + ports_for_fwg, + firewall_group) + except fw_ext.FirewallInternalDriverError: + msg = _LE("FWaaS driver error in update_firewall_group " + "(add) for firewall group: %s") + LOG.exception(msg, firewall_group['id']) + status = n_const.ERROR + + # Handle the add router and/or rule, policy, firewall group attribute + # updates. + if status not in (n_const.ERROR, n_const.INACTIVE): + ports_for_fwg = self._get_firewall_group_ports(context, + firewall_group) + if ports_for_fwg: + + LOG.debug("Update (create) firewall group %(fwg_id)s on " + "ports: %(ports)s" % {'fwg_id': firewall_group['id'], + 'ports': ', '.join(fw_ports)}) + + # Set firewall group status, which will be overwritten if call + # to driver fails. + if firewall_group['admin_state_up']: + status = n_const.ACTIVE + else: + status = n_const.DOWN + + # Call the driver. + try: + self.fwaas_driver.update_firewall_group( + self.conf.agent_mode, ports_for_fwg, + firewall_group) + except fw_ext.FirewallInternalDriverError: + msg = _LE("FWaaS driver error in update_firewall_group " + "for firewall group: %s") + LOG.exception(msg, firewall_group['id']) + status = n_const.ERROR + else: + status = n_const.INACTIVE + + # Return status to plugin. + try: + self.fwplugin_rpc.set_firewall_group_status(context, + firewall_group['id'], status) + except Exception: + LOG.exception(_LE("FWaaS RPC failure in update_firewall_group " + "for firewall group: %(fwg_id)s"), + {'fwg_id': firewall_group['id']}) + self.services_sync_needed = True + + @log_helpers.log_method_call + def delete_firewall_group(self, context, firewall_group, host): + """Handles RPC from plugin to delete a firewall group. + """ + + ports_for_fwg = self._get_firewall_group_ports(context, firewall_group, + to_delete=True) + + if not ports_for_fwg: + return + + fw_ports = [p for ri_ports in ports_for_fwg for p in ri_ports[1]] + LOG.debug("Delete firewall group %(fwg_id)s on ports: %(ports)s" + % {'fwg_id': firewall_group['id'], + 'ports': ', '.join(fw_ports)}) + + # Set the firewall group's status to return to plugin; status may be + # overwritten if call to driver fails. + if firewall_group['admin_state_up']: + status = n_const.ACTIVE + else: + status = n_const.DOWN + try: + self.fwaas_driver.delete_firewall_group(self.conf.agent_mode, + ports_for_fwg, + firewall_group) + # Call the driver. + except fw_ext.FirewallInternalDriverError: + LOG.exception(_LE("FWaaS driver error in delete_firewall_group " + "for firewall group: %(fwg_id)s"), + {'fwg_id': firewall_group['id']}) + status = n_const.ERROR + + # Notify plugin of deletion or return firewall group's status to + # plugin, as appopriate. + try: + if status in [n_const.ACTIVE, n_const.DOWN]: + self.fwplugin_rpc.firewall_group_deleted(context, + firewall_group['id']) + else: + self.fwplugin_rpc.set_firewall_group_status(context, + firewall_group['id'], status) + except Exception: + LOG.exception(_LE("FWaaS RPC failure in delete_firewall_group " + "for firewall group: %(fwg_id)s"), + {'fwg_id': firewall_group['id']}) + self.services_sync_needed = True + + +class L3WithFWaaS(FWaaSL3AgentRpcCallback, agent.L3NATAgentWithStateReport): + + def __init__(self, host, conf=None): + if conf: + self.conf = conf + else: + self.conf = cfg.CONF + super(L3WithFWaaS, self).__init__(host=self.conf.host, conf=self.conf) diff --git a/neutron_fwaas/services/firewall/agents/varmour/varmour_router.py b/neutron_fwaas/services/firewall/agents/varmour/varmour_router.py index 008be8480..ffef0a09e 100644 --- a/neutron_fwaas/services/firewall/agents/varmour/varmour_router.py +++ b/neutron_fwaas/services/firewall/agents/varmour/varmour_router.py @@ -21,13 +21,13 @@ eventlet.monkey_patch() import netaddr from neutron.agent.common import config from neutron.agent.l3 import agent +from neutron.agent.l3 import config as l3_config from neutron.agent.l3 import ha from neutron.agent.l3 import router_info from neutron.agent.linux import external_process from neutron.agent.linux import interface from neutron.agent.linux import ip_lib from neutron.common import config as common_config -from neutron.conf.agent.l3 import config as l3_config from neutron import service as neutron_service from neutron_lib import constants as l3_constants from oslo_config import cfg diff --git a/neutron_fwaas/services/firewall/drivers/fwaas_base_v2.py b/neutron_fwaas/services/firewall/drivers/fwaas_base_v2.py new file mode 100644 index 000000000..ee60d7e89 --- /dev/null +++ b/neutron_fwaas/services/firewall/drivers/fwaas_base_v2.py @@ -0,0 +1,96 @@ +# Copyright (c) 2016 +# 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. + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +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. Argument agent_mode indicates the l3 agent in DVR or + DVR_SNAT or LEGACY mode. + """ + @abc.abstractmethod + def create_firewall_group(self, agent_mode, 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_group(self, agent_mode, apply_list, firewall): + """Delete firewall. + + Removes all policies created by this instance and frees up + all the resources. + """ + pass + + @abc.abstractmethod + def update_firewall_group(self, agent_mode, 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, agent_mode, apply_list, firewall): + """Apply the default policy on all trusted interfaces. + + Remove current policy and apply the default policy on all trusted + interfaces. + """ + pass diff --git a/neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py b/neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py new file mode 100644 index 000000000..cf6f7680e --- /dev/null +++ b/neutron_fwaas/services/firewall/drivers/linux/iptables_fwaas_v2.py @@ -0,0 +1,456 @@ +# Copyright (c) 2016 +# 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. + +from neutron.agent.linux import iptables_manager +from neutron.agent.linux import utils as linux_utils +from oslo_log import log as logging + +from neutron_fwaas._i18n import _LE +from neutron_fwaas.extensions import firewall as fw_ext +from neutron_fwaas.services.firewall.drivers import fwaas_base_v2 + +LOG = logging.getLogger(__name__) +FWAAS_DRIVER_NAME = 'Fwaas iptables driver' +FWAAS_DEFAULT_CHAIN = 'fwaas-default-policy' + +FWAAS_TO_IPTABLE_ACTION_MAP = {'allow': 'ACCEPT', + 'deny': 'DROP', + 'reject': 'REJECT'} +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'} + +INTERNAL_DEV_PREFIX = 'qr-' +SNAT_INT_DEV_PREFIX = 'sg-' +ROUTER_2_FIP_DEV_PREFIX = 'rfp-' + +MAX_INTF_NAME_LEN = 14 + + +class IptablesFwaasDriver(fwaas_base_v2.FwaasDriverBase): + """IPTables driver for Firewall As A Service.""" + + def __init__(self): + LOG.debug("Initializing fwaas iptables driver") + self.pre_firewall = None + + def _get_intf_name(self, if_prefix, port_id): + _name = "%s%s" % (if_prefix, port_id) + return _name[:MAX_INTF_NAME_LEN] + + def create_firewall_group(self, agent_mode, 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(agent_mode, apply_list, firewall) + self._remove_conntrack_new_firewall(agent_mode, + apply_list, firewall) + self.pre_firewall = dict(firewall) + else: + self.apply_default_policy(agent_mode, apply_list, firewall) + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception(_LE("Failed to create firewall: %s"), firewall['id']) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def _get_ipt_mgrs_with_if_prefix(self, agent_mode, router_info): + """Gets the iptables manager along with the if prefix to apply rules. + + With DVR we can have differing namespaces depending on which agent + (on Network or Compute node). Also, there is an associated i/f for + each namespace. The iptables on the relevant namespace and matching + i/f are provided. On the Network node we could have both the snat + namespace and a fip so this is provided back as a list - so in that + scenario rules can be applied on both. + """ + if not router_info.router.get('distributed'): + return [{'ipt': router_info.iptables_manager, + 'if_prefix': INTERNAL_DEV_PREFIX}] + ipt_mgrs = [] + # TODO(sridar): refactor to get strings to a common location. + if agent_mode == 'dvr_snat': + if router_info.snat_iptables_manager: + ipt_mgrs.append({'ipt': router_info.snat_iptables_manager, + 'if_prefix': SNAT_INT_DEV_PREFIX}) + if router_info.dist_fip_count: + # handle the fip case on n/w or compute node. + ipt_mgrs.append({'ipt': router_info.iptables_manager, + 'if_prefix': ROUTER_2_FIP_DEV_PREFIX}) + return ipt_mgrs + + def delete_firewall_group(self, agent_mode, 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, router_fw_ports in apply_list: + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + ipt_mgr = ipt_if_prefix['ipt'] + self._remove_chains(fwid, ipt_mgr) + self._remove_default_chains(ipt_mgr) + # apply the changes immediately (no defer in firewall path) + ipt_mgr.defer_apply_off() + self.pre_firewall = None + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception(_LE("Failed to delete firewall: %s"), fwid) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def update_firewall_group(self, agent_mode, 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']: + if self.pre_firewall: + self._remove_conntrack_updated_firewall(agent_mode, + apply_list, self.pre_firewall, firewall) + else: + self._remove_conntrack_new_firewall(agent_mode, + apply_list, firewall) + self._setup_firewall(agent_mode, apply_list, firewall) + else: + self.apply_default_policy(agent_mode, apply_list, firewall) + self.pre_firewall = dict(firewall) + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception(_LE("Failed to update firewall: %s"), firewall['id']) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def apply_default_policy(self, agent_mode, 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, router_fw_ports in apply_list: + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + # the following only updates local memory; no hole in FW + ipt_mgr = ipt_if_prefix['ipt'] + 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_if_prefix, + router_fw_ports) + + # apply the changes immediately (no defer in firewall path) + ipt_mgr.defer_apply_off() + except (LookupError, RuntimeError): + # catch known library exceptions and raise Fwaas generic exception + LOG.exception( + _LE("Failed to apply default policy on firewall: %s"), fwid) + raise fw_ext.FirewallInternalDriverError(driver=FWAAS_DRIVER_NAME) + + def _setup_firewall(self, agent_mode, apply_list, firewall): + fwid = firewall['id'] + for router_info, router_fw_ports in apply_list: + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + ipt_mgr = ipt_if_prefix['ipt'] + # 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_if_prefix, router_fw_ports) + + # apply the changes immediately (no defer in firewall path) + ipt_mgr.defer_apply_off() + + 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_if_prefix, router_fw_ports): + """Create Fwaas chain using the rules in the policy + """ + egress_rule_list = firewall['egress_rule_list'] + ingress_rule_list = firewall['ingress_rule_list'] + fwid = firewall['id'] + ipt_mgr = ipt_if_prefix['ipt'] + + # 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 ingress_rule_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) + table.add_rule(ichain_name, iptbl_rule) + + for rule in egress_rule_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'] + ochain_name = self._get_chain_name(fwid, ver, EGRESS_DIRECTION) + table.add_rule(ochain_name, iptbl_rule) + + self._enable_policy_chain(fwid, ipt_if_prefix, router_fw_ports) + + def _find_changed_rules(self, pre_firewall, firewall): + """Find the rules changed between the current firewall + and the updating rule + """ + changed_rules = [] + for fw_rule_list in ['egress_rule_list', 'ingress_rule_list']: + pre_fw_rules = pre_firewall[fw_rule_list] + fw_rules = firewall[fw_rule_list] + for pre_fw_rule in pre_fw_rules: + for fw_rule in fw_rules: + if (pre_fw_rule.get('id') == fw_rule.get('id') and + pre_fw_rule != fw_rule): + changed_rules.append(pre_fw_rule) + changed_rules.append(fw_rule) + return changed_rules + + def _find_removed_rules(self, pre_firewall, firewall): + removed_rules = [] + for fw_rule_list in ['egress_rule_list', 'ingress_rule_list']: + pre_fw_rules = pre_firewall[fw_rule_list] + fw_rules = firewall[fw_rule_list] + fw_rule_ids = [fw_rule['id'] for fw_rule in fw_rules] + removed_rules.extend([pre_fw_rule for pre_fw_rule in pre_fw_rules + if pre_fw_rule['id'] not in fw_rule_ids]) + return removed_rules + + def _find_new_rules(self, pre_firewall, firewall): + return self._find_removed_rules(firewall, pre_firewall) + + def _get_conntrack_cmd_from_rule(self, ipt_mgr, rule=None): + prefixcmd = ['ip', 'netns', 'exec'] + [ipt_mgr.namespace] + cmd = ['conntrack', '-D'] + if rule: + conntrack_filter = self._get_conntrack_filter_from_rule(rule) + exec_cmd = prefixcmd + cmd + conntrack_filter + else: + exec_cmd = prefixcmd + cmd + return exec_cmd + + def _remove_conntrack_by_cmd(self, cmd): + if cmd: + try: + linux_utils.execute(cmd, run_as_root=True, + check_exit_code=True, + extra_ok_codes=[1]) + except RuntimeError: + LOG.exception( + _LE("Failed execute conntrack command %s"), str(cmd)) + + def _remove_conntrack_new_firewall(self, agent_mode, apply_list, firewall): + """Remove conntrack when create new firewall""" + routers_list = list(set([apply_info[0] for apply_info in apply_list])) + for router_info in routers_list: + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + ipt_mgr = ipt_if_prefix['ipt'] + cmd = self._get_conntrack_cmd_from_rule(ipt_mgr) + self._remove_conntrack_by_cmd(cmd) + + def _remove_conntrack_updated_firewall(self, agent_mode, + apply_list, pre_firewall, firewall): + """Remove conntrack when updated firewall""" + routers_list = list(set([apply_info[0] for apply_info in apply_list])) + for router_info in routers_list: + ipt_if_prefix_list = self._get_ipt_mgrs_with_if_prefix( + agent_mode, router_info) + for ipt_if_prefix in ipt_if_prefix_list: + ipt_mgr = ipt_if_prefix['ipt'] + ch_rules = self._find_changed_rules(pre_firewall, + firewall) + i_rules = self._find_new_rules(pre_firewall, firewall) + r_rules = self._find_removed_rules(pre_firewall, firewall) + removed_conntrack_rules_list = ch_rules + i_rules + r_rules + for rule in removed_conntrack_rules_list: + cmd = self._get_conntrack_cmd_from_rule(ipt_mgr, rule) + self._remove_conntrack_by_cmd(cmd) + + def _get_conntrack_filter_from_rule(self, rule): + """Get conntrack filter from rule. + The key for get conntrack filter is protocol, destination_port + and source_port. If we want to take more keys, add to the list. + """ + conntrack_filter = [] + keys = [['-p', 'protocol'], ['-f', 'ip_version'], + ['--dport', 'destination_port'], ['--sport', 'source_port']] + for key in keys: + if rule.get(key[1]): + if key[1] == 'ip_version': + conntrack_filter.append(key[0]) + conntrack_filter.append('ipv' + str(rule.get(key[1]))) + else: + conntrack_filter.append(key[0]) + conntrack_filter.append(rule.get(key[1])) + return conntrack_filter + + 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'].remove_chain(chain_name) + else: + ipt_mgr.ipv6['filter'].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_if_prefix, router_fw_ports): + bname = iptables_manager.binary_name + ipt_mgr = ipt_if_prefix['ipt'] + if_prefix = ipt_if_prefix['if_prefix'] + + for (ver, tbl) in [(IPV4, ipt_mgr.ipv4['filter']), + (IPV6, ipt_mgr.ipv6['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: + for router_fw_port in router_fw_ports: + intf_name = self._get_intf_name(if_prefix, + router_fw_port) + jump_rule = ['%s %s -j %s-%s' % ( + IPTABLES_DIR[direction], intf_name, + 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) + for router_fw_port in router_fw_ports: + intf_name = self._get_intf_name(if_prefix, + router_fw_port) + jump_rule = ['-o %s -j %s-%s' % (intf_name, 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) + for router_fw_port in router_fw_ports: + intf_name = self._get_intf_name(if_prefix, + router_fw_port) + jump_rule = ['-i %s -j %s-%s' % (intf_name, 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): + action = FWAAS_TO_IPTABLE_ACTION_MAP[rule.get('action')] + + 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(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_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py index 54eedc6de..50e2fa350 100644 --- a/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py +++ b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent.py @@ -17,10 +17,10 @@ import uuid import mock from oslo_config import cfg +from neutron.agent.l3 import config as l3_config from neutron.agent.l3 import ha from neutron.agent.l3 import router_info from neutron.agent.linux import ip_lib -from neutron.conf.agent.l3 import config as l3_config from neutron.conf import common as base_config from neutron import context from neutron.plugins.common import constants @@ -50,7 +50,7 @@ def _setup_test_agent_class(service_plugins): def __init__(self, conf): self.event_observers = mock.Mock() self.conf = conf - super(FWaasTestAgent, self).__init__('myhost', conf) + super(FWaasTestAgent, self).__init__("myhost", conf) return FWaasTestAgent @@ -64,7 +64,7 @@ class TestFwaasL3AgentRpcCallback(base.BaseTestCase): self.conf.register_opts(l3_config.OPTS) self.conf.register_opts(ha.OPTS) self.conf.register_opts(firewall_agent_api.FWaaSOpts, 'fwaas') - self.api = FWaasAgent('myhost', self.conf) + self.api = FWaasAgent("myhost", self.conf) self.api.fwaas_driver = test_firewall_agent_api.NoopFwaasDriver() self.adminContext = context.get_admin_context() self.router_id = str(uuid.uuid4()) diff --git a/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py new file mode 100644 index 000000000..8b11b3271 --- /dev/null +++ b/neutron_fwaas/tests/unit/services/firewall/agents/l3reference/test_firewall_l3_agent_v2.py @@ -0,0 +1,308 @@ +# Copyright (c) 2016 +# 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. + +import uuid + +import mock +from oslo_config import cfg + +from neutron.agent.l3 import config as l3_config +from neutron.agent.l3 import router_info +from neutron.agent.linux import ip_lib +from neutron import context +from neutron.plugins.common import constants + +from neutron_fwaas.services.firewall.agents import firewall_agent_api +from neutron_fwaas.services.firewall.agents.l3reference \ + import firewall_l3_agent_v2 +from neutron_fwaas.tests import base +from neutron_fwaas.tests.unit.services.firewall.agents \ + import test_firewall_agent_api + + +class FWaasHelper(object): + def __init__(self, host): + pass + + +class FWaasAgent(firewall_l3_agent_v2.FWaaSL3AgentRpcCallback, FWaasHelper): + neutron_service_plugins = [] + + +def _setup_test_agent_class(service_plugins): + class FWaasTestAgent(firewall_l3_agent_v2.FWaaSL3AgentRpcCallback, + FWaasHelper): + neutron_service_plugins = service_plugins + + def __init__(self, conf): + self.event_observers = mock.Mock() + self.conf = conf + super(FWaasTestAgent, self).__init__('myhost', conf) + + return FWaasTestAgent + + +class TestFwaasL3AgentRpcCallback(base.BaseTestCase): + def setUp(self): + super(TestFwaasL3AgentRpcCallback, self).setUp() + + self.conf = cfg.ConfigOpts() + self.conf.register_opts(l3_config.OPTS) + self.conf.register_opts(firewall_agent_api.FWaaSOpts, 'fwaas') + self.api = FWaasAgent('myhost', self.conf) + self.api.fwaas_driver = test_firewall_agent_api.NoopFwaasDriverV2() + self.adminContext = context.get_admin_context() + self.context = mock.sentinel.context + self.router_id = str(uuid.uuid4()) + self.agent_conf = mock.Mock() + self.ri_kwargs = {'router': {'id': self.router_id, + 'project_id': str(uuid.uuid4())}, + 'agent_conf': self.agent_conf, + 'interface_driver': mock.ANY, + 'use_ipv6': mock.ANY + } + + def test_fw_config_match(self): + test_agent_class = _setup_test_agent_class([constants.FIREWALL]) + cfg.CONF.set_override('enabled', True, 'fwaas') + with mock.patch('oslo_utils.importutils.import_object'): + test_agent_class(cfg.CONF) + + def test_fw_config_mismatch_plugin_enabled_agent_disabled(self): + self.skipTest('this is broken') + test_agent_class = _setup_test_agent_class([constants.FIREWALL]) + cfg.CONF.set_override('enabled', False, 'fwaas') + self.assertRaises(SystemExit, test_agent_class, cfg.CONF) + + def test_fw_plugin_list_unavailable(self): + test_agent_class = _setup_test_agent_class(None) + cfg.CONF.set_override('enabled', False, 'fwaas') + with mock.patch('oslo_utils.importutils.import_object'): + test_agent_class(cfg.CONF) + + def test_create_firewall_group(self): + firewall_group = {'id': 0, 'project_id': 1, + 'admin_state_up': True, + 'add-port-ids': [1, 2]} + self.api.plugin_rpc = mock.Mock() + with mock.patch.object(self.api, '_get_firewall_group_ports' + ) as mock_get_firewall_group_ports, \ + mock.patch.object(self.api, '_get_in_ns_ports' + ) as mock_get_in_ns_ports, \ + mock.patch.object(self.api.fwaas_driver, + 'create_firewall_group' + ) as mock_driver_create_firewall_group, \ + mock.patch.object(self.api.fwplugin_rpc, + 'set_firewall_group_status' + ) as mock_set_firewall_group_status: + + mock_driver_create_firewall_group.return_value = True + + self.api.create_firewall_group(self.context, firewall_group, + host='host') + + mock_get_firewall_group_ports.assert_called_once_with(self.context, + firewall_group) + mock_get_in_ns_ports.assert_called + assert mock_get_in_ns_ports + mock_set_firewall_group_status.assert_called_once_with( + self.context, firewall_group['id'], 'ACTIVE') + + def test_update_firewall_group_with_ports_added_and_deleted(self): + firewall_group = {'id': 0, 'project_id': 1, + 'admin_state_up': True, + 'add-port-ids': [1, 2], + 'del-port-ids': [3, 4], + 'router_ids': [], + 'last-port': False} + + self.api.plugin_rpc = mock.Mock() + with mock.patch.object(self.api, '_get_firewall_group_ports' + ) as mock_get_firewall_group_ports, \ + mock.patch.object(self.api, '_get_in_ns_ports' + ) as mock_get_in_ns_ports, \ + mock.patch.object(self.api.fwaas_driver, + 'update_firewall_group' + ) as mock_driver_update_firewall_group, \ + mock.patch.object(self.api.fwaas_driver, + 'delete_firewall_group' + ) as mock_driver_delete_firewall_group, \ + mock.patch.object(self.api.fwplugin_rpc, + 'set_firewall_group_status' + ) as mock_set_firewall_group_status: + + mock_driver_delete_firewall_group.return_value = True + mock_driver_update_firewall_group.return_value = True + + calls = [mock.call(self.context, firewall_group, to_delete=True, + require_new_plugin=True), + mock.call(self.context, firewall_group)] + + self.api.update_firewall_group(self.context, firewall_group, + host='host') + + self.assertEqual(mock_get_firewall_group_ports.call_args_list, + calls) + mock_get_in_ns_ports.assert_called + mock_set_firewall_group_status.assert_called_once_with( + self.context, firewall_group['id'], 'ACTIVE') + + def test_update_firewall_group_with_ports_added_and_admin_state_down(self): + firewall_group = {'id': 0, 'project_id': 1, + 'admin_state_up': False, + 'add-port-ids': [1, 2], + 'del-port-ids': [], + 'router_ids': [], + 'last-port': False} + + self.api.plugin_rpc = mock.Mock() + with mock.patch.object(self.api, '_get_firewall_group_ports' + ) as mock_get_firewall_group_ports, \ + mock.patch.object(self.api, '_get_in_ns_ports' + ) as mock_get_in_ns_ports, \ + mock.patch.object(self.api.fwaas_driver, + 'update_firewall_group' + ) as mock_driver_update_firewall_group, \ + mock.patch.object(self.api.fwplugin_rpc, + 'set_firewall_group_status' + ) as mock_set_firewall_group_status: + + mock_driver_update_firewall_group.return_value = True + + self.api.update_firewall_group(self.context, firewall_group, + host='host') + + mock_get_firewall_group_ports.assert_called + mock_get_in_ns_ports.assert_called + mock_set_firewall_group_status.assert_called_once_with( + self.context, firewall_group['id'], 'DOWN') + + def test_update_firewall_group_with_all_ports_deleted(self): + firewall_group = {'id': 0, 'project_id': 1, + 'admin_state_up': True, + 'add-port-ids': [], + 'del-port-ids': [3, 4], + 'last-port': True} + + self.api.plugin_rpc = mock.Mock() + with mock.patch.object(self.api, '_get_firewall_group_ports' + ) as mock_get_firewall_group_ports, \ + mock.patch.object(self.api, '_get_in_ns_ports' + ) as mock_get_in_ns_ports, \ + mock.patch.object(self.api.fwaas_driver, + 'delete_firewall_group' + ) as mock_driver_delete_firewall_group, \ + mock.patch.object(self.api.fwplugin_rpc, + 'set_firewall_group_status' + ) as mock_set_firewall_group_status: + + mock_driver_delete_firewall_group.return_value = True + + self.api.update_firewall_group(self.context, firewall_group, + host='host') + + mock_get_firewall_group_ports.assert_called_once_with(self.context, + firewall_group, require_new_plugin=True, to_delete=True) + mock_get_in_ns_ports.assert_called + mock_set_firewall_group_status.assert_called_once_with( + self.context, firewall_group['id'], 'INACTIVE') + + def test_update_firewall_group_with_no_ports_added_or_deleted(self): + firewall_group = {'id': 0, 'project_id': 1, + 'admin_state_up': True, + 'add-port-ids': [], + 'del-port-ids': [], + 'router_ids': []} + + self.api.plugin_rpc = mock.Mock() + with mock.patch.object(self.api.fwaas_driver, 'update_firewall_group' + ) as mock_driver_update_firewall_group, \ + mock.patch.object(self.api.fwplugin_rpc, + 'set_firewall_group_status' + ) as mock_set_firewall_group_status: + + mock_driver_update_firewall_group.return_value = True + + self.api.update_firewall_group(self.context, firewall_group, + host='host') + mock_set_firewall_group_status.assert_called_once_with( + self.context, firewall_group['id'], 'INACTIVE') + + def test_delete_firewall_group(self): + firewall_group = {'id': 0, 'project_id': 1, + 'admin_state_up': True, + 'add-port-ids': [], + 'del-port-ids': [3, 4], + 'last-port': False} + + self.api.plugin_rpc = mock.Mock() + with mock.patch.object(self.api, '_get_firewall_group_ports' + ) as mock_get_firewall_group_ports, \ + mock.patch.object(self.api, '_get_in_ns_ports' + ) as mock_get_in_ns_ports, \ + mock.patch.object(self.api.fwaas_driver, + 'delete_firewall_group' + ) as mock_driver_delete_firewall_group, \ + mock.patch.object(self.api.fwplugin_rpc, + 'firewall_group_deleted' + ) as mock_firewall_group_deleted: + + mock_driver_delete_firewall_group.return_value = True + + self.api.delete_firewall_group(self.context, firewall_group, + host='host') + + mock_get_firewall_group_ports.assert_called_once_with( + self.context, firewall_group, to_delete=True) + mock_get_in_ns_ports.assert_called + mock_firewall_group_deleted.assert_called_once_with(self.context, + firewall_group['id']) + + def _prepare_router_data(self): + return router_info.RouterInfo(self.router_id, + **self.ri_kwargs) + + def test_get_in_ns_ports_for_non_ns_fw(self): + port_ids = [1, 2] + ports = [{'id': pid} for pid in port_ids] + ri = self._prepare_router_data() + ri.internal_ports = ports + self.api.router_info = {ri.router_id: ri} + fw_port_ids = port_ids + + with mock.patch.object(ip_lib.IPWrapper, + 'get_namespaces') as mock_get_namespaces: + + mock_get_namespaces.return_value = [] + ports_for_fw_list = self.api._get_in_ns_ports(fw_port_ids) + + mock_get_namespaces.assert_called_once_with() + self.assertFalse(ports_for_fw_list) + + def test_get_in_ns_ports_for_fw(self): + port_ids = [1, 2] + ports = [{'id': pid} for pid in port_ids] + ri = self._prepare_router_data() + ri.internal_ports = ports + self.api.router_info = {} + self.api.router_info[ri.router_id] = ri + fw_port_ids = port_ids + ports_for_fw_expected = [(ri, port_ids)] + + with mock.patch.object(ip_lib.IPWrapper, + 'get_namespaces') as mock_get_namespaces: + mock_get_namespaces.return_value = [ri.ns_name] + ports_for_fw_actual = self.api._get_in_ns_ports(fw_port_ids) + self.assertEqual(ports_for_fw_expected, ports_for_fw_actual) diff --git a/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py b/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py index 9ffeb043a..fff45de9d 100644 --- a/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py +++ b/neutron_fwaas/tests/unit/services/firewall/agents/test_firewall_agent_api.py @@ -16,14 +16,15 @@ import mock from neutron_fwaas.services.firewall.agents import firewall_agent_api as api -from neutron_fwaas.services.firewall.drivers import fwaas_base as base_driver +from neutron_fwaas.services.firewall.drivers import fwaas_base +from neutron_fwaas.services.firewall.drivers import fwaas_base_v2 from neutron_fwaas.tests import base -class NoopFwaasDriver(base_driver.FwaasDriverBase): +class NoopFwaasDriver(fwaas_base.FwaasDriverBase): """Noop Fwaas Driver. - Firewall driver which does nothing. + v1 firewall driver which does nothing. This driver is for disabling Fwaas functionality. """ @@ -40,6 +41,26 @@ class NoopFwaasDriver(base_driver.FwaasDriverBase): pass +class NoopFwaasDriverV2(fwaas_base_v2.FwaasDriverBase): + """Noop Fwaas Driver. + + v2 firewall driver which does nothing. + This driver is for disabling Fwaas functionality. + """ + + def create_firewall_group(self, agent_mode, apply_list, firewall): + pass + + def delete_firewall_group(self, agent_mode, apply_list, firewall): + pass + + def update_firewall_group(self, agent_mode, apply_list, firewall): + pass + + def apply_default_policy(self, agent_mode, apply_list, firewall): + pass + + class TestFWaaSAgentApi(base.BaseTestCase): def setUp(self): super(TestFWaaSAgentApi, self).setUp() diff --git a/neutron_fwaas/tests/unit/services/firewall/agents/varmour/test_varmour_router.py b/neutron_fwaas/tests/unit/services/firewall/agents/varmour/test_varmour_router.py index 7dcb3ce1e..1ca444d35 100644 --- a/neutron_fwaas/tests/unit/services/firewall/agents/varmour/test_varmour_router.py +++ b/neutron_fwaas/tests/unit/services/firewall/agents/varmour/test_varmour_router.py @@ -16,12 +16,12 @@ import mock + from neutron.agent.common import config as agent_config +from neutron.agent.l3 import config as l3_config from neutron.agent.l3 import ha from neutron.agent.l3 import router_info from neutron.agent.linux import interface -from neutron.conf.agent.l3 import config as l3_config -from neutron.conf import common as base_config from neutron_fwaas.services.firewall.agents.varmour import varmour_router from neutron_fwaas.tests import base from neutron_lib import constants as l3_constants @@ -39,7 +39,6 @@ class TestVarmourRouter(base.BaseTestCase): self.skipTest('this is broken') super(TestVarmourRouter, self).setUp() self.conf = agent_config.setup_conf() - self.conf.register_opts(base_config.core_opts) self.conf.register_opts(l3_config.OPTS) self.conf.register_opts(ha.OPTS) agent_config.register_process_monitor_opts(self.conf) diff --git a/neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py b/neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py new file mode 100644 index 000000000..5f0e8e23f --- /dev/null +++ b/neutron_fwaas/tests/unit/services/firewall/drivers/linux/test_iptables_fwaas_v2.py @@ -0,0 +1,386 @@ +# Copyright (c) 2016 +# 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. + +import copy +import mock +from oslo_config import cfg + +from neutron.agent.common import config as a_cfg +from neutron.tests import base +from neutron.tests.unit.api.v2 import test_base as test_api_v2 +import neutron_fwaas.services.firewall.drivers.linux.iptables_fwaas_v2 as fwaas + + +_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' +FAKE_PORT_IDS = ('1_fake-port-uuid', '2_fake-port-uuid') +FW_LEGACY = 'legacy' +MAX_INTF_NAME_LEN = 14 + + +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.iptables_cls_p = mock.patch( + 'neutron.agent.linux.iptables_manager.IptablesManager') + self.iptables_cls_p.start() + self.firewall = fwaas.IptablesFwaasDriver() + + def _fake_rules_v4(self, fwid, apply_list): + rule_list = [] + rule1 = {'enabled': True, + 'action': 'allow', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '80', + 'source_ip_address': '10.24.4.2', + 'id': 'fake-fw-rule1'} + rule2 = {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '22', + 'id': 'fake-fw-rule2'} + rule3 = {'enabled': True, + 'action': 'reject', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '23', + 'id': 'fake-fw-rule3'} + ingress_chain = ('iv4%s' % fwid)[:11] + egress_chain = ('ov4%s' % fwid)[:11] + for router_info_inst, port_ids in apply_list: + v4filter_inst = router_info_inst.iptables_manager.ipv4['filter'] + v4filter_inst.chains.append(ingress_chain) + v4filter_inst.chains.append(egress_chain) + rule_list.append(rule1) + rule_list.append(rule2) + rule_list.append(rule3) + 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', + 'egress_rule_list': rule_list, + 'ingress_rule_list': rule_list} + return fw_inst + + def _fake_firewall(self, rule_list): + _rule_list = copy.deepcopy(rule_list) + for rule in _rule_list: + rule['position'] = str(_rule_list.index(rule)) + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': True, + 'tenant_id': 'tenant-uuid', + 'egress_rule_list': _rule_list, + 'ingress_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', + 'egress_rule_list': rule_list, + 'ingress_rule_list': rule_list} + return fw_inst + + def _fake_apply_list(self, router_count=1, distributed=False, + distributed_mode=None): + apply_list = [] + while router_count > 0: + iptables_inst = mock.Mock() + if distributed: + router_inst = {'distributed': distributed} + else: + router_inst = {} + v4filter_inst = mock.Mock() + v6filter_inst = mock.Mock() + v4filter_inst.chains = [] + v6filter_inst.chains = [] + iptables_inst.ipv4 = {'filter': v4filter_inst} + iptables_inst.ipv6 = {'filter': v6filter_inst} + router_info_inst = mock.Mock() + router_info_inst.iptables_manager = iptables_inst + router_info_inst.snat_iptables_manager = iptables_inst + if distributed_mode == 'dvr': + router_info_inst.dist_fip_count = 1 + router_info_inst.router = router_inst + apply_list.append((router_info_inst, FAKE_PORT_IDS)) + router_count -= 1 + return apply_list + + def _get_intf_name(self, if_prefix, port_id): + _name = "%s%s" % (if_prefix, port_id) + return _name[:MAX_INTF_NAME_LEN] + + def _setup_firewall_with_rules(self, func, router_count=1, + distributed=False, distributed_mode=None): + apply_list = self._fake_apply_list(router_count=router_count, + distributed=distributed, distributed_mode=distributed_mode) + rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list) + firewall = self._fake_firewall(rule_list) + if distributed: + if distributed_mode == 'dvr_snat': + if_prefix = 'sg-' + if distributed_mode == 'dvr': + if_prefix = 'rfp-' + else: + if_prefix = 'qr-' + distributed_mode = 'legacy' + func(distributed_mode, 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' + rule3 = '-p tcp --dport 23 -j REJECT' + 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]) + for router_info_inst, port_ids in apply_list: + v4filter_inst = router_info_inst.iptables_manager.ipv4['filter'] + calls = [mock.call.remove_chain('iv4fake-fw-uuid'), + mock.call.remove_chain('ov4fake-fw-uuid'), + mock.call.remove_chain('fwaas-default-policy'), + mock.call.add_chain('fwaas-default-policy'), + mock.call.add_rule('fwaas-default-policy', '-j DROP'), + mock.call.add_chain(ingress_chain), + mock.call.add_rule(ingress_chain, invalid_rule), + mock.call.add_rule(ingress_chain, est_rule), + mock.call.add_chain(egress_chain), + mock.call.add_rule(egress_chain, invalid_rule), + mock.call.add_rule(egress_chain, est_rule), + mock.call.add_rule(ingress_chain, rule1), + mock.call.add_rule(ingress_chain, rule2), + mock.call.add_rule(ingress_chain, rule3), + mock.call.add_rule(egress_chain, rule1), + mock.call.add_rule(egress_chain, rule2), + mock.call.add_rule(egress_chain, rule3)] + + intf_name = self._get_intf_name(if_prefix, FAKE_PORT_IDS[-1]) + calls.append(mock.call.add_rule('FORWARD', + '-o %s -j %s' % (intf_name, ipt_mgr_ichain))) + calls.append(mock.call.add_rule('FORWARD', + '-i %s -j %s' % (intf_name, ipt_mgr_echain))) + + for direction in ['o', 'i']: + for port_id in FAKE_PORT_IDS: + intf_name = self._get_intf_name(if_prefix, port_id) + calls.append(mock.call.add_rule('FORWARD', + '-%s %s -j %s-fwaas-defau' % (direction, + intf_name, bname))) + v4filter_inst.assert_has_calls(calls) + + def test_create_firewall_group_no_rules(self): + apply_list = self._fake_apply_list() + first_ri = apply_list[0][0] + firewall = self._fake_firewall_no_rule() + self.firewall.create_firewall_group('legacy', apply_list, firewall) + invalid_rule = '-m state --state INVALID -j DROP' + est_rule = '-m state --state ESTABLISHED,RELATED -j ACCEPT' + bname = fwaas.iptables_manager.binary_name + for ip_version in (4, 6): + ingress_chain = ('iv%s%s' % (ip_version, firewall['id'])) + egress_chain = ('ov%s%s' % (ip_version, firewall['id'])) + calls = [mock.call.remove_chain( + 'iv%sfake-fw-uuid' % ip_version), + mock.call.remove_chain( + 'ov%sfake-fw-uuid' % ip_version), + mock.call.remove_chain('fwaas-default-policy'), + mock.call.add_chain('fwaas-default-policy'), + mock.call.add_rule('fwaas-default-policy', '-j DROP'), + mock.call.add_chain(ingress_chain), + mock.call.add_rule(ingress_chain, invalid_rule), + mock.call.add_rule(ingress_chain, est_rule), + mock.call.add_chain(egress_chain), + mock.call.add_rule(egress_chain, invalid_rule), + mock.call.add_rule(egress_chain, est_rule)] + for port_id in FAKE_PORT_IDS: + for direction in ['o', 'i']: + mock.call.add_rule('FORWARD', + '-%s qr-%s -j %s-fwaas-defau' % (port_id, + direction, + bname)) + if ip_version == 4: + v4filter_inst = first_ri.iptables_manager.ipv4['filter'] + v4filter_inst.assert_has_calls(calls) + else: + v6filter_inst = first_ri.iptables_manager.ipv6['filter'] + v6filter_inst.assert_has_calls(calls) + + def test_create_firewall_group_with_rules(self): + self._setup_firewall_with_rules(self.firewall.create_firewall_group) + + def test_create_firewall_group_with_rules_without_distributed_attr(self): + self._setup_firewall_with_rules(self.firewall.create_firewall_group, + distributed=None) + + def test_create_firewall_group_with_rules_two_routers(self): + self._setup_firewall_with_rules(self.firewall.create_firewall_group, + router_count=2) + + def test_update_firewall_group_with_rules(self): + self._setup_firewall_with_rules(self.firewall.update_firewall_group) + + def test_update_firewall_group_with_rules_without_distributed_attr(self): + self._setup_firewall_with_rules(self.firewall.update_firewall_group, + distributed=None) + + def _test_delete_firewall_group(self, distributed=False): + apply_list = self._fake_apply_list(distributed=distributed) + first_ri = apply_list[0][0] + firewall = self._fake_firewall_no_rule() + self.firewall.delete_firewall_group('legacy', apply_list, firewall) + ingress_chain = 'iv4%s' % firewall['id'] + egress_chain = 'ov4%s' % firewall['id'] + calls = [mock.call.remove_chain(ingress_chain), + mock.call.remove_chain(egress_chain), + mock.call.remove_chain('fwaas-default-policy')] + first_ri.iptables_manager.ipv4['filter'].assert_has_calls(calls) + + def test_delete_firewall_group(self): + self._test_delete_firewall_group() + + def test_delete_firewall_group_without_distributed_attr(self): + self._test_delete_firewall_group(distributed=None) + + def test_create_firewall_group_with_admin_down(self): + apply_list = self._fake_apply_list() + first_ri = apply_list[0][0] + rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list) + firewall = self._fake_firewall_with_admin_down(rule_list) + self.firewall.create_firewall_group('legacy', apply_list, firewall) + calls = [mock.call.remove_chain('iv4fake-fw-uuid'), + mock.call.remove_chain('ov4fake-fw-uuid'), + mock.call.remove_chain('fwaas-default-policy'), + mock.call.add_chain('fwaas-default-policy'), + mock.call.add_rule('fwaas-default-policy', '-j DROP')] + first_ri.iptables_manager.ipv4['filter'].assert_has_calls(calls) + + def test_create_firewall_group_with_rules_dvr_snat(self): + self._setup_firewall_with_rules(self.firewall.create_firewall_group, + distributed=True, distributed_mode='dvr_snat') + + def test_update_firewall_group_with_rules_dvr_snat(self): + self._setup_firewall_with_rules(self.firewall.update_firewall_group, + distributed=True, distributed_mode='dvr_snat') + + def test_create_firewall_group_with_rules_dvr(self): + self._setup_firewall_with_rules(self.firewall.create_firewall_group, + distributed=True, distributed_mode='dvr') + + def test_update_firewall_group_with_rules_dvr(self): + self._setup_firewall_with_rules(self.firewall.update_firewall_group, + distributed=True, distributed_mode='dvr') + + def test_remove_conntrack_new_firewall(self): + apply_list = self._fake_apply_list() + firewall = self._fake_firewall_no_rule() + self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall) + for router_info_inst, port_ids in apply_list: + namespace = router_info_inst.iptables_manager.namespace + cmd = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D'] + calls = [ + mock.call(cmd, run_as_root=True, check_exit_code=True, + extra_ok_codes=[1])] + self.utils_exec.assert_has_calls(calls) + + def test_remove_conntrack_inserted_rule(self): + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list) + firewall = self._fake_firewall(rule_list) + self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall) + self.firewall.pre_firewall = dict(firewall) + insert_rule = {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'protocol': 'icmp', + 'id': 'fake-fw-rule'} + rule_list.insert(2, insert_rule) + firewall = self._fake_firewall(rule_list) + self.firewall.update_firewall_group(FW_LEGACY, apply_list, firewall) + for router_info_inst, port_ids in apply_list: + namespace = router_info_inst.iptables_manager.namespace + cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack', + '-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '23'] + cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack', + '-D', '-p', 'icmp', '-f', 'ipv4'] + calls = [ + mock.call(cmd1, run_as_root=True, check_exit_code=True, + extra_ok_codes=[1]), + mock.call(cmd2, run_as_root=True, check_exit_code=True, + extra_ok_codes=[1])] + self.utils_exec.assert_has_calls(calls) + + def test_remove_conntrack_removed_rule(self): + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list) + firewall = self._fake_firewall(rule_list) + self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall) + self.firewall.pre_firewall = dict(firewall) + remove_rule = rule_list[1] + rule_list.remove(remove_rule) + firewall = self._fake_firewall(rule_list) + self.firewall.update_firewall_group(FW_LEGACY, apply_list, firewall) + for router_info_inst, port_ids in apply_list: + namespace = router_info_inst.iptables_manager.namespace + cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack', + '-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '23'] + cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack', + '-D', '-p', 'tcp', '-f', 'ipv4', '--dport', '22'] + calls = [ + mock.call(cmd1, run_as_root=True, check_exit_code=True, + extra_ok_codes=[1]), + mock.call(cmd2, run_as_root=True, check_exit_code=True, + extra_ok_codes=[1])] + self.utils_exec.assert_has_calls(calls) + + def test_remove_conntrack_changed_rule(self): + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4(FAKE_FW_ID, apply_list) + firewall = self._fake_firewall(rule_list) + self.firewall.create_firewall_group(FW_LEGACY, apply_list, firewall) + income_rule = {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'protocol': 'icmp', + 'id': 'fake-fw-rule2'} + rule_list[1] = income_rule + firewall = self._fake_firewall(rule_list) + self.firewall.update_firewall_group(FW_LEGACY, apply_list, firewall) + for router_info_inst, port_ids in apply_list: + namespace = router_info_inst.iptables_manager.namespace + cmd1 = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D', + '-p', 'tcp', '-f', 'ipv4', '--dport', '22'] + cmd2 = ['ip', 'netns', 'exec', namespace, 'conntrack', '-D', + '-p', 'icmp', '-f', 'ipv4'] + calls = [ + mock.call(cmd1, run_as_root=True, check_exit_code=True, + extra_ok_codes=[1]), + mock.call(cmd2, run_as_root=True, check_exit_code=True, + extra_ok_codes=[1])] + self.utils_exec.assert_has_calls(calls) diff --git a/neutron_fwaas/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py b/neutron_fwaas/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py index 58a8c216b..e98cc3d6b 100644 --- a/neutron_fwaas/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py +++ b/neutron_fwaas/tests/unit/services/firewall/drivers/varmour/test_varmour_fwaas.py @@ -17,11 +17,10 @@ import mock from neutron.agent.common import config as agent_config +from neutron.agent.l3 import config as l3_config from neutron.agent.l3 import ha from neutron.agent.l3 import router_info from neutron.agent.linux import interface -from neutron.conf.agent.l3 import config as l3_config -from neutron.conf import common as base_config from neutron.tests import base from neutron_lib import constants as l3_constants from oslo_utils import uuidutils @@ -39,7 +38,6 @@ class TestBasicRouterOperations(base.BaseTestCase): def setUp(self): super(TestBasicRouterOperations, self).setUp() self.conf = agent_config.setup_conf() - self.conf.register_opts(base_config.core_opts) self.conf.register_opts(l3_config.OPTS) self.conf.register_opts(ha.OPTS) agent_config.register_process_monitor_opts(self.conf)