From a36a1dba74884c0268ee35f729775649d9d5e312 Mon Sep 17 00:00:00 2001 From: Adit Sarfaty Date: Mon, 26 Nov 2018 14:55:49 +0200 Subject: [PATCH] NSX|V: FWaaS-V2 driver This patch adds a driver for FWaaS V2 support in the NSX-V plugin. It supports setting firewall rules per router interface port on the router edge firewall. In addition, the FWaaS TVD driver will now support NSX-V as well. The driver code is a combination of the NSX-V3 FWaas-V2 code, and the old NSX-V FWaaS-V1 code that is being deleted. Change-Id: Iacc7eaff0c70b68156516008cf0277c154edd76b --- doc/source/devstack.rst | 30 +- ...nsxv-fwaas-v2-driver-0b78d5e2c4034b21.yaml | 6 + setup.cfg | 2 +- vmware_nsx/plugins/nsx_v/plugin.py | 117 +++---- vmware_nsx/plugins/nsx_v3/plugin.py | 3 +- .../fwaas/common/fwaas_callbacks_v2.py | 52 +++- .../fwaas/common/fwaas_driver_base.py | 93 ++++++ .../fwaas/nsx_tv/edge_fwaas_driver_v2.py | 27 +- .../fwaas/nsx_v/edge_fwaas_driver_v2.py | 142 +++++++++ .../fwaas/nsx_v/fwaas_callbacks_v2.py | 167 ++++++++++ .../fwaas/nsx_v3/edge_fwaas_driver_base.py | 19 +- .../fwaas/nsx_v3/edge_fwaas_driver_v2.py | 50 --- .../tests/unit/nsx_v/test_fwaas_v2_driver.py | 288 ++++++++++++++++++ 13 files changed, 858 insertions(+), 138 deletions(-) create mode 100644 releasenotes/notes/nsxv-fwaas-v2-driver-0b78d5e2c4034b21.yaml create mode 100644 vmware_nsx/services/fwaas/common/fwaas_driver_base.py create mode 100644 vmware_nsx/services/fwaas/nsx_v/edge_fwaas_driver_v2.py create mode 100644 vmware_nsx/services/fwaas/nsx_v/fwaas_callbacks_v2.py create mode 100644 vmware_nsx/tests/unit/nsx_v/test_fwaas_v2_driver.py diff --git a/doc/source/devstack.rst b/doc/source/devstack.rst index 720cd3dabe..0c57c534cd 100644 --- a/doc/source/devstack.rst +++ b/doc/source/devstack.rst @@ -41,6 +41,25 @@ Optional: Update the nsx qos_peak_bw_multiplier in nsx.ini (default value is 2.0 [NSX] qos_peak_bw_multiplier = +FWaaS (V2) Driver +~~~~~~~~~~~~~~~~~ + +Add neutron-fwaas repo as an external repository and configure following flags in ``local.conf``:: + + [[local|localrc]] + enable_service q-fwaas-v2 + Q_SERVICE_PLUGIN_CLASSES+=,firewall_v2 + + [[post-config|$NEUTRON_CONF]] + [fwaas] + enabled = True + driver = vmware_nsxv_edge_v2 + + [service_providers] + service_provider = FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.agents.agents.FirewallAgentDriver:default + +Note - if devstack fails due to ml2_conf.ini being missing, please copy neutron/plugins/ml2/ml2_conf.ini.sample to /etc/neutron/plugins/ml2/ml2_conf.ini and stack again. + L2GW Driver ~~~~~~~~~~~ @@ -188,8 +207,8 @@ FWaaS (V2) Driver Add neutron-fwaas repo as an external repository and configure following flags in ``local.conf``:: [[local|localrc]] - ENABLED_SERVICES+=,q-fwaas-v2 - Q_SERVICE_PLUGIN_CLASSES+=,neutron_fwaas.services.firewall.fwaas_plugin_v2.FirewallPluginV2 + enable_service q-fwaas-v2 + Q_SERVICE_PLUGIN_CLASSES+=,firewall_v2 [[post-config|$NEUTRON_CONF]] [fwaas] @@ -199,6 +218,8 @@ Add neutron-fwaas repo as an external repository and configure following flags i [service_providers] service_provider = FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.agents.agents.FirewallAgentDriver:default +Note - if devstack fails due to ml2_conf.ini being missing, please copy neutron/plugins/ml2/ml2_conf.ini.sample to /etc/neutron/plugins/ml2/ml2_conf.ini and stack again. + LBaaS v2 Driver ~~~~~~~~~~~~~~~ @@ -297,14 +318,13 @@ Configure the service provider:: [DEFAULT] api_extensions_path = $DEST/neutron-lbaas/neutron_lbaas/extensions - FWaaS (V2) Driver ~~~~~~~~~~~~~~~~~ Add neutron-fwaas repo as an external repository and configure following flags in ``local.conf``:: [[local|localrc]] - ENABLED_SERVICES+=,q-fwaas-v2 + enable_service q-fwaas-v2 Q_SERVICE_PLUGIN_CLASSES+=,vmware_nsxtvd_fwaasv2 [[post-config|$NEUTRON_CONF]] @@ -317,6 +337,8 @@ Add neutron-fwaas repo as an external repository and configure following flags i [service_providers] service_provider = FIREWALL_V2:fwaas_db:neutron_fwaas.services.firewall.service_drivers.agents.agents.FirewallAgentDriver:default +Note - if devstack fails due to ml2_conf.ini being missing, please copy neutron/plugins/ml2/ml2_conf.ini.sample to /etc/neutron/plugins/ml2/ml2_conf.ini and stack again. + L2GW Driver ~~~~~~~~~~~ diff --git a/releasenotes/notes/nsxv-fwaas-v2-driver-0b78d5e2c4034b21.yaml b/releasenotes/notes/nsxv-fwaas-v2-driver-0b78d5e2c4034b21.yaml new file mode 100644 index 0000000000..7028eae5fb --- /dev/null +++ b/releasenotes/notes/nsxv-fwaas-v2-driver-0b78d5e2c4034b21.yaml @@ -0,0 +1,6 @@ +--- +prelude: > + The NSX-V plugin can suppport FWaaS-V2 for setting router edges firewall rules. +features: + - | + The NSX-V plugin can suppport FWaaS-V2 for setting router edges firewall rules. diff --git a/setup.cfg b/setup.cfg index 4607cbb835..0fe5fe63b8 100644 --- a/setup.cfg +++ b/setup.cfg @@ -36,7 +36,7 @@ neutron.core_plugins = vmware_dvs = vmware_nsx.plugin:NsxDvsPlugin vmware_nsxtvd = vmware_nsx.plugin:NsxTVDPlugin firewall_drivers = - vmware_nsxv3_edge = vmware_nsx.services.fwaas.nsx_v3.edge_fwaas_driver_v1:EdgeFwaasV3DriverV1 + vmware_nsxv_edge_v2 = vmware_nsx.services.fwaas.nsx_v.edge_fwaas_driver_v2:EdgeFwaasVDriverV2 vmware_nsxv3_edge_v2 = vmware_nsx.services.fwaas.nsx_v3.edge_fwaas_driver_v2:EdgeFwaasV3DriverV2 vmware_nsxtvd_edge_v2 = vmware_nsx.services.fwaas.nsx_tv.edge_fwaas_driver_v2:EdgeFwaasTVDriverV2 neutron.service_plugins = diff --git a/vmware_nsx/plugins/nsx_v/plugin.py b/vmware_nsx/plugins/nsx_v/plugin.py index 535da30c93..f61303debb 100644 --- a/vmware_nsx/plugins/nsx_v/plugin.py +++ b/vmware_nsx/plugins/nsx_v/plugin.py @@ -144,6 +144,8 @@ from vmware_nsx.plugins.nsx_v.vshield import edge_utils from vmware_nsx.plugins.nsx_v.vshield import securitygroup_utils from vmware_nsx.plugins.nsx_v.vshield import vcns_driver from vmware_nsx.services.flowclassifier.nsx_v import utils as fc_utils +from vmware_nsx.services.fwaas.common import utils as fwaas_utils +from vmware_nsx.services.fwaas.nsx_v import fwaas_callbacks_v2 from vmware_nsx.services.lbaas.nsx_v.implementation import healthmon_mgr from vmware_nsx.services.lbaas.nsx_v.implementation import l7policy_mgr from vmware_nsx.services.lbaas.nsx_v.implementation import l7rule_mgr @@ -329,9 +331,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, # Make sure starting rpc listeners (for QoS and other agents) # will happen only once self.start_rpc_listeners_called = False - - # Init the FWaaS support - self._init_fwaas() + self.fwaas_callbacks = None # Service insertion driver register self._si_handler = fc_utils.NsxvServiceInsertionHandler(self) @@ -412,6 +412,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, self.octavia_listener = octavia_listener.NSXOctaviaListener( **octavia_objects) + # Init the FWaaS support + self._init_fwaas() + self.init_is_complete = True def _get_octavia_objects(self): @@ -497,8 +500,9 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, def _init_fwaas(self): # Bind FWaaS callbacks to the driver - #TODO(asarfaty): waiting for FWaaS v2 support - self.fwaas_callbacks = None + if fwaas_utils.is_fwaas_v2_plugin_enabled(): + LOG.info("NSXv FWaaS v2 plugin enabled") + self.fwaas_callbacks = fwaas_callbacks_v2.NsxvFwaasCallbacksV2() def _create_security_group_container(self): name = "OpenStack Security Group container" @@ -3913,9 +3917,29 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, context, router_id, interface_info) def remove_router_interface(self, context, router_id, interface_info): + # Get the router interface port id + if self.fwaas_callbacks: + port_id = interface_info.get('port_id') + if not port_id: + subnet_id = interface_info['subnet_id'] + subnet = self._get_subnet(context, subnet_id) + rport_qry = context.session.query(models_v2.Port) + ports = rport_qry.filter_by( + device_id=router_id, + device_owner=l3_db.DEVICE_OWNER_ROUTER_INTF, + network_id=subnet['network_id']) + for p in ports: + if p['fixed_ips'][0]['subnet_id'] == subnet_id: + port_id = p['id'] + break + router_driver = self._find_router_driver(context, router_id) - return router_driver.remove_router_interface( + result = router_driver.remove_router_interface( context, router_id, interface_info) + # inform the FWaaS that interface port was removed + if self.fwaas_callbacks and port_id: + self.fwaas_callbacks.delete_port(context, port_id) + return result def _get_floatingips_by_router(self, context, router_id): fip_qry = context.session.query(l3_db_models.FloatingIP) @@ -4015,35 +4039,22 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, router_db is the neutron router structure router_id is the id of the actual router that will be updated on the NSX (in case of distributed router it can be plr or tlr) + + This is just a wrapper of update_router_firewall """ if not router_id: router_id = router_db['id'] - # Add fw rules if FWaaS is enabled - # in case of a distributed-router: - # router['id'] is the id of the neutron router (=tlr) - # and router_id is the plr/tlr (the one that is being updated) - fwaas_rules = None - if (self.fwaas_callbacks and - self.fwaas_callbacks.should_apply_firewall_to_router( - context, router_db, router_id)): - fwaas_rules = self.fwaas_callbacks.get_fwaas_rules_for_router( - context, router_db['id']) + self.update_router_firewall(context, router_id, router_db) - self.update_router_firewall(context, router_id, router_db, - fwaas_rules=fwaas_rules) - - def update_router_firewall(self, context, router_id, router_db, - fwaas_rules=None): + def update_router_firewall(self, context, router_id, router_db): """Recreate all rules in the router edge firewall router_db is the neutron router structure router_id is the id of the actual router that will be updated on the NSX (in case of distributed router it can be plr or tlr) - if fwaas_rules is not none - this router is attached to a firewall """ fw_rules = [] - router_with_firewall = True if fwaas_rules is not None else False edge_id = self._get_edge_id_by_rtr_id(context, router_id) # Add FW rule/s to open subnets firewall flows and static routes @@ -4056,33 +4067,41 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, if self.metadata_proxy_handler: fw_rules += nsx_v_md_proxy.get_router_fw_rules() - # Add FWaaS rules - if router_with_firewall and fwaas_rules: - fw_rules += fwaas_rules + # Add FWaaS rules if FWaaS is enabled + if (self.fwaas_callbacks and + self.fwaas_callbacks.should_apply_firewall_to_router( + context, router_db, router_id)): + fwaas_rules = self.fwaas_callbacks.get_fwaas_rules_for_router( + context, router_db['id'], edge_id) + if fwaas_rules: + fw_rules += fwaas_rules - if not router_with_firewall: - dnat_rule = self._get_dnat_fw_rule(context, router_db) - if dnat_rule: - fw_rules.append(dnat_rule) + # The rules added from here forward are relevant only for interface + # ports without fwaas firewall group + # To allow this traffic on interfaces with firewall group, the user + # should add specific rules. + dnat_rule = self._get_dnat_fw_rule(context, router_db) + if dnat_rule: + fw_rules.append(dnat_rule) - # Add rule for not NAT-ed allocation pools - alloc_pool_rule = self._get_allocation_pools_fw_rule( - context, router_db) - if alloc_pool_rule: - fw_rules.append(alloc_pool_rule) + # Add rule for not NAT-ed allocation pools + alloc_pool_rule = self._get_allocation_pools_fw_rule( + context, router_db) + if alloc_pool_rule: + fw_rules.append(alloc_pool_rule) - # Add no-snat rules - nosnat_fw_rules = self._get_nosnat_subnets_fw_rules( - context, router_db) - fw_rules.extend(nosnat_fw_rules) + # Add no-snat rules + nosnat_fw_rules = self._get_nosnat_subnets_fw_rules( + context, router_db) + fw_rules.extend(nosnat_fw_rules) - vpn_plugin = directory.get_plugin(plugin_const.VPN) - if vpn_plugin: - vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider] - vpn_rules = ( - vpn_driver._generate_ipsecvpn_firewall_rules( - self.plugin_type(), context, edge_id=edge_id)) - fw_rules.extend(vpn_rules) + vpn_plugin = directory.get_plugin(plugin_const.VPN) + if vpn_plugin: + vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider] + vpn_rules = ( + vpn_driver._generate_ipsecvpn_firewall_rules( + self.plugin_type(), context, edge_id=edge_id)) + fw_rules.extend(vpn_rules) # Get the load balancer rules in case they are refreshed # (relevant only for older LB that are still on the router edge) @@ -4102,11 +4121,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin, fw = {'firewall_rule_list': fw_rules} try: - # If we have a firewall we shouldn't add the default - # allow-external rule - allow_external = False if router_with_firewall else True - edge_utils.update_firewall(self.nsx_v, context, router_id, fw, - allow_external=allow_external) + edge_utils.update_firewall(self.nsx_v, context, router_id, fw) except vsh_exc.ResourceNotFound: LOG.error("Failed to update firewall for router %s", router_id) diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index b0d1a5bae9..be72efb01f 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -2577,7 +2577,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base, relay_target = [] if self.fwaas_callbacks: relay_target = (self.fwaas_callbacks.fwaas_driver. - translate_addresses_to_target(set(relay_servers))) + translate_addresses_to_target(set(relay_servers), + self.plugin_type())) dhcp_services = self._get_port_relay_services() diff --git a/vmware_nsx/services/fwaas/common/fwaas_callbacks_v2.py b/vmware_nsx/services/fwaas/common/fwaas_callbacks_v2.py index 86da48a404..ca63388ebb 100644 --- a/vmware_nsx/services/fwaas/common/fwaas_callbacks_v2.py +++ b/vmware_nsx/services/fwaas/common/fwaas_callbacks_v2.py @@ -1,4 +1,4 @@ -# Copyright 2017 VMware, Inc. +# Copyright 2018 VMware, Inc. # All Rights Reserved # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -48,22 +48,20 @@ class NsxFwaasCallbacksV2(firewall_l3_agent_v2.L3WithFWaaS): neutron_conf.agent_mode = 'nsx' super(NsxFwaasCallbacksV2, self).__init__(conf=neutron_conf) self.agent_api = DummyAgentApi() - self._core_plugin = None + self.core_plugin = self._get_core_plugin() @property def plugin_type(self): pass - @property - def core_plugin(self): - """Get the NSX-V3 core plugin""" - if not self._core_plugin: - self._core_plugin = directory.get_plugin() - if self._core_plugin.is_tvd_plugin(): - # get the plugin that match this driver - self._core_plugin = self._core_plugin.get_plugin_by_type( - self.plugin_type) - return self._core_plugin + def _get_core_plugin(self): + """Get the NSX core plugin""" + core_plugin = directory.get_plugin() + if core_plugin.is_tvd_plugin(): + # get the plugin that match this driver + core_plugin = core_plugin.get_plugin_by_type( + self.plugin_type) + return core_plugin # Override functions using the agent_api that is not used by our plugin def _get_firewall_group_ports(self, context, firewall_group, @@ -203,3 +201,33 @@ class NsxFwaasCallbacksV2(firewall_l3_agent_v2.L3WithFWaaS): port_id=port_id).first() if entry: return entry.firewall_group_id + + def should_apply_firewall_to_router(self, context, router_id): + """Return True if there are FWaaS rules that are attached to an + interface of the given router. + """ + if not self.fwaas_enabled: + return False + + ctx = context.elevated() + router_interfaces = self.core_plugin._get_router_interfaces( + ctx, router_id) + for port in router_interfaces: + fwg_id = self._get_port_firewall_group_id(ctx, port['id']) + if fwg_id: + # check the state of this firewall group + fwg = self._get_fw_group_from_plugin(ctx, fwg_id) + if fwg is not None: + if fwg.get('status') not in (nl_constants.ERROR, + nl_constants.PENDING_DELETE): + # Found a router interface port with rules + return True + return False + + def delete_port(self, context, port_id): + # Mark the FW group as inactive if this is the last port + fwg = self.get_port_fwg(context, port_id) + if (fwg and fwg.get('status') == nl_constants.ACTIVE and + len(fwg.get('ports', [])) <= 1): + self.fwplugin_rpc.set_firewall_group_status( + context, fwg['id'], nl_constants.INACTIVE) diff --git a/vmware_nsx/services/fwaas/common/fwaas_driver_base.py b/vmware_nsx/services/fwaas/common/fwaas_driver_base.py new file mode 100644 index 0000000000..b999dfd890 --- /dev/null +++ b/vmware_nsx/services/fwaas/common/fwaas_driver_base.py @@ -0,0 +1,93 @@ +# Copyright 2017 VMware, 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. + +import abc + +from oslo_log import helpers as log_helpers +from oslo_log import log as logging + +from neutron_lib.exceptions import firewall_v2 as exceptions + +try: + from neutron_fwaas.services.firewall.service_drivers.agents.drivers \ + import fwaas_base +except ImportError: + # FWaaS project no found + from vmware_nsx.services.fwaas.common import fwaas_mocks \ + as fwaas_base + +LOG = logging.getLogger(__name__) + + +class EdgeFwaasDriverBaseV2(fwaas_base.FwaasDriverBase): + """NSX Base driver for Firewall As A Service - V2.""" + + def __init__(self, driver_name): + super(EdgeFwaasDriverBaseV2, self).__init__() + self.driver_name = driver_name + + @log_helpers.log_method_call + def create_firewall_group(self, agent_mode, apply_list, firewall_group): + """Create the Firewall with a given policy. """ + self._validate_firewall_group(firewall_group) + self._update_backend_routers(apply_list, firewall_group['id']) + + @log_helpers.log_method_call + def update_firewall_group(self, agent_mode, apply_list, firewall_group): + """Remove previous policy and apply the new policy.""" + self._validate_firewall_group(firewall_group) + self._update_backend_routers(apply_list, firewall_group['id']) + + @log_helpers.log_method_call + def delete_firewall_group(self, agent_mode, apply_list, firewall_group): + """Delete firewall. + + Removes rules created by this instance from the backend firewall + And add the default allow rule. + """ + self._update_backend_routers(apply_list, firewall_group['id']) + + @log_helpers.log_method_call + def apply_default_policy(self, agent_mode, apply_list, firewall_group): + """Apply the default policy (deny all). + + The backend firewall always has this policy (=deny all) as default, + so we only need to delete the current rules. + """ + self._update_backend_routers(apply_list, firewall_group['id']) + + @abc.abstractmethod + def _update_backend_routers(self, apply_list, fwg_id): + """Update all the affected router on the backend""" + pass + + def _validate_firewall_group(self, firewall_group): + """Validate the rules in the firewall group""" + for rule in firewall_group['egress_rule_list']: + if rule.get('source_ip_address'): + # this rule cannot be used as egress rule + LOG.error("Rule %(id)s cannot be used in an egress " + "policy because it has a source", + {'id': rule['id']}) + raise exceptions.FirewallInternalDriverError( + driver=self.driver_name) + for rule in firewall_group['ingress_rule_list']: + if rule.get('destination_ip_address'): + # this rule cannot be used as ingress rule + LOG.error("Rule %(id)s cannot be used in an ingress " + "policy because it has a destination", + {'id': rule['id']}) + raise exceptions.FirewallInternalDriverError( + driver=self.driver_name) diff --git a/vmware_nsx/services/fwaas/nsx_tv/edge_fwaas_driver_v2.py b/vmware_nsx/services/fwaas/nsx_tv/edge_fwaas_driver_v2.py index ba5e12da21..2dd646ba32 100644 --- a/vmware_nsx/services/fwaas/nsx_tv/edge_fwaas_driver_v2.py +++ b/vmware_nsx/services/fwaas/nsx_tv/edge_fwaas_driver_v2.py @@ -20,6 +20,7 @@ from neutron_lib.exceptions import firewall_v2 as exceptions from vmware_nsx.extensions import projectpluginmap from vmware_nsx.plugins.nsx import utils as tvd_utils +from vmware_nsx.services.fwaas.nsx_v import edge_fwaas_driver_v2 as v_driver from vmware_nsx.services.fwaas.nsx_v3 import edge_fwaas_driver_v2 as t_driver LOG = logging.getLogger(__name__) @@ -36,15 +37,13 @@ except ImportError: class EdgeFwaasTVDriverV2(fwaas_base_v2.FwaasDriverBase): """NSX-TV driver for Firewall As A Service - V2. - - This driver is just a wrapper calling the relevant nsx-v3 driver """ def __init__(self): super(EdgeFwaasTVDriverV2, self).__init__() self.driver_name = FWAAS_DRIVER_NAME - # supported drivers (Only NSX-T): + # supported drivers: self.drivers = {} try: self.drivers[projectpluginmap.NsxPlugins.NSX_T] = ( @@ -53,10 +52,20 @@ class EdgeFwaasTVDriverV2(fwaas_base_v2.FwaasDriverBase): LOG.warning("EdgeFwaasTVDriverV2 failed to initialize the NSX-T " "driver") self.drivers[projectpluginmap.NsxPlugins.NSX_T] = None + try: + self.drivers[projectpluginmap.NsxPlugins.NSX_V] = ( + v_driver.EdgeFwaasVDriverV2()) + except Exception: + LOG.warning("EdgeFwaasTVDriverV2 failed to initialize the NSX-V " + "driver") + self.drivers[projectpluginmap.NsxPlugins.NSX_V] = None def get_T_driver(self): return self.drivers[projectpluginmap.NsxPlugins.NSX_T] + def get_V_driver(self): + return self.drivers[projectpluginmap.NsxPlugins.NSX_V] + def _get_driver_for_project(self, project): plugin_type = tvd_utils.get_tvd_plugin_type_for_project(project) if not self.drivers.get(plugin_type): @@ -87,8 +96,12 @@ class EdgeFwaasTVDriverV2(fwaas_base_v2.FwaasDriverBase): d = self._get_driver_for_project(firewall_group['tenant_id']) return d.apply_default_policy(agent_mode, apply_list, firewall_group) - def translate_addresses_to_target(self, cidrs, fwaas_rule_id=None): + def translate_addresses_to_target(self, cidrs, plugin_type, + fwaas_rule_id=None): # This api is called directly from the core plugin - # Assuming nsx-T as it is the only one supported now. - return self.get_T_driver().translate_addresses_to_target( - cidrs, fwaas_rule_id=fwaas_rule_id) + if not self.drivers.get(plugin_type): + LOG.error("%s has no support for plugin %s", + self.driver_name, plugin_type) + else: + return self.drivers[plugin_type].translate_addresses_to_target( + cidrs, fwaas_rule_id=fwaas_rule_id) diff --git a/vmware_nsx/services/fwaas/nsx_v/edge_fwaas_driver_v2.py b/vmware_nsx/services/fwaas/nsx_v/edge_fwaas_driver_v2.py new file mode 100644 index 0000000000..8e6e544c7e --- /dev/null +++ b/vmware_nsx/services/fwaas/nsx_v/edge_fwaas_driver_v2.py @@ -0,0 +1,142 @@ +# Copyright 2018 VMware, 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. + +from neutron_lib import context as n_context +from oslo_log import log as logging + +from neutron_lib.exceptions import firewall_v2 as exceptions +from neutron_lib.plugins import directory + +from vmware_nsx.common import locking +from vmware_nsx.extensions import projectpluginmap +from vmware_nsx.plugins.nsx_v.vshield import edge_utils +from vmware_nsx.services.fwaas.common import fwaas_driver_base + +LOG = logging.getLogger(__name__) +FWAAS_DRIVER_NAME = 'Fwaas V2 NSX-V driver' + + +class EdgeFwaasVDriverV2(fwaas_driver_base.EdgeFwaasDriverBaseV2): + """NSX-V driver for Firewall As A Service - V2.""" + + def __init__(self): + super(EdgeFwaasVDriverV2, self).__init__(FWAAS_DRIVER_NAME) + self._core_plugin = None + + @property + def core_plugin(self): + """Get the NSX-V core plugin""" + if not self._core_plugin: + self._core_plugin = directory.get_plugin() + if self._core_plugin.is_tvd_plugin(): + self._core_plugin = self._core_plugin.get_plugin_by_type( + projectpluginmap.NsxPlugins.NSX_V) + if not self._core_plugin: + # The NSX-V plugin was not initialized + return + # make sure plugin init was completed + if not self._core_plugin.init_is_complete: + self._core_plugin.init_complete(None, None, {}) + return self._core_plugin + + def should_apply_firewall_to_router(self, router_data, + raise_exception=True): + """Return True if the firewall rules allowed to be added the router + + Return False in those cases: + - router without an external gateway (rule may be added later when + there is a gateway) + + Raise an exception if the router is unsupported + (and raise_exception is True): + - shared router (not supported) + - md proxy router (not supported) + + """ + if (not router_data.get('distributed') and + router_data.get('router_type') == 'shared'): + LOG.error("Cannot apply firewall to shared router %s", + router_data['id']) + if raise_exception: + raise exceptions.FirewallInternalDriverError( + driver=self.driver_name) + return False + + if router_data.get('name', '').startswith('metadata_proxy_router'): + LOG.error("Cannot apply firewall to the metadata proxy router %s", + router_data['id']) + if raise_exception: + raise exceptions.FirewallInternalDriverError( + driver=self.driver_name) + return False + + if not router_data.get('external_gateway_info'): + LOG.info("Cannot apply firewall to router %s with no gateway", + router_data['id']) + return False + + return True + + def _update_backend_routers(self, apply_list, fwg_id): + """Update all the affected routers on the backend""" + LOG.info("Updating routers firewall for firewall group %s", fwg_id) + context = n_context.get_admin_context() + routers = set() + routers_mapping = {} + # the apply_list is a list of tuples: routerInfo, port-id + for router_info, port_id in apply_list: + # Skip dummy entries that were added only to avoid errors + if isinstance(router_info, str): + continue + # Skip unsupported routers + if not self.should_apply_firewall_to_router(router_info.router): + continue + + lookup_id = None + router_id = router_info.router_id + if router_info.router.get('distributed'): + # Distributed router (need to update the plr edge) + lookup_id = self.core_plugin.edge_manager.get_plr_by_tlr_id( + context, router_id) + else: + # Exclusive router + lookup_id = router_id + if lookup_id: + # look for the edge id in the DB + edge_id = edge_utils.get_router_edge_id(context, lookup_id) + if edge_id: + routers_mapping[router_id] = {'edge_id': edge_id, + 'lookup_id': lookup_id} + routers.add(router_id) + + # update each router once using the core plugin + for router_id in routers: + router_db = self.core_plugin._get_router(context, router_id) + edge_id = routers_mapping[router_id]['edge_id'] + LOG.info("Updating FWaaS rules for router %s on edge %s", + router_id, edge_id) + router_lookup_id = routers_mapping[router_id]['lookup_id'] + try: + with locking.LockManager.get_lock(str(edge_id)): + self.core_plugin.update_router_firewall( + context, router_lookup_id, router_db) + except Exception as e: + # catch known library exceptions and raise Fwaas generic + # exception + LOG.error("Failed to update firewall rules on edge " + "%(edge_id)s for router %(rtr)s: %(e)s", + {'e': e, 'rtr': router_id, 'edge_id': edge_id}) + raise exceptions.FirewallInternalDriverError( + driver=self.driver_name) diff --git a/vmware_nsx/services/fwaas/nsx_v/fwaas_callbacks_v2.py b/vmware_nsx/services/fwaas/nsx_v/fwaas_callbacks_v2.py new file mode 100644 index 0000000000..4fd6c36646 --- /dev/null +++ b/vmware_nsx/services/fwaas/nsx_v/fwaas_callbacks_v2.py @@ -0,0 +1,167 @@ +# Copyright 2018 VMware, 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. + +from oslo_log import log as logging + +from vmware_nsx.db import nsxv_db +from vmware_nsx.extensions import projectpluginmap +from vmware_nsx.plugins.nsx_v.vshield import edge_firewall_driver +from vmware_nsx.services.fwaas.common import fwaas_callbacks_v2 as \ + com_callbacks +from vmware_nsx.services.fwaas.nsx_tv import edge_fwaas_driver_v2 as tv_driver + +LOG = logging.getLogger(__name__) +RULE_NAME_PREFIX = 'Fwaas-' + + +class NsxvFwaasCallbacksV2(com_callbacks.NsxFwaasCallbacksV2): + """NSX-V RPC callbacks for Firewall As A Service - V2.""" + + def __init__(self): + super(NsxvFwaasCallbacksV2, self).__init__() + # update the fwaas driver in case of TV plugin + self.internal_driver = None + if self.fwaas_enabled: + if self.fwaas_driver.driver_name == tv_driver.FWAAS_DRIVER_NAME: + self.internal_driver = self.fwaas_driver.get_V_driver() + else: + self.internal_driver = self.fwaas_driver + + @property + def plugin_type(self): + return projectpluginmap.NsxPlugins.NSX_V + + def should_apply_firewall_to_router(self, context, router, router_id): + """Return True if the FWaaS rules should be added to this router.""" + # in case of a distributed-router: + # router['id'] is the id of the neutron router (=tlr) + # and router_id is the plr/tlr (the one that is being updated) + + # First check if there are rules attached to this router + if not super(NsxvFwaasCallbacksV2, + self).should_apply_firewall_to_router( + context, router['id']): + return False + + # get all the relevant router info + # ("router" does not have all the fields) + ctx_elevated = context.elevated() + router_data = self.core_plugin.get_router(ctx_elevated, router['id']) + if not router_data: + LOG.error("Couldn't read router %s data", router['id']) + return False + + if router_data.get('distributed'): + if router_id == router['id']: + # Do not add firewall rules on the tlr router. + return False + + # Check if the FWaaS driver supports this router + if not self.internal_driver.should_apply_firewall_to_router( + router_data, raise_exception=False): + return False + + return True + + def get_fwaas_rules_for_router(self, context, router_id, edge_id): + """Return the list of (translated) FWaaS rules for this router.""" + ctx_elevated = context.elevated() + router_interfaces = self.core_plugin._get_router_interfaces( + ctx_elevated, router_id) + + fw_rules = [] + # Add firewall rules per port attached to a firewall group + for port in router_interfaces: + fwg = self.get_port_fwg(ctx_elevated, port['id']) + if fwg: + # get the interface vnic + edge_vnic_bind = nsxv_db.get_edge_vnic_binding( + context.session, edge_id, port['network_id']) + vnic_id = 'vnic-index-%s' % edge_vnic_bind.vnic_index + # Add the FWaaS rules for this port + fw_rules.extend( + self.get_port_translated_rules(vnic_id, fwg)) + + return fw_rules + + def get_port_translated_rules(self, vnic_id, firewall_group): + """Return the list of translated rules per port + + Ingress/Egress firewall rules + default ingress/egress drop + """ + port_rules = [] + logged = False + # Add the firewall group ingress/egress rules only if the fw is up + if firewall_group['admin_state_up']: + port_rules.extend(self.translate_rules( + firewall_group['ingress_rule_list'], + replace_dest=vnic_id, + logged=logged, + is_ingress=True)) + port_rules.extend(self.translate_rules( + firewall_group['egress_rule_list'], + replace_src=vnic_id, + logged=logged, + is_ingress=False)) + + # Add ingress/egress block rules for this port + port_rules.extend([ + {'name': "Block port ingress", + 'action': edge_firewall_driver.FWAAS_DENY, + 'destination_vnic_groups': [vnic_id], + 'logged': logged}, + {'name': "Block port egress", + 'action': edge_firewall_driver.FWAAS_DENY, + 'source_vnic_groups': [vnic_id], + 'logged': logged}]) + + return port_rules + + def translate_rules(self, fwaas_rules, replace_dest=None, replace_src=None, + logged=False, is_ingress=True): + translated_rules = [] + for rule in fwaas_rules: + if not rule['enabled']: + # skip disabled rules + continue + # Make sure the rule has a name, and it starts with the prefix + # (backend max name length is 30) + if rule.get('name'): + rule['name'] = RULE_NAME_PREFIX + rule['name'] + else: + rule['name'] = RULE_NAME_PREFIX + rule['id'] + rule['name'] = rule['name'][:30] + if rule.get('id'): + # update rules ID to prevent DB duplications in + # NsxvEdgeFirewallRuleBinding + if is_ingress: + rule['id'] = ('ingress-%s' % rule['id'])[:36] + else: + rule['id'] = ('egress-%s' % rule['id'])[:36] + # source & destination should be lists + if replace_dest: + rule['destination_vnic_groups'] = [replace_dest] + elif rule.get('destination_ip_address'): + rule['destination_ip_address'] = [ + rule['destination_ip_address']] + if replace_src: + rule['source_vnic_groups'] = [replace_src] + elif rule.get('source_ip_address'): + rule['source_ip_address'] = [rule['source_ip_address']] + if logged: + rule['logged'] = True + translated_rules.append(rule) + + return translated_rules diff --git a/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_base.py b/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_base.py index 20acd0e6d4..a621f3d7a9 100644 --- a/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_base.py +++ b/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_base.py @@ -23,27 +23,21 @@ from neutron_lib.plugins import directory from oslo_log import log as logging from vmware_nsx.extensions import projectpluginmap +from vmware_nsx.services.fwaas.common import fwaas_driver_base from vmware_nsxlib.v3 import nsx_constants as consts LOG = logging.getLogger(__name__) RULE_NAME_PREFIX = 'Fwaas-' DEFAULT_RULE_NAME = 'Default LR Layer3 Rule' -try: - from neutron_fwaas.services.firewall.service_drivers.agents.drivers \ - import fwaas_base -except ImportError: - # FWaaS project no found - from vmware_nsx.services.fwaas.common import fwaas_mocks \ - as fwaas_base - -class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase): +#TODO(asarfaty): this base class now serves only 1 driver and can be merged +# with it +class CommonEdgeFwaasV3Driver(fwaas_driver_base.EdgeFwaasDriverBaseV2): """Base class for NSX-V3 driver for Firewall As A Service - V1 & V2.""" def __init__(self, driver_exception, driver_name): - super(CommonEdgeFwaasV3Driver, self).__init__() - self.driver_name = driver_name + super(CommonEdgeFwaasV3Driver, self).__init__(driver_name) self.backend_support = True self.driver_exception = driver_exception registry.subscribe( @@ -139,7 +133,8 @@ class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase): cidr, consts.IPV6 if net.version == 6 else consts.IPV4) - def translate_addresses_to_target(self, cidrs, fwaas_rule_id=None): + def translate_addresses_to_target(self, cidrs, plugin_type, + fwaas_rule_id=None): translated_cidrs = [] for ip in cidrs: res = self._translate_cidr(ip, fwaas_rule_id) diff --git a/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_v2.py b/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_v2.py index 2bec6cde63..aa0863054b 100644 --- a/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_v2.py +++ b/vmware_nsx/services/fwaas/nsx_v3/edge_fwaas_driver_v2.py @@ -14,7 +14,6 @@ # under the License. from neutron_lib import context as n_context -from oslo_log import helpers as log_helpers from oslo_log import log as logging from neutron_lib.exceptions import firewall_v2 as exceptions @@ -35,36 +34,6 @@ class EdgeFwaasV3DriverV2(base_driver.CommonEdgeFwaasV3Driver): super(EdgeFwaasV3DriverV2, self).__init__(exception_cls, FWAAS_DRIVER_NAME) - @log_helpers.log_method_call - def create_firewall_group(self, agent_mode, apply_list, firewall_group): - """Create the Firewall with a given policy. """ - self._validate_firewall_group(firewall_group) - self._update_backend_routers(apply_list, firewall_group['id']) - - @log_helpers.log_method_call - def update_firewall_group(self, agent_mode, apply_list, firewall_group): - """Remove previous policy and apply the new policy.""" - self._validate_firewall_group(firewall_group) - self._update_backend_routers(apply_list, firewall_group['id']) - - @log_helpers.log_method_call - def delete_firewall_group(self, agent_mode, apply_list, firewall_group): - """Delete firewall. - - Removes rules created by this instance from the backend firewall - And add the default allow rule. - """ - self._update_backend_routers(apply_list, firewall_group['id']) - - @log_helpers.log_method_call - def apply_default_policy(self, agent_mode, apply_list, firewall_group): - """Apply the default policy (deny all). - - The backend firewall always has this policy (=deny all) as default, - so we only need to delete the current rules. - """ - self._update_backend_routers(apply_list, firewall_group['id']) - def _update_backend_routers(self, apply_list, fwg_id): """Update all the affected router on the backend""" self.validate_backend_version() @@ -121,22 +90,3 @@ class EdgeFwaasV3DriverV2(base_driver.CommonEdgeFwaasV3Driver): 'direction': 'OUT'}]) return port_rules - - def _validate_firewall_group(self, firewall_group): - """Validate the rules in the firewall group""" - for rule in firewall_group['egress_rule_list']: - if rule.get('source_ip_address'): - # this rule cannot be used as egress rule - LOG.error("Rule %(id)s cannot be used in an egress " - "policy because it has a source", - {'id': rule['id']}) - raise exceptions.FirewallInternalDriverError( - driver=FWAAS_DRIVER_NAME) - for rule in firewall_group['ingress_rule_list']: - if rule.get('destination_ip_address'): - # this rule cannot be used as ingress rule - LOG.error("Rule %(id)s cannot be used in an ingress " - "policy because it has a destination", - {'id': rule['id']}) - raise exceptions.FirewallInternalDriverError( - driver=FWAAS_DRIVER_NAME) diff --git a/vmware_nsx/tests/unit/nsx_v/test_fwaas_v2_driver.py b/vmware_nsx/tests/unit/nsx_v/test_fwaas_v2_driver.py new file mode 100644 index 0000000000..aabba0dc8c --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_v/test_fwaas_v2_driver.py @@ -0,0 +1,288 @@ +# Copyright 2018 VMware, 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. + +import copy + +import mock + +from neutron_lib.exceptions import firewall_v2 as exceptions +from neutron_lib.plugins import directory + +from vmware_nsx.db import nsxv_models +from vmware_nsx.plugins.nsx_v.vshield import edge_firewall_driver +from vmware_nsx.plugins.nsx_v.vshield import edge_utils +from vmware_nsx.services.fwaas.nsx_v import edge_fwaas_driver_v2 +from vmware_nsx.services.fwaas.nsx_v import fwaas_callbacks_v2 +from vmware_nsx.tests.unit.nsx_v import test_plugin as test_v_plugin + +FAKE_FW_ID = 'fake_fw_uuid' +FAKE_ROUTER_ID = 'fake_rtr_uuid' +FAKE_PORT_ID = 'fake_port_uuid' +FAKE_NET_ID = 'fake_net_uuid' +FAKE_DB_OBJ = nsxv_models.NsxvEdgeVnicBinding(vnic_index='1') + + +class NsxvFwaasTestCase(test_v_plugin.NsxVPluginV2TestCase): + def setUp(self): + super(NsxvFwaasTestCase, self).setUp() + self.firewall = edge_fwaas_driver_v2.EdgeFwaasVDriverV2() + + self.plugin = directory.get_plugin() + self.plugin.fwaas_callbacks = fwaas_callbacks_v2.\ + NsxvFwaasCallbacksV2() + self.plugin.fwaas_callbacks.fwaas_enabled = True + self.plugin.fwaas_callbacks.fwaas_driver = self.firewall + self.plugin.fwaas_callbacks.internal_driver = self.firewall + self.plugin.init_is_complete = True + self.plugin.metadata_proxy_handler = None + + # Start some mocks + self.router = {'id': FAKE_ROUTER_ID, + 'external_gateway_info': {'network_id': 'external'}} + mock.patch.object(self.plugin, '_get_router', + return_value=self.router).start() + mock.patch.object(self.plugin, 'get_router', + return_value=self.router).start() + self.port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID} + mock.patch.object(self.plugin, '_get_router_interfaces', + return_value=[self.port]).start() + mock.patch.object(self.plugin, 'get_port', + return_value=self.port).start() + mock.patch.object(self.plugin, '_get_subnet_fw_rules', + return_value=[]).start() + mock.patch.object(self.plugin, '_get_dnat_fw_rule', + return_value=[]).start() + mock.patch.object(self.plugin, '_get_allocation_pools_fw_rule', + return_value=[]).start() + mock.patch.object(self.plugin, '_get_nosnat_subnets_fw_rules', + return_value=[]).start() + + def _fake_rules_v4(self, is_ingress=True, cidr='10.24.4.0/24'): + rule1 = {'enabled': True, + 'action': 'allow', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '80', + 'id': 'fake-fw-rule1', + 'description': 'first rule', + 'position': '0'} + rule2 = {'enabled': True, + 'action': 'reject', + 'ip_version': 4, + 'protocol': 'tcp', + 'destination_port': '22:24', + 'source_port': '1:65535', + 'id': 'fake-fw-rule2', + 'position': '1'} + rule3 = {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'protocol': 'icmp', + 'id': 'fake-fw-rule3', + 'position': '2'} + rule4 = {'enabled': True, + 'action': 'deny', + 'ip_version': 4, + 'id': 'fake-fw-rule4', + 'position': '3'} + if is_ingress: + # source ips are allowed + rule1['source_ip_address'] = cidr + else: + # dest ips are allowed for egress rules + rule1['destination_ip_address'] = cidr + + return [rule1, rule2, rule3, rule4] + + def _fake_translated_rules(self, rules_list, + nsx_port_id, + is_ingress=True, + logged=False): + translated_rules = copy.copy(rules_list) + for rule in translated_rules: + if logged: + rule['logged'] = True + if is_ingress: + rule['destination_vnic_groups'] = ['vnic-index-1'] + else: + rule['source_vnic_groups'] = ['vnic-index-1'] + if rule.get('destination_ip_address'): + rule['destination_ip_address'] = [ + rule['destination_ip_address']] + if rule.get('source_ip_address'): + rule['source_ip_address'] = [ + rule['source_ip_address']] + rule['name'] = (fwaas_callbacks_v2.RULE_NAME_PREFIX + + (rule.get('name') or rule['id']))[:30] + if rule.get('id'): + if is_ingress: + rule['id'] = ('ingress-%s' % rule['id'])[:36] + else: + rule['id'] = ('egress-%s' % rule['id'])[:36] + + return translated_rules + + def _fake_empty_firewall_group(self): + fw_inst = {'id': FAKE_FW_ID, + 'admin_state_up': True, + 'tenant_id': 'tenant-uuid', + 'ingress_rule_list': [], + 'egress_rule_list': []} + return fw_inst + + def _fake_firewall_group(self, rule_list, is_ingress=True, + admin_state_up=True): + _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': admin_state_up, + 'tenant_id': 'tenant-uuid', + 'ingress_rule_list': [], + 'egress_rule_list': []} + if is_ingress: + fw_inst['ingress_rule_list'] = _rule_list + else: + fw_inst['egress_rule_list'] = _rule_list + return fw_inst + + def _fake_firewall_group_with_admin_down(self, rule_list, + is_ingress=True): + return self._fake_firewall_group( + rule_list, is_ingress=is_ingress, admin_state_up=False) + + def _fake_apply_list(self): + router_inst = self.router + router_info_inst = mock.Mock() + router_info_inst.router = router_inst + router_info_inst.router_id = FAKE_ROUTER_ID + apply_list = [(router_info_inst, FAKE_PORT_ID)] + return apply_list + + def test_create_firewall_no_rules(self): + apply_list = self._fake_apply_list() + firewall = self._fake_empty_firewall_group() + with mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg', + return_value=firewall),\ + mock.patch.object(self.plugin.fwaas_callbacks, + '_get_port_firewall_group_id', + return_value=FAKE_FW_ID),\ + mock.patch.object(self.plugin.fwaas_callbacks, + '_get_fw_group_from_plugin', + return_value=firewall),\ + mock.patch("vmware_nsx.db.nsxv_db.get_edge_vnic_binding", + return_value=FAKE_DB_OBJ),\ + mock.patch.object(edge_utils, "update_firewall") as update_fw,\ + mock.patch.object(edge_utils, 'get_router_edge_id', + return_value='edge-1'): + self.firewall.create_firewall_group('nsx', apply_list, firewall) + # expecting 2 block rules for the logical port (egress & ingress) + # and last default allow all rule + expected_rules = [ + {'name': "Block port ingress", + 'action': edge_firewall_driver.FWAAS_DENY, + 'destination_vnic_groups': ['vnic-index-1'], + 'logged': False}, + {'name': "Block port egress", + 'action': edge_firewall_driver.FWAAS_DENY, + 'source_vnic_groups': ['vnic-index-1'], + 'logged': False}] + update_fw.assert_called_once_with( + self.plugin.nsx_v, mock.ANY, FAKE_ROUTER_ID, + {'firewall_rule_list': expected_rules}) + + def _setup_firewall_with_rules(self, func, is_ingress=True): + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4(is_ingress=is_ingress) + firewall = self._fake_firewall_group(rule_list, is_ingress=is_ingress) + with mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg', + return_value=firewall),\ + mock.patch.object(self.plugin.fwaas_callbacks, + '_get_port_firewall_group_id', + return_value=FAKE_FW_ID),\ + mock.patch.object(self.plugin.fwaas_callbacks, + '_get_fw_group_from_plugin', + return_value=firewall),\ + mock.patch("vmware_nsx.db.nsxv_db.get_edge_vnic_binding", + return_value=FAKE_DB_OBJ),\ + mock.patch.object(edge_utils, "update_firewall") as update_fw,\ + mock.patch.object(edge_utils, 'get_router_edge_id', + return_value='edge-1'): + func('nsx', apply_list, firewall) + expected_rules = self._fake_translated_rules( + rule_list, + 'vnic-index-1', is_ingress=is_ingress) + [ + {'name': "Block port ingress", + 'action': edge_firewall_driver.FWAAS_DENY, + 'destination_vnic_groups': ['vnic-index-1'], + 'logged': False}, + {'name': "Block port egress", + 'action': edge_firewall_driver.FWAAS_DENY, + 'source_vnic_groups': ['vnic-index-1'], + 'logged': False}] + + update_fw.assert_called_once_with( + self.plugin.nsx_v, mock.ANY, FAKE_ROUTER_ID, + {'firewall_rule_list': expected_rules}) + + def test_create_firewall_with_ingress_rules(self): + self._setup_firewall_with_rules(self.firewall.create_firewall_group) + + def test_update_firewall_with_ingress_rules(self): + self._setup_firewall_with_rules(self.firewall.update_firewall_group) + + def test_create_firewall_with_egress_rules(self): + self._setup_firewall_with_rules(self.firewall.create_firewall_group, + is_ingress=False) + + def test_update_firewall_with_egress_rules(self): + self._setup_firewall_with_rules(self.firewall.update_firewall_group, + is_ingress=False) + + def test_create_firewall_with_illegal_rules(self): + """Use ingress rules as the egress list and verify failure""" + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4(is_ingress=True) + firewall = self._fake_firewall_group(rule_list, is_ingress=False) + self.assertRaises(exceptions.FirewallInternalDriverError, + self.firewall.create_firewall_group, 'nsx', + apply_list, firewall) + + def test_delete_firewall(self): + apply_list = self._fake_apply_list() + firewall = self._fake_empty_firewall_group() + with mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg', + return_value=None),\ + mock.patch("vmware_nsx.db.db.get_nsx_switch_and_port_id", + return_value=('vnic-index-1', 0)),\ + mock.patch.object(edge_utils, "update_firewall") as update_fw,\ + mock.patch.object(edge_utils, 'get_router_edge_id', + return_value='edge-1'): + self.firewall.delete_firewall_group('nsx', apply_list, firewall) + update_fw.assert_called_once_with( + self.plugin.nsx_v, mock.ANY, FAKE_ROUTER_ID, + {'firewall_rule_list': []}) + + def test_create_firewall_with_admin_down(self): + apply_list = self._fake_apply_list() + rule_list = self._fake_rules_v4() + firewall = self._fake_firewall_group_with_admin_down(rule_list) + with mock.patch.object(edge_utils, "update_firewall") as update_fw,\ + mock.patch.object(edge_utils, 'get_router_edge_id', + return_value='edge-1'): + self.firewall.create_firewall_group('nsx', apply_list, firewall) + update_fw.assert_called_once_with( + self.plugin.nsx_v, mock.ANY, FAKE_ROUTER_ID, + {'firewall_rule_list': []})