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
This commit is contained in:
Adit Sarfaty 2018-11-26 14:55:49 +02:00
parent 85c9ae8071
commit a36a1dba74
13 changed files with 858 additions and 138 deletions

View File

@ -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 = <i.e 10.0>
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
~~~~~~~~~~~

View File

@ -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.

View File

@ -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 =

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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': []})