NSX|V3: Add DHCP relay firewall rules

When FWaaS v1 or v2 are used, there is a need to add FW rules
to allow the dhcp traffic to the relay server.
Those rules are added to the firewall before the default deny rule.
In case of FWaaS v2 - for each port separately.
The admin utility handling a change in the DHCP relay configuration
will now update the rules as well.

Change-Id: I30e666085fe5cdf17d48984518c73f79bf8cdf55
This commit is contained in:
Adit Sarfaty 2017-09-14 16:17:24 +03:00 committed by garyk
parent afdb9ea7ac
commit 96c1e57a7f
8 changed files with 262 additions and 28 deletions

View File

@ -181,7 +181,7 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
nsxlib.feature_supported(nsxlib_consts.FEATURE_DHCP_RELAY)):
relay_id = None
if cfg.CONF.nsx_v3.init_objects_by_tags:
# Find the TZ by its tag
# Find the relay service by its tag
relay_id = nsxlib.get_id_by_resource_and_tag(
nsxlib.relay_service.resource_type,
cfg.CONF.nsx_v3.search_objects_scope,
@ -191,8 +191,13 @@ class NsxV3AvailabilityZone(common_az.ConfiguredAvailabilityZone):
relay_id = nsxlib.relay_service.get_id_by_name_or_id(
self.dhcp_relay_service)
self.dhcp_relay_service = relay_id
# if there is a relay service - also find the server ips
if self.dhcp_relay_service:
self.dhcp_relay_servers = nsxlib.relay_service.get_server_ips(
self.dhcp_relay_service)
else:
self.dhcp_relay_service = None
self.dhcp_relay_servers = None
class NsxV3AvailabilityZones(common_az.ConfiguredAvailabilityZones):

View File

@ -3305,6 +3305,26 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return self.fwaas_callbacks.update_router_firewall(
context, self.nsxlib, router_id, ports)
def _get_port_relay_servers(self, context, port_id, network_id=None):
if not network_id:
port = self.get_port(context, port_id)
network_id = port['network_id']
net_az = self.get_network_az_by_net_id(context, network_id)
return net_az.dhcp_relay_servers
def _get_port_relay_services(self):
# DHCP services: UDP 67, 68, 2535
#TODO(asarfaty): use configurable ports
service1 = self.nsxlib.firewall_section.get_nsservice(
nsxlib_consts.L4_PORT_SET_NSSERVICE,
l4_protocol=nsxlib_consts.UDP,
destination_ports=['67-68'])
service2 = self.nsxlib.firewall_section.get_nsservice(
nsxlib_consts.L4_PORT_SET_NSSERVICE,
l4_protocol=nsxlib_consts.UDP,
destination_ports=['2535'])
return [service1, service2]
def get_extra_fw_rules(self, context, router_id, port_id=None):
"""Return firewall rules that should be added to the router firewall
@ -3317,8 +3337,59 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
port should be returned, and the rules should be ingress/egress
(but not both) and include the source/dest nsx logical port.
"""
#TODO(asarfaty): DHCP relay rules
return []
extra_rules = []
# DHCP relay rules:
# get the list of relevant relay servers
elv_ctx = context.elevated()
if port_id:
relay_servers = self._get_port_relay_servers(elv_ctx, port_id)
else:
relay_servers = []
filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],
'device_id': [router_id]}
ports = self.get_ports(elv_ctx, filters=filters)
for port in ports:
port_relay_servers = self._get_port_relay_servers(
elv_ctx, port['id'], network_id=port['network_id'])
if port_relay_servers:
relay_servers.extend(port_relay_servers)
# Add rules to allow dhcp traffic relay servers
if relay_servers:
# if it is a single port, the source/dest is this logical port
if port_id:
_net_id, nsx_port_id = nsx_db.get_nsx_switch_and_port_id(
context.session, port_id)
port_target = [{'target_type': 'LogicalPort',
'target_id': nsx_port_id}]
else:
port_target = None
# translate the relay server ips to the firewall format
relay_target = []
if self.fwaas_callbacks:
relay_target = (self.fwaas_callbacks.fwaas_driver.
translate_addresses_to_target(set(relay_servers)))
dhcp_services = self._get_port_relay_services()
# ingress rule
extra_rules.append({
'display_name': "DHCP Relay ingress traffic",
'action': nsxlib_consts.FW_ACTION_ALLOW,
'sources': relay_target,
'destinations': port_target,
'services': dhcp_services,
'direction': 'IN'})
# egress rule
extra_rules.append({
'display_name': "DHCP Relay egress traffic",
'action': nsxlib_consts.FW_ACTION_ALLOW,
'destinations': relay_target,
'sources': port_target,
'services': dhcp_services,
'direction': 'OUT'})
return extra_rules
def _get_ports_and_address_groups(self, context, router_id, network_id,
exclude_sub_ids=None):

View File

@ -98,7 +98,7 @@ class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase):
cidr,
consts.IPV6 if netaddr.valid_ipv6(cidr) else consts.IPV4)
def _translate_addresses(self, cidrs):
def translate_addresses_to_target(self, cidrs):
return [self._translate_cidr(ip) for ip in cidrs]
@staticmethod
@ -170,7 +170,7 @@ class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase):
'target_id': replace_dest}]
nsx_rule['direction'] = 'IN'
elif rule.get('destination_ip_address'):
nsx_rule['destinations'] = self._translate_addresses(
nsx_rule['destinations'] = self.translate_addresses_to_target(
[rule['destination_ip_address']])
if replace_src:
# set this value as the source logical port,
@ -179,7 +179,7 @@ class CommonEdgeFwaasV3Driver(fwaas_base.FwaasDriverBase):
'target_id': replace_src}]
nsx_rule['direction'] = 'OUT'
elif rule.get('source_ip_address'):
nsx_rule['sources'] = self._translate_addresses(
nsx_rule['sources'] = self.translate_addresses_to_target(
[rule['source_ip_address']])
if rule.get('protocol'):
nsx_rule['services'] = self._translate_services(rule)

View File

@ -53,7 +53,6 @@ class Nsxv3FwaasCallbacksV1(com_clbcks.NsxFwaasCallbacks):
This method should be called on FWaaS updates, and on router
interfaces changes.
"""
# find the backend router and its firewall section
nsx_id, sect_id = self.fwaas_driver.get_backend_router_and_fw_section(
context, router_id)

View File

@ -188,27 +188,37 @@ def update_dhcp_relay(resource, event, trigger, **kwargs):
# initialize the availability zones and nsxlib
config.register_nsxv3_azs(cfg.CONF, cfg.CONF.nsx_v3.availability_zones)
# get all neutron router interfaces ports
admin_cxt = neutron_context.get_admin_context()
with utils.NsxV3PluginWrapper() as plugin:
filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF]}
ports = plugin.get_ports(admin_cxt, filters=filters)
for port in ports:
# get the backend router port by the tag
nsx_port_id = nsxlib.get_id_by_resource_and_tag(
'LogicalRouterDownLinkPort',
'os-neutron-rport-id', port['id'])
if not nsx_port_id:
LOG.warning("Couldn't find nsx router port for interface %s",
port['id'])
continue
# get the network of this port
network_id = port['network_id']
# check the relay service on the az of the network
az = plugin.get_network_az_by_net_id(admin_cxt, network_id)
nsxlib.logical_router_port.update(
nsx_port_id, relay_service_uuid=az.dhcp_relay_service)
#TODO(asarfaty) also update the firewall rules of the routers
# Make sure FWaaS was initialized
plugin.init_fwaas_for_admin_utils()
# get all neutron routers and interfaces ports
routers = plugin.get_routers(admin_cxt)
for router in routers:
LOG.info("Updating router %s", router['id'])
filters = {'device_owner': [l3_db.DEVICE_OWNER_ROUTER_INTF],
'device_id': [router['id']]}
ports = plugin.get_ports(admin_cxt, filters=filters)
for port in ports:
# get the backend router port by the tag
nsx_port_id = nsxlib.get_id_by_resource_and_tag(
'LogicalRouterDownLinkPort',
'os-neutron-rport-id', port['id'])
if not nsx_port_id:
LOG.warning("Couldn't find nsx router port for interface "
"%s", port['id'])
continue
# get the network of this port
network_id = port['network_id']
# check the relay service on the az of the network
az = plugin.get_network_az_by_net_id(admin_cxt, network_id)
nsxlib.logical_router_port.update(
nsx_port_id, relay_service_uuid=az.dhcp_relay_service)
# if FWaaS is enables, also update the firewall rules
plugin.update_router_firewall(admin_cxt, router['id'])
LOG.info("Done.")

View File

@ -13,14 +13,22 @@
# under the License.
from oslo_config import cfg
from neutron.db import db_base_plugin_v2
from neutron import manager
from neutron_lib import context
from neutron_lib.plugins import constants as const
from neutron_lib.plugins import directory
from neutron_fwaas.services.firewall import fwaas_plugin as fwaas_plugin_v1
from neutron_fwaas.services.firewall import fwaas_plugin_v2
from vmware_nsx.db import db as nsx_db
from vmware_nsx.plugins.nsx_v3 import plugin
from vmware_nsx.plugins.nsx_v3 import utils as v3_utils
from vmware_nsx.services.fwaas.nsx_v3 import fwaas_callbacks_v1
from vmware_nsx.services.fwaas.nsx_v3 import fwaas_callbacks_v2
from vmware_nsxlib.v3 import nsx_constants
_NSXLIB = None
@ -107,6 +115,35 @@ class NsxV3PluginWrapper(plugin.NsxV3Plugin):
def __exit__(self, exc_type, exc_value, traceback):
directory.add_plugin(const.CORE, None)
def _init_fwaas_plugin(self, provider, callbacks_class, plugin_callbacks):
fwaas_plugin_class = manager.NeutronManager.load_class_for_provider(
'neutron.service_plugins', provider)
fwaas_plugin = fwaas_plugin_class()
self.fwaas_callbacks = callbacks_class(self.nsxlib)
# override the fwplugin_rpc since there is no RPC support in adminutils
self.fwaas_callbacks.fwplugin_rpc = plugin_callbacks(fwaas_plugin)
def init_fwaas_for_admin_utils(self):
# initialize the FWaaS plugin and callbacks
self.fwaas_callbacks = None
# This is an ugly patch to find out if it is v1 or v2
service_plugins = cfg.CONF.service_plugins
for srv_plugin in service_plugins:
if 'firewall' in srv_plugin:
if 'v2' in srv_plugin:
# FWaaS V2
self._init_fwaas_plugin(
'firewall_v2',
fwaas_callbacks_v2.Nsxv3FwaasCallbacksV2,
fwaas_plugin_v2.FirewallCallbacks)
else:
# FWaaS V1
self._init_fwaas_plugin(
'firewall',
fwaas_callbacks_v1.Nsxv3FwaasCallbacksV1,
fwaas_plugin_v1.FirewallCallbacks)
return
def _init_dhcp_metadata(self):
pass

View File

@ -29,6 +29,9 @@ from vmware_nsxlib.v3 import nsx_constants as consts
FAKE_FW_ID = 'fake_fw_uuid'
FAKE_ROUTER_ID = 'fake_rtr_uuid'
MOCK_NSX_ID = 'nsx_router_id'
FAKE_PORT_ID = 'fake_port_uuid'
FAKE_NET_ID = 'fake_net_uuid'
FAKE_NSX_PORT_ID = 'fake_nsx_port_uuid'
MOCK_DEFAULT_RULE_ID = 'nsx_default_rule_id'
MOCK_SECTION_ID = 'sec_id'
DEFAULT_RULE = {'is_default': True,
@ -177,6 +180,8 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
"update") as update_fw, \
mock.patch.object(self.plugin, '_get_router_interfaces',
return_value=[]), \
mock.patch.object(self.plugin, 'get_ports',
return_value=[]), \
mock.patch.object(self.plugin, 'get_router',
return_value=apply_list[0]), \
mock.patch.object(self.plugin.fwaas_callbacks,
@ -199,6 +204,8 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
"update") as update_fw,\
mock.patch.object(self.plugin, '_get_router_interfaces',
return_value=[]), \
mock.patch.object(self.plugin, 'get_ports',
return_value=[]), \
mock.patch.object(self.plugin, 'get_router',
return_value=apply_list[0]), \
mock.patch.object(self.plugin.fwaas_callbacks,
@ -269,6 +276,8 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
"update") as update_fw, \
mock.patch.object(self.plugin, '_get_router_interfaces',
return_value=[]), \
mock.patch.object(self.plugin, 'get_ports',
return_value=[]), \
mock.patch.object(self.plugin, 'get_router',
return_value=apply_list[0]), \
mock.patch.object(self.plugin.fwaas_callbacks,
@ -281,3 +290,47 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
update_fw.assert_called_once_with(
MOCK_SECTION_ID,
rules=[self._default_rule()])
def test_create_firewall_with_dhcp_relay(self):
apply_list = self._fake_apply_list()
firewall = self._fake_firewall_no_rule()
relay_server = '1.1.1.1'
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
with mock.patch("vmware_nsxlib.v3.security.NsxLibFirewallSection."
"update") as update_fw,\
mock.patch.object(self.plugin, '_get_router_interfaces',
return_value=[port]), \
mock.patch.object(self.plugin, 'get_ports',
return_value=[port]), \
mock.patch.object(self.plugin, 'get_router',
return_value=apply_list[0]), \
mock.patch.object(self.plugin, '_get_port_relay_servers',
return_value=[relay_server]),\
mock.patch.object(self.plugin.fwaas_callbacks,
'_get_router_firewall_id',
return_value=firewall['id']), \
mock.patch.object(self.plugin.fwaas_callbacks,
'_get_fw_from_plugin',
return_value=firewall):
self.firewall.create_firewall('nsx', apply_list, firewall)
# expecting 2 allow rules for the relay servers + default rule
expected_rules = expected_rules = [
{'display_name': "DHCP Relay ingress traffic",
'action': consts.FW_ACTION_ALLOW,
'destinations': None,
'sources': [{'target_id': relay_server,
'target_type': 'IPv4Address'}],
'services': self.plugin._get_port_relay_services(),
'direction': 'IN'},
{'display_name': "DHCP Relay egress traffic",
'action': consts.FW_ACTION_ALLOW,
'sources': None,
'destinations': [{'target_id': relay_server,
'target_type': 'IPv4Address'}],
'services': self.plugin._get_port_relay_services(),
'direction': 'OUT'},
self._default_rule()
]
update_fw.assert_called_once_with(
MOCK_SECTION_ID,
rules=expected_rules)

View File

@ -29,6 +29,7 @@ from vmware_nsxlib.v3 import nsx_constants as consts
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_NSX_PORT_ID = 'fake_nsx_port_uuid'
MOCK_NSX_ID = 'nsx_nsx_router_id'
MOCK_DEFAULT_RULE_ID = 'nsx_default_rule_id'
@ -191,9 +192,11 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
def test_create_firewall_no_rules(self):
apply_list = self._fake_apply_list()
firewall = self._fake_empty_firewall_group()
port = {'id': FAKE_PORT_ID}
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
with mock.patch.object(self.plugin, '_get_router_interfaces',
return_value=[port]),\
mock.patch.object(self.plugin, 'get_port',
return_value=port),\
mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg',
return_value=firewall),\
mock.patch("vmware_nsx.db.db.get_nsx_switch_and_port_id",
@ -224,9 +227,11 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
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)
port = {'id': FAKE_PORT_ID}
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
with mock.patch.object(self.plugin, '_get_router_interfaces',
return_value=[port]),\
mock.patch.object(self.plugin, 'get_port',
return_value=port),\
mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg',
return_value=firewall),\
mock.patch("vmware_nsx.db.db.get_nsx_switch_and_port_id",
@ -302,3 +307,57 @@ class Nsxv3FwaasTestCase(test_v3_plugin.NsxV3PluginTestCaseMixin):
update_fw.assert_called_once_with(
MOCK_SECTION_ID,
rules=[self._default_rule()])
def test_create_firewall_with_dhcp_relay(self):
apply_list = self._fake_apply_list()
firewall = self._fake_empty_firewall_group()
port = {'id': FAKE_PORT_ID, 'network_id': FAKE_NET_ID}
relay_server = '1.1.1.1'
with mock.patch.object(self.plugin, '_get_router_interfaces',
return_value=[port]),\
mock.patch.object(self.plugin, 'get_port',
return_value=port),\
mock.patch.object(self.plugin, '_get_port_relay_servers',
return_value=[relay_server]),\
mock.patch.object(self.plugin.fwaas_callbacks, 'get_port_fwg',
return_value=firewall),\
mock.patch("vmware_nsx.db.db.get_nsx_switch_and_port_id",
return_value=(0, FAKE_NSX_PORT_ID)),\
mock.patch("vmware_nsxlib.v3.security.NsxLibFirewallSection."
"update") as update_fw:
self.firewall.create_firewall_group('nsx', apply_list, firewall)
# expecting 2 allow rules for the relay servers,
# 2 block rules for the logical port (egress & ingress)
# and last default allow all rule
expected_rules = [
{'display_name': "DHCP Relay ingress traffic",
'action': consts.FW_ACTION_ALLOW,
'destinations': [{'target_type': 'LogicalPort',
'target_id': FAKE_NSX_PORT_ID}],
'sources': [{'target_id': relay_server,
'target_type': 'IPv4Address'}],
'services': self.plugin._get_port_relay_services(),
'direction': 'IN'},
{'display_name': "DHCP Relay egress traffic",
'action': consts.FW_ACTION_ALLOW,
'sources': [{'target_type': 'LogicalPort',
'target_id': FAKE_NSX_PORT_ID}],
'destinations': [{'target_id': relay_server,
'target_type': 'IPv4Address'}],
'services': self.plugin._get_port_relay_services(),
'direction': 'OUT'},
{'display_name': "Block port ingress",
'action': consts.FW_ACTION_DROP,
'destinations': [{'target_type': 'LogicalPort',
'target_id': FAKE_NSX_PORT_ID}],
'direction': 'IN'},
{'display_name': "Block port egress",
'action': consts.FW_ACTION_DROP,
'sources': [{'target_type': 'LogicalPort',
'target_id': FAKE_NSX_PORT_ID}],
'direction': 'OUT'},
self._default_rule()
]
update_fw.assert_called_once_with(
MOCK_SECTION_ID,
rules=expected_rules)