diff --git a/vmware_nsx/plugins/nsx_v3/availability_zones.py b/vmware_nsx/plugins/nsx_v3/availability_zones.py index 021e3e2dfc..de0314c28c 100644 --- a/vmware_nsx/plugins/nsx_v3/availability_zones.py +++ b/vmware_nsx/plugins/nsx_v3/availability_zones.py @@ -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): diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 1d0412adae..88355de234 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -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): 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 b4d32ffc82..dd9392cf92 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 @@ -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) diff --git a/vmware_nsx/services/fwaas/nsx_v3/fwaas_callbacks_v1.py b/vmware_nsx/services/fwaas/nsx_v3/fwaas_callbacks_v1.py index 07dab6580e..44c2af9e8c 100644 --- a/vmware_nsx/services/fwaas/nsx_v3/fwaas_callbacks_v1.py +++ b/vmware_nsx/services/fwaas/nsx_v3/fwaas_callbacks_v1.py @@ -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) diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py index 8df8ffadf4..350f92eb92 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/routers.py @@ -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.") diff --git a/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py b/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py index 253a419527..6a8cd77d27 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py +++ b/vmware_nsx/shell/admin/plugins/nsxv3/resources/utils.py @@ -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 diff --git a/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v1_driver.py b/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v1_driver.py index 1e0e1e90cf..8f6d472101 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v1_driver.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v1_driver.py @@ -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) diff --git a/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v2_driver.py b/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v2_driver.py index 42ffc5c4bd..942c95f97e 100644 --- a/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v2_driver.py +++ b/vmware_nsx/tests/unit/nsx_v3/test_fwaas_v2_driver.py @@ -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)