From db43ace22ffd684fa38a1d4f3216ef7bd0740881 Mon Sep 17 00:00:00 2001 From: Anna Khmelnitsky Date: Thu, 25 Oct 2018 21:18:46 -0700 Subject: [PATCH] NSX|P: Handle port security Change-Id: I4e8a64f5730f359f5b5b4a6c93da13e123ac6321 --- vmware_nsx/plugins/common/plugin.py | 50 --------- vmware_nsx/plugins/common_v3/__init__.py | 0 vmware_nsx/plugins/common_v3/plugin.py | 129 +++++++++++++++++++++++ vmware_nsx/plugins/nsx_p/plugin.py | 64 +++++++---- vmware_nsx/plugins/nsx_v3/plugin.py | 45 +------- 5 files changed, 176 insertions(+), 112 deletions(-) create mode 100644 vmware_nsx/plugins/common_v3/__init__.py create mode 100644 vmware_nsx/plugins/common_v3/plugin.py diff --git a/vmware_nsx/plugins/common/plugin.py b/vmware_nsx/plugins/common/plugin.py index 2a6b44cd79..b19128a251 100644 --- a/vmware_nsx/plugins/common/plugin.py +++ b/vmware_nsx/plugins/common/plugin.py @@ -23,7 +23,6 @@ from neutron.db import l3_attrs_db from neutron.db import l3_db from neutron.db import models_v2 from neutron_lib.api.definitions import address_scope as ext_address_scope -from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import availability_zone as az_def from neutron_lib.api.definitions import external_net as extnet_apidef from neutron_lib.api.definitions import network as net_def @@ -39,7 +38,6 @@ from neutron_lib import constants from neutron_lib import context as n_context from neutron_lib.db import api as db_api from neutron_lib import exceptions as n_exc -from neutron_lib.exceptions import allowedaddresspairs as addr_exc from neutron_lib.plugins import directory from neutron_lib.services.qos import constants as qos_consts from neutron_lib.utils import net @@ -48,7 +46,6 @@ from vmware_nsx._i18n import _ from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import utils from vmware_nsx.extensions import maclearning as mac_ext -from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as sg_prefix from vmware_nsx.services.qos.common import utils as qos_com_utils from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils @@ -600,59 +597,12 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2, self.set_extra_attr_value(context, router_db, extra_attr, r[extra_attr]) - def _get_interface_network(self, context, interface_info): - is_port, is_sub = self._validate_interface_info(interface_info) - if is_port: - net_id = self.get_port(context, - interface_info['port_id'])['network_id'] - elif is_sub: - net_id = self.get_subnet(context, - interface_info['subnet_id'])['network_id'] - return net_id - - def _fix_sg_rule_dict_ips(self, sg_rule): - # 0.0.0.0/# is not a valid entry for local and remote so we need - # to change this to None - if (sg_rule.get('remote_ip_prefix') and - sg_rule['remote_ip_prefix'].startswith('0.0.0.0/')): - sg_rule['remote_ip_prefix'] = None - if (sg_rule.get(sg_prefix.LOCAL_IP_PREFIX) and - validators.is_attr_set(sg_rule[sg_prefix.LOCAL_IP_PREFIX]) and - sg_rule[sg_prefix.LOCAL_IP_PREFIX].startswith('0.0.0.0/')): - sg_rule[sg_prefix.LOCAL_IP_PREFIX] = None - - def _validate_interface_address_scope(self, context, - router_db, interface_info): - gw_network_id = (router_db.gw_port.network_id if router_db.gw_port - else None) - - subnet = self.get_subnet(context, interface_info['subnet_ids'][0]) - if not router_db.enable_snat and gw_network_id: - self._validate_address_scope_for_router_interface( - context.elevated(), router_db.id, gw_network_id, subnet['id']) - def _validate_ipv4_address_pairs(self, address_pairs): for pair in address_pairs: ip = pair.get('ip_address') if not utils.is_ipv4_ip_address(ip): raise nsx_exc.InvalidIPAddress(ip_address=ip) - # NSXv3 and Policy only - def _create_port_address_pairs(self, context, port_data): - (port_security, has_ip) = self._determine_port_security_and_has_ip( - context, port_data) - - address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS) - if validators.is_attr_set(address_pairs): - if not port_security: - raise addr_exc.AddressPairAndPortSecurityRequired() - else: - self._validate_ipv4_address_pairs(address_pairs) - self._process_create_allowed_address_pairs(context, port_data, - address_pairs) - else: - port_data[addr_apidef.ADDRESS_PAIRS] = [] - def get_housekeeper(self, context, name, fields=None): # run the job in readonly mode and get the results self.housekeeper.run(context, name, readonly=True) diff --git a/vmware_nsx/plugins/common_v3/__init__.py b/vmware_nsx/plugins/common_v3/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/plugins/common_v3/plugin.py b/vmware_nsx/plugins/common_v3/plugin.py new file mode 100644 index 0000000000..d6242192ef --- /dev/null +++ b/vmware_nsx/plugins/common_v3/plugin.py @@ -0,0 +1,129 @@ +# 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 neutron.extensions import securitygroup as ext_sg +from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef +from neutron_lib.api.definitions import port_security as psec +from neutron_lib.api import validators +from neutron_lib.exceptions import allowedaddresspairs as addr_exc +from neutron_lib.exceptions import port_security as psec_exc + +from vmware_nsx.common import exceptions as nsx_exc +from vmware_nsx.common import utils +from vmware_nsx.db import extended_security_group as extended_sec +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.plugins.common import plugin + +LOG = logging.getLogger(__name__) + + +class NsxPluginV3Base(plugin.NsxPluginBase, + extended_sec.ExtendedSecurityGroupPropertiesMixin): + """Common methods for NSX-V3 plugins""" + + def _get_interface_network(self, context, interface_info): + is_port, is_sub = self._validate_interface_info(interface_info) + if is_port: + net_id = self.get_port(context, + interface_info['port_id'])['network_id'] + elif is_sub: + net_id = self.get_subnet(context, + interface_info['subnet_id'])['network_id'] + return net_id + + def _fix_sg_rule_dict_ips(self, sg_rule): + # 0.0.0.0/# is not a valid entry for local and remote so we need + # to change this to None + if (sg_rule.get('remote_ip_prefix') and + sg_rule['remote_ip_prefix'].startswith('0.0.0.0/')): + sg_rule['remote_ip_prefix'] = None + if (sg_rule.get(sg_prefix.LOCAL_IP_PREFIX) and + validators.is_attr_set(sg_rule[sg_prefix.LOCAL_IP_PREFIX]) and + sg_rule[sg_prefix.LOCAL_IP_PREFIX].startswith('0.0.0.0/')): + sg_rule[sg_prefix.LOCAL_IP_PREFIX] = None + + def _validate_interface_address_scope(self, context, + router_db, interface_info): + gw_network_id = (router_db.gw_port.network_id if router_db.gw_port + else None) + + subnet = self.get_subnet(context, interface_info['subnet_ids'][0]) + if not router_db.enable_snat and gw_network_id: + self._validate_address_scope_for_router_interface( + context.elevated(), router_db.id, gw_network_id, subnet['id']) + + def _validate_ipv4_address_pairs(self, address_pairs): + for pair in address_pairs: + ip = pair.get('ip_address') + if not utils.is_ipv4_ip_address(ip): + raise nsx_exc.InvalidIPAddress(ip_address=ip) + + def _create_port_address_pairs(self, context, port_data): + (port_security, has_ip) = self._determine_port_security_and_has_ip( + context, port_data) + + address_pairs = port_data.get(addr_apidef.ADDRESS_PAIRS) + if validators.is_attr_set(address_pairs): + if not port_security: + raise addr_exc.AddressPairAndPortSecurityRequired() + else: + self._validate_ipv4_address_pairs(address_pairs) + self._process_create_allowed_address_pairs(context, port_data, + address_pairs) + else: + port_data[addr_apidef.ADDRESS_PAIRS] = [] + + def _provider_sgs_specified(self, port_data): + # checks if security groups were updated adding/modifying + # security groups, port security is set and port has ip + provider_sgs_specified = (validators.is_attr_set( + port_data.get(provider_sg.PROVIDER_SECURITYGROUPS)) and + port_data.get(provider_sg.PROVIDER_SECURITYGROUPS) != []) + return provider_sgs_specified + + def _create_port_preprocess_security( + self, context, port, port_data, neutron_db, is_ens_tz_port): + (port_security, has_ip) = self._determine_port_security_and_has_ip( + context, port_data) + port_data[psec.PORTSECURITY] = port_security + # No port security is allowed if the port belongs to an ENS TZ + if (port_security and is_ens_tz_port and + not self._ens_psec_supported()): + raise nsx_exc.NsxENSPortSecurity() + self._process_port_port_security_create( + context, port_data, neutron_db) + + # allowed address pair checks + self._create_port_address_pairs(context, port_data) + + if port_security and has_ip: + self._ensure_default_security_group_on_port(context, port) + (sgids, psgids) = self._get_port_security_groups_lists( + context, port) + elif (self._check_update_has_security_groups({'port': port_data}) or + self._provider_sgs_specified(port_data) or + self._get_provider_security_groups_on_port(context, port)): + LOG.error("Port has conflicting port security status and " + "security groups") + raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups() + else: + sgids = psgids = [] + port_data[ext_sg.SECURITYGROUPS] = ( + self._get_security_groups_on_port(context, port)) + return port_security, has_ip, sgids, psgids diff --git a/vmware_nsx/plugins/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index 3ae9674218..22eb407ef1 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -39,6 +39,7 @@ from neutron.db import portsecurity_db from neutron.db import securitygroups_db from neutron.db import vlantransparent_db from neutron.extensions import providernet +from neutron.extensions import securitygroup as ext_sg from neutron.quota import resource_registry from neutron_lib.api.definitions import allowedaddresspairs as addr_apidef from neutron_lib.api.definitions import external_net @@ -62,14 +63,13 @@ from vmware_nsx.common import locking from vmware_nsx.common import managers from vmware_nsx.common import utils from vmware_nsx.db import db as nsx_db -from vmware_nsx.db import extended_security_group as extend_sg 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.extensions import projectpluginmap 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 securitygrouplogging as sg_logging -from vmware_nsx.plugins.common import plugin as nsx_plugin_common +from vmware_nsx.plugins.common_v3 import plugin as nsx_plugin_common from vmware_nsx.plugins.nsx_v3 import utils as v3_utils from vmware_nsxlib.v3 import exceptions as nsx_lib_exc @@ -93,9 +93,8 @@ NSX_P_PROVIDER_SECTION_CATEGORY = policy_constants.CATEGORY_INFRASTRUCTURE @resource_extend.has_resource_extenders class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, addr_pair_db.AllowedAddressPairsMixin, - nsx_plugin_common.NsxPluginBase, + nsx_plugin_common.NsxPluginV3Base, extend_sg_rule.ExtendedSecurityGroupRuleMixin, - extend_sg.ExtendedSecurityGroupPropertiesMixin, securitygroups_db.SecurityGroupDbMixin, external_net_db.External_net_db_mixin, extraroute_db.ExtraRoute_db_mixin, @@ -253,7 +252,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, webob.exc.HTTPBadRequest, }) - def _create_network_at_the_backend(self, context, net_data): + def _create_network_on_backend(self, context, net_data): # TODO(annak): provider network net_data['id'] = net_data.get('id') or uuidutils.generate_uuid() @@ -306,7 +305,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # Create the backend NSX network if not is_external_net: try: - self._create_network_at_the_backend(context, created_net) + self._create_network_on_backend(context, created_net) except Exception as e: LOG.exception("Failed to create NSX network network: %s", e) with excutils.save_and_reraise_exception(): @@ -402,32 +401,47 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return updated_subnet - def _build_address_bindings(self, port): + def _build_port_address_bindings(self, context, port_data): + psec_on, has_ip = self._determine_port_security_and_has_ip(context, + port_data) + if not psec_on: + return None + address_bindings = [] - for fixed_ip in port['fixed_ips']: + for fixed_ip in port_data['fixed_ips']: if netaddr.IPNetwork(fixed_ip['ip_address']).version != 4: #TODO(annak): enable when IPv6 is supported continue binding = self.nsxpolicy.segment_port.build_address_binding( - fixed_ip['ip_address'], port['mac_address']) + fixed_ip['ip_address'], port_data['mac_address']) address_bindings.append(binding) - for pair in port.get(addr_apidef.ADDRESS_PAIRS): + for pair in port_data.get(addr_apidef.ADDRESS_PAIRS): binding = self.nsxpolicy.segment_port.build_address_binding( pair['ip_address'], pair['mac_address']) address_bindings.append(binding) return address_bindings - def _create_port_at_the_backend(self, context, port_data): + def _build_port_tags(self, port_data): + sec_groups = port_data.get(ext_sg.SECURITYGROUPS, []) + sec_groups += port_data.get(provider_sg.PROVIDER_SECURITYGROUPS, []) + + tags = [] + for sg in sec_groups: + tags = nsxlib_utils.add_v3_tag(tags, + NSX_P_SECURITY_GROUP_TAG, + sg) + + return tags + + def _create_port_on_backend(self, context, port_data): # TODO(annak): admin_state not supported by policy # TODO(annak): handle exclude list # TODO(annak): switching profiles when supported name = self._build_port_name(context, port_data) - psec, has_ip = self._determine_port_security_and_has_ip(context, - port_data) - address_bindings = (self._build_address_bindings(port_data) - if psec else None) + address_bindings = self._build_port_address_bindings( + context, port_data) device_owner = port_data.get('device_owner') vif_id = None if device_owner and device_owner != l3_db.DEVICE_OWNER_ROUTER_INTF: @@ -435,6 +449,10 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, tags = self.nsxpolicy.build_v3_api_version_project_tag( context.tenant_name) + tags = self._build_port_tags(port_data) + tags.append(self.nsxpolicy.build_v3_api_version_project_tag( + context.tenant_name)) + self.nsxpolicy.segment_port.create_or_overwrite( name, port_data['network_id'], @@ -471,11 +489,18 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, neutron_db = self.base_create_port(context, port) port["port"].update(neutron_db) - self._create_port_address_pairs(context, port_data) + (is_psec_on, has_ip, sgids, psgids) = ( + self._create_port_preprocess_security(context, port, + port_data, neutron_db, + False)) + + self._process_port_create_security_group(context, port_data, sgids) + self._process_port_create_provider_security_group( + context, port_data, psgids) if not is_external_net: try: - self._create_port_at_the_backend(context, port_data) + self._create_port_on_backend(context, port_data) except Exception as e: with excutils.save_and_reraise_exception(): LOG.error('Failed to create port %(id)s on NSX ' @@ -512,8 +537,9 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, def _update_port_on_backend(self, context, lport_id, original_port, updated_port): - #TODO(asarfaty): implement - pass + # For now port create and update are the same + # Update might evolve with more features + return self._create_port_on_backend(context, updated_port) def update_port(self, context, id, port): with db_api.CONTEXT_WRITER.using(context): diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 95efd97ab1..b548074256 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -97,7 +97,6 @@ from vmware_nsx.common import managers from vmware_nsx.common import nsx_constants from vmware_nsx.common import utils from vmware_nsx.db import db as nsx_db -from vmware_nsx.db import extended_security_group 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 nsx_portbindings_db as pbin_db @@ -109,7 +108,7 @@ from vmware_nsx.extensions import projectpluginmap from vmware_nsx.extensions import providersecuritygroup as provider_sg from vmware_nsx.extensions import securitygrouplogging as sg_logging from vmware_nsx.plugins.common.housekeeper import housekeeper -from vmware_nsx.plugins.common import plugin as nsx_plugin_common +from vmware_nsx.plugins.common_v3 import plugin as nsx_plugin_common from vmware_nsx.plugins.nsx import utils as tvd_utils from vmware_nsx.plugins.nsx_v3 import availability_zones as nsx_az from vmware_nsx.plugins.nsx_v3 import utils as v3_utils @@ -161,9 +160,8 @@ NSX_V3_OS_DFW_UUID = '00000000-def0-0000-0fed-000000000000' # the classes into a new class to handle the order correctly. @resource_extend.has_resource_extenders class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, - extended_security_group.ExtendedSecurityGroupPropertiesMixin, addr_pair_db.AllowedAddressPairsMixin, - nsx_plugin_common.NsxPluginBase, + nsx_plugin_common.NsxPluginV3Base, extend_sg_rule.ExtendedSecurityGroupRuleMixin, securitygroups_db.SecurityGroupDbMixin, external_net_db.External_net_db_mixin, @@ -2411,14 +2409,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, return result - def _provider_sgs_specified(self, port_data): - # checks if security groups were updated adding/modifying - # security groups, port security is set and port has ip - provider_sgs_specified = (validators.is_attr_set( - port_data.get(provider_sg.PROVIDER_SECURITYGROUPS)) and - port_data.get(provider_sg.PROVIDER_SECURITYGROUPS) != []) - return provider_sgs_specified - def _get_net_tz(self, context, net_id): mappings = nsx_db.get_nsx_switch_ids(context.session, net_id) if mappings: @@ -2443,37 +2433,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, # Check the host-switch-mode of the TZ connected to the ports network return self._is_ens_tz_net(context, port_data['network_id']) - def _create_port_preprocess_security( - self, context, port, port_data, neutron_db, is_ens_tz_port): - (port_security, has_ip) = self._determine_port_security_and_has_ip( - context, port_data) - port_data[psec.PORTSECURITY] = port_security - # No port security is allowed if the port belongs to an ENS TZ - if (port_security and is_ens_tz_port and - not self._ens_psec_supported()): - raise nsx_exc.NsxENSPortSecurity() - self._process_port_port_security_create( - context, port_data, neutron_db) - - # allowed address pair checks - self._create_port_address_pairs(context, port_data) - - if port_security and has_ip: - self._ensure_default_security_group_on_port(context, port) - (sgids, psgids) = self._get_port_security_groups_lists( - context, port) - elif (self._check_update_has_security_groups({'port': port_data}) or - self._provider_sgs_specified(port_data) or - self._get_provider_security_groups_on_port(context, port)): - LOG.error("Port has conflicting port security status and " - "security groups") - raise psec_exc.PortSecurityAndIPRequiredForSecurityGroups() - else: - sgids = psgids = [] - port_data[ext_sg.SECURITYGROUPS] = ( - self._get_security_groups_on_port(context, port)) - return port_security, has_ip, sgids, psgids - def _assert_on_dhcp_relay_without_router(self, context, port_data, original_port=None): # Prevent creating/updating port with device owner prefix 'compute'