NSX|P add port security support

Adding segment profiles to the backend port Including mac learning support,
port security & spoofguard.
In addition - adding the exclude port tag for ports without port security

Change-Id: Ief4a3989316f7b7097c5be6145aae169cde87e8e
This commit is contained in:
Adit Sarfaty 2018-12-11 14:50:15 +02:00
parent ee46de95c3
commit 1f8ac3e9f6
5 changed files with 257 additions and 34 deletions

View File

@ -206,6 +206,10 @@ class NSXClient(object):
segment_ports = self.get_os_nsx_segment_ports(segment_id) segment_ports = self.get_os_nsx_segment_ports(segment_id)
for p in segment_ports: for p in segment_ports:
try: try:
self.nsxpolicy.segment_port_security_profiles.delete(
segment_id, p['id'])
self.nsxpolicy.segment_port_discovery_profiles.delete(
segment_id, p['id'])
self.nsxpolicy.segment_port.delete(segment_id, p['id']) self.nsxpolicy.segment_port.delete(segment_id, p['id'])
except exceptions.ManagerError as e: except exceptions.ManagerError as e:
print("Failed to delete segment port %s: %s" % (p['id'], e)) print("Failed to delete segment port %s: %s" % (p['id'], e))

View File

@ -400,6 +400,16 @@ class NsxPluginV3Base(plugin.NsxPluginBase,
LOG.warning(err_msg) LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg) raise n_exc.InvalidInput(error_message=err_msg)
def _assert_on_port_admin_state(self, port_data, device_owner):
"""Do not allow changing the admin state of some ports"""
if (device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or
device_owner == l3_db.DEVICE_OWNER_ROUTER_GW):
if port_data.get("admin_state_up") is False:
err_msg = _("admin_state_up=False router ports are not "
"supported")
LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def _validate_update_port(self, context, id, original_port, port_data): def _validate_update_port(self, context, id, original_port, port_data):
qos_selected = validators.is_attr_set(port_data.get qos_selected = validators.is_attr_set(port_data.get
(qos_consts.QOS_POLICY_ID)) (qos_consts.QOS_POLICY_ID))
@ -1174,3 +1184,13 @@ class NsxPluginV3Base(plugin.NsxPluginBase,
super(NsxPluginV3Base, self).delete_port(context, port_id) super(NsxPluginV3Base, self).delete_port(context, port_id)
if nsx_port_id: if nsx_port_id:
self.nsxlib.logical_port.delete(nsx_port_id) self.nsxlib.logical_port.delete(nsx_port_id)
def _is_excluded_port(self, device_owner, port_security):
if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
return False
if device_owner == constants.DEVICE_OWNER_DHCP:
if not self._has_native_dhcp_metadata():
return True
elif not port_security:
return True
return False

View File

@ -68,6 +68,7 @@ from vmware_nsx.common import utils
from vmware_nsx.db import db as nsx_db from vmware_nsx.db import db as nsx_db
from vmware_nsx.db import extended_security_group_rule as extend_sg_rule from vmware_nsx.db import extended_security_group_rule as extend_sg_rule
from vmware_nsx.db import maclearning as mac_db from vmware_nsx.db import maclearning as mac_db
from vmware_nsx.extensions import maclearning as mac_ext
from vmware_nsx.extensions import projectpluginmap from vmware_nsx.extensions import projectpluginmap
from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import providersecuritygroup as provider_sg
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as sg_prefix from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as sg_prefix
@ -80,6 +81,7 @@ from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
from vmware_nsxlib.v3 import policy_constants from vmware_nsxlib.v3 import policy_constants
from vmware_nsxlib.v3 import policy_defs from vmware_nsxlib.v3 import policy_defs
from vmware_nsxlib.v3 import security
from vmware_nsxlib.v3 import utils as nsxlib_utils from vmware_nsxlib.v3 import utils as nsxlib_utils
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -97,7 +99,8 @@ NSX_P_PROVIDER_SECTION_CATEGORY = policy_constants.CATEGORY_INFRASTRUCTURE
SPOOFGUARD_PROFILE_UUID = 'neutron-spoofguard-profile' SPOOFGUARD_PROFILE_UUID = 'neutron-spoofguard-profile'
NO_SPOOFGUARD_PROFILE_UUID = policy_defs.SpoofguardProfileDef.DEFAULT_PROFILE NO_SPOOFGUARD_PROFILE_UUID = policy_defs.SpoofguardProfileDef.DEFAULT_PROFILE
MAC_DISCOVERY_PROFILE_UUID = 'neutron-mac-discovery-profile' MAC_DISCOVERY_PROFILE_UUID = 'neutron-mac-discovery-profile'
NO_SEG_SECURITY_PROFILE_UUID = ( NO_SEG_SECURITY_PROFILE_UUID = 'neutron-no-segment-security-profile'
SEG_SECURITY_PROFILE_UUID = (
policy_defs.SegmentSecurityProfileDef.DEFAULT_PROFILE) policy_defs.SegmentSecurityProfileDef.DEFAULT_PROFILE)
@ -141,7 +144,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
"security-group-logging", "security-group-logging",
"provider-security-group", "provider-security-group",
"port-security-groups-filtering", "port-security-groups-filtering",
"vlan-transparent"] "vlan-transparent",
'mac-learning']
@resource_registry.tracked_resources( @resource_registry.tracked_resources(
network=models_v2.Network, network=models_v2.Network,
@ -284,14 +288,32 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
mac_learning_enabled=True, mac_learning_enabled=True,
tags=self.nsxpolicy.build_v3_api_version_tag()) tags=self.nsxpolicy.build_v3_api_version_tag())
# No Port security segment-security profile # No Port security segment-security profile (find it or create)
# (default NSX profile. just verify it exists)
try: try:
self.nsxpolicy.segment_security_profile.get( self.nsxpolicy.segment_security_profile.get(
NO_SEG_SECURITY_PROFILE_UUID) NO_SEG_SECURITY_PROFILE_UUID)
except nsx_lib_exc.ResourceNotFound:
self.nsxpolicy.segment_security_profile.create_or_overwrite(
NO_SEG_SECURITY_PROFILE_UUID,
profile_id=NO_SEG_SECURITY_PROFILE_UUID,
bpdu_filter_enable=False,
dhcp_client_block_enabled=False,
dhcp_client_block_v6_enabled=False,
dhcp_server_block_enabled=False,
dhcp_server_block_v6_enabled=False,
non_ip_traffic_block_enabled=False,
ra_guard_enabled=False,
rate_limits_enabled=False,
tags=self.nsxpolicy.build_v3_api_version_tag())
# Port security segment-security profile
# (default NSX profile. just verify it exists)
try:
self.nsxpolicy.segment_security_profile.get(
SEG_SECURITY_PROFILE_UUID)
except nsx_lib_exc.ResourceNotFound: except nsx_lib_exc.ResourceNotFound:
msg = (_("Cannot find segment security profile %s") % msg = (_("Cannot find segment security profile %s") %
NO_SEG_SECURITY_PROFILE_UUID) SEG_SECURITY_PROFILE_UUID)
raise nsx_exc.NsxPluginException(err_msg=msg) raise nsx_exc.NsxPluginException(err_msg=msg)
@staticmethod @staticmethod
@ -613,10 +635,9 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
return tags return tags
def _create_port_on_backend(self, context, port_data): def _create_port_on_backend(self, context, port_data, is_psec_on):
# TODO(annak): admin_state not supported by policy # TODO(annak): admin_state not supported by policy
# TODO(annak): handle exclude list # TODO(annak): handle exclude list
# TODO(annak): switching profiles when supported
name = self._build_port_name(context, port_data) name = self._build_port_name(context, port_data)
address_bindings = self._build_port_address_bindings( address_bindings = self._build_port_address_bindings(
context, port_data) context, port_data)
@ -632,6 +653,10 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
tags.extend(self.nsxpolicy.build_v3_api_version_project_tag( tags.extend(self.nsxpolicy.build_v3_api_version_project_tag(
context.tenant_name)) context.tenant_name))
if self._is_excluded_port(device_owner, is_psec_on):
tags.append({'scope': security.PORT_SG_SCOPE,
'tag': nsxlib_consts.EXCLUDE_PORT})
segment_id = self._get_network_nsx_segment_id( segment_id = self._get_network_nsx_segment_id(
context, port_data['network_id']) context, port_data['network_id'])
self.nsxpolicy.segment_port.create_or_overwrite( self.nsxpolicy.segment_port.create_or_overwrite(
@ -643,6 +668,34 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
attachment_type=attachment_type, attachment_type=attachment_type,
tags=tags) tags=tags)
# add the security profiles to the port
if is_psec_on:
spoofguard_profile = SPOOFGUARD_PROFILE_UUID
seg_sec_profile = SEG_SECURITY_PROFILE_UUID
else:
spoofguard_profile = NO_SPOOFGUARD_PROFILE_UUID
seg_sec_profile = NO_SEG_SECURITY_PROFILE_UUID
self.nsxpolicy.segment_port_security_profiles.create_or_overwrite(
name, segment_id, port_data['id'],
spoofguard_profile_id=spoofguard_profile,
segment_security_profile_id=seg_sec_profile)
# add the mac discovery profile to the port
mac_discovery_profile = None
mac_disc_profile_must = False
if is_psec_on:
address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS)
if validators.is_attr_set(address_pairs) and address_pairs:
mac_disc_profile_must = True
mac_learning_enabled = (
validators.is_attr_set(port_data.get(mac_ext.MAC_LEARNING)) and
port_data.get(mac_ext.MAC_LEARNING) is True)
if mac_disc_profile_must or mac_learning_enabled:
mac_discovery_profile = MAC_DISCOVERY_PROFILE_UUID
self.nsxpolicy.segment_port_discovery_profiles.create_or_overwrite(
name, segment_id, port_data['id'],
mac_discovery_profile_id=mac_discovery_profile)
def base_create_port(self, context, port): def base_create_port(self, context, port):
neutron_db = super(NsxPolicyPlugin, self).create_port(context, port) neutron_db = super(NsxPolicyPlugin, self).create_port(context, port)
self._extension_manager.process_create_port( self._extension_manager.process_create_port(
@ -680,11 +733,25 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
self._process_port_create_security_group(context, port_data, sgids) self._process_port_create_security_group(context, port_data, sgids)
self._process_port_create_provider_security_group( self._process_port_create_provider_security_group(
context, port_data, psgids) context, port_data, psgids)
#TODO(asarfaty): Handle mac learning
# Handle port mac learning
if validators.is_attr_set(port_data.get(mac_ext.MAC_LEARNING)):
# Make sure mac_learning and port sec are not both enabled
if port_data.get(mac_ext.MAC_LEARNING) and is_psec_on:
msg = _('Mac learning requires that port security be '
'disabled')
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
# save the mac learning value in the DB
self._create_mac_learning_state(context, port_data)
elif mac_ext.MAC_LEARNING in port_data:
# This is due to the fact that the default is
# ATTR_NOT_SPECIFIED
port_data.pop(mac_ext.MAC_LEARNING)
if not is_external_net: if not is_external_net:
try: try:
self._create_port_on_backend(context, port_data) self._create_port_on_backend(context, port_data, is_psec_on)
except Exception as e: except Exception as e:
with excutils.save_and_reraise_exception(): with excutils.save_and_reraise_exception():
LOG.error('Failed to create port %(id)s on NSX ' LOG.error('Failed to create port %(id)s on NSX '
@ -721,17 +788,22 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
if not self._network_is_external(context, net_id): if not self._network_is_external(context, net_id):
try: try:
segment_id = self._get_network_nsx_segment_id(context, net_id) segment_id = self._get_network_nsx_segment_id(context, net_id)
self.nsxpolicy.segment_port.delete(segment_id, port_data['id']) self.nsxpolicy.segment_port_security_profiles.delete(
segment_id, port_id)
self.nsxpolicy.segment_port_discovery_profiles.delete(
segment_id, port_id)
self.nsxpolicy.segment_port.delete(segment_id, port_id)
except Exception as ex: except Exception as ex:
LOG.error("Failed to delete port %(id)s on NSX backend " LOG.error("Failed to delete port %(id)s on NSX backend "
"due to %(e)s", {'id': port_id, 'e': ex}) "due to %(e)s", {'id': port_id, 'e': ex})
# Do not fail the neutron action # Do not fail the neutron action
def _update_port_on_backend(self, context, lport_id, def _update_port_on_backend(self, context, lport_id,
original_port, updated_port): original_port, updated_port,
is_psec_on):
# For now port create and update are the same # For now port create and update are the same
# Update might evolve with more features # Update might evolve with more features
return self._create_port_on_backend(context, updated_port) return self._create_port_on_backend(context, updated_port, is_psec_on)
def update_port(self, context, port_id, port): def update_port(self, context, port_id, port):
with db_api.CONTEXT_WRITER.using(context): with db_api.CONTEXT_WRITER.using(context):
@ -740,6 +812,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
original_port = super(NsxPolicyPlugin, self).get_port( original_port = super(NsxPolicyPlugin, self).get_port(
context, port_id) context, port_id)
port_data = port['port'] port_data = port['port']
self._validate_update_port(context, port_id, original_port,
port_data)
validate_port_sec = self._should_validate_port_sec_on_update_port( validate_port_sec = self._should_validate_port_sec_on_update_port(
port_data) port_data)
is_external_net = self._network_is_external( is_external_net = self._network_is_external(
@ -784,14 +858,23 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
vif_type=self._vif_type_by_vnic_type(direct_vnic_type)) vif_type=self._vif_type_by_vnic_type(direct_vnic_type))
self._extend_nsx_port_dict_binding(context, updated_port) self._extend_nsx_port_dict_binding(context, updated_port)
#TODO(asarfaty): Handle mac learning mac_learning_state = updated_port.get(mac_ext.MAC_LEARNING)
if mac_learning_state is not None:
if port_security and mac_learning_state:
msg = _('Mac learning requires that port security be '
'disabled')
LOG.error(msg)
raise n_exc.InvalidInput(error_message=msg)
self._update_mac_learning_state(context, port_id,
mac_learning_state)
# update the port in the backend, only if it exists in the DB # update the port in the backend, only if it exists in the DB
# (i.e not external net) # (i.e not external net)
if not is_external_net: if not is_external_net:
try: try:
self._update_port_on_backend(context, port_id, self._update_port_on_backend(context, port_id,
original_port, updated_port) original_port, updated_port,
port_security)
except Exception as e: except Exception as e:
LOG.error('Failed to update port %(id)s on NSX ' LOG.error('Failed to update port %(id)s on NSX '
'backend. Exception: %(e)s', 'backend. Exception: %(e)s',

View File

@ -1842,16 +1842,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
LOG.error(err_msg) LOG.error(err_msg)
raise n_exc.InvalidInput(error_message=err_msg) raise n_exc.InvalidInput(error_message=err_msg)
def _is_excluded_port(self, device_owner, port_security):
if device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF:
return False
if device_owner == const.DEVICE_OWNER_DHCP:
if not cfg.CONF.nsx_v3.native_dhcp_metadata:
return True
elif not port_security:
return True
return False
def _create_port_at_the_backend(self, context, port_data, def _create_port_at_the_backend(self, context, port_data,
l2gw_port_check, psec_is_on, l2gw_port_check, psec_is_on,
is_ens_tz_port): is_ens_tz_port):
@ -2077,16 +2067,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
LOG.warning(err_msg) LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg) raise n_exc.InvalidInput(error_message=err_msg)
def _assert_on_port_admin_state(self, port_data, device_owner):
"""Do not allow changing the admin state of some ports"""
if (device_owner == l3_db.DEVICE_OWNER_ROUTER_INTF or
device_owner == l3_db.DEVICE_OWNER_ROUTER_GW):
if port_data.get("admin_state_up") is False:
err_msg = _("admin_state_up=False router ports are not "
"supported")
LOG.warning(err_msg)
raise n_exc.InvalidInput(error_message=err_msg)
def _filter_ipv4_dhcp_fixed_ips(self, context, fixed_ips): def _filter_ipv4_dhcp_fixed_ips(self, context, fixed_ips):
ips = [] ips = []
for fixed_ip in fixed_ips: for fixed_ip in fixed_ips:

View File

@ -34,6 +34,7 @@ from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources from neutron_lib.callbacks import resources
from neutron_lib import constants from neutron_lib import constants
from neutron_lib import context from neutron_lib import context
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory from neutron_lib.plugins import directory
from vmware_nsx.common import utils from vmware_nsx.common import utils
@ -402,6 +403,141 @@ class NsxPTestPorts(test_db_base_plugin_v2.TestPortsV2,
self.assertEqual(res['port']['fixed_ips'], self.assertEqual(res['port']['fixed_ips'],
data['port']['fixed_ips']) data['port']['fixed_ips'])
def test_create_port_with_mac_learning_true(self):
plugin = directory.get_plugin()
ctx = context.get_admin_context()
with self.network() as network:
data = {'port': {
'network_id': network['network']['id'],
'tenant_id': self._tenant_id,
'name': 'qos_port',
'admin_state_up': True,
'device_id': 'fake_device',
'device_owner': 'fake_owner',
'fixed_ips': [],
'port_security_enabled': False,
'mac_address': '00:00:00:00:00:01',
'mac_learning_enabled': True}
}
port = plugin.create_port(ctx, data)
self.assertTrue(port['mac_learning_enabled'])
def test_create_port_with_mac_learning_false(self):
plugin = directory.get_plugin()
ctx = context.get_admin_context()
with self.network() as network:
data = {'port': {
'network_id': network['network']['id'],
'tenant_id': self._tenant_id,
'name': 'qos_port',
'admin_state_up': True,
'device_id': 'fake_device',
'device_owner': 'fake_owner',
'fixed_ips': [],
'port_security_enabled': False,
'mac_address': '00:00:00:00:00:01',
'mac_learning_enabled': False}
}
port = plugin.create_port(ctx, data)
self.assertFalse(port['mac_learning_enabled'])
def test_update_port_with_mac_learning_true(self):
plugin = directory.get_plugin()
ctx = context.get_admin_context()
with self.network() as network:
data = {'port': {
'network_id': network['network']['id'],
'tenant_id': self._tenant_id,
'name': 'qos_port',
'admin_state_up': True,
'device_id': 'fake_device',
'device_owner': 'fake_owner',
'fixed_ips': [],
'port_security_enabled': False,
'mac_address': '00:00:00:00:00:01'}
}
port = plugin.create_port(ctx, data)
data['port']['mac_learning_enabled'] = True
update_res = plugin.update_port(ctx, port['id'], data)
self.assertTrue(update_res['mac_learning_enabled'])
def test_update_port_with_mac_learning_false(self):
plugin = directory.get_plugin()
ctx = context.get_admin_context()
with self.network() as network:
data = {'port': {
'network_id': network['network']['id'],
'tenant_id': self._tenant_id,
'name': 'qos_port',
'admin_state_up': True,
'device_id': 'fake_device',
'device_owner': 'fake_owner',
'fixed_ips': [],
'port_security_enabled': False,
'mac_address': '00:00:00:00:00:01'}
}
port = plugin.create_port(ctx, data)
data['port']['mac_learning_enabled'] = False
update_res = plugin.update_port(ctx, port['id'], data)
self.assertFalse(update_res['mac_learning_enabled'])
def test_update_port_with_mac_learning_failes(self):
plugin = directory.get_plugin()
ctx = context.get_admin_context()
with self.network() as network:
data = {'port': {
'network_id': network['network']['id'],
'tenant_id': self._tenant_id,
'name': 'qos_port',
'admin_state_up': True,
'device_id': 'fake_device',
'device_owner': constants.DEVICE_OWNER_FLOATINGIP,
'fixed_ips': [],
'port_security_enabled': False,
'mac_address': '00:00:00:00:00:01'}
}
port = plugin.create_port(ctx, data)
data['port']['mac_learning_enabled'] = True
self.assertRaises(
n_exc.InvalidInput,
plugin.update_port, ctx, port['id'], data)
def _create_l3_ext_network(
self, physical_network=DEFAULT_TIER0_ROUTER_UUID):
name = 'l3_ext_net'
net_type = utils.NetworkTypes.L3_EXT
providernet_args = {pnet.NETWORK_TYPE: net_type,
pnet.PHYSICAL_NETWORK: physical_network}
return self.network(name=name,
router__external=True,
providernet_args=providernet_args,
arg_list=(pnet.NETWORK_TYPE,
pnet.PHYSICAL_NETWORK))
def test_fail_create_port_with_ext_net(self):
expected_error = 'InvalidInput'
with self._create_l3_ext_network() as network:
with self.subnet(network=network, cidr='10.0.0.0/24'):
device_owner = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'X'
res = self._create_port(self.fmt,
network['network']['id'],
exc.HTTPBadRequest.code,
device_owner=device_owner)
data = self.deserialize(self.fmt, res)
self.assertEqual(expected_error, data['NeutronError']['type'])
def test_fail_update_port_with_ext_net(self):
with self._create_l3_ext_network() as network:
with self.subnet(network=network, cidr='10.0.0.0/24') as subnet:
with self.port(subnet=subnet) as port:
device_owner = constants.DEVICE_OWNER_COMPUTE_PREFIX + 'X'
data = {'port': {'device_owner': device_owner}}
req = self.new_update_request('ports',
data, port['port']['id'])
res = req.get_response(self.api)
self.assertEqual(exc.HTTPBadRequest.code,
res.status_int)
class NsxPTestSecurityGroup(NsxPPluginTestCaseMixin, class NsxPTestSecurityGroup(NsxPPluginTestCaseMixin,
test_securitygroup.TestSecurityGroups, test_securitygroup.TestSecurityGroups,