diff --git a/vmware_nsx/common/config.py b/vmware_nsx/common/config.py index 1daa80f2bd..2f08d6412e 100644 --- a/vmware_nsx/common/config.py +++ b/vmware_nsx/common/config.py @@ -336,6 +336,14 @@ nsx_v3_and_p = [ cfg.IntOpt('redirects', default=2, help=_('Number of times a HTTP redirect should be followed.')), + cfg.BoolOpt('log_security_groups_blocked_traffic', + default=False, + help=_("(Optional) Indicates whether distributed-firewall " + "rule for security-groups blocked traffic is logged.")), + cfg.BoolOpt('log_security_groups_allowed_traffic', + default=False, + help=_("(Optional) Indicates whether distributed-firewall " + "security-groups rules are logged.")), ] nsx_v3_opts = nsx_v3_and_p + [ @@ -422,14 +430,6 @@ nsx_v3_opts = nsx_v3_and_p + [ help=_("(Optional) This is the name or UUID of the NSX dhcp " "relay service that will be used to enable DHCP relay " "on router ports.")), - cfg.BoolOpt('log_security_groups_blocked_traffic', - default=False, - help=_("(Optional) Indicates whether distributed-firewall " - "rule for security-groups blocked traffic is logged.")), - cfg.BoolOpt('log_security_groups_allowed_traffic', - default=False, - help=_("(Optional) Indicates whether distributed-firewall " - "security-groups rules are logged.")), cfg.ListOpt('availability_zones', default=[], help=_('Optional parameter defining the networks availability ' diff --git a/vmware_nsx/db/extended_security_group_rule.py b/vmware_nsx/db/extended_security_group_rule.py index fc39dd209e..ccc3e970e7 100644 --- a/vmware_nsx/db/extended_security_group_rule.py +++ b/vmware_nsx/db/extended_security_group_rule.py @@ -16,6 +16,7 @@ from neutron_lib.db import model_base import sqlalchemy as sa from sqlalchemy import orm +from sqlalchemy.orm import exc from neutron.db import _resource_extend as resource_extend from neutron.db import api as db_api @@ -90,3 +91,13 @@ class ExtendedSecurityGroupRuleMixin(object): sg_rule_db.ext_properties.local_ip_prefix) else: sg_rule_res[ext_local_ip.LOCAL_IP_PREFIX] = None + + def _get_security_group_rule_local_ip(self, context, rule_id): + with db_api.context_manager.reader.using(context): + try: + prop = context.session.query( + NsxExtendedSecurityGroupRuleProperties).filter_by( + rule_id=rule_id).one() + except exc.NoResultFound: + return False + return prop[ext_local_ip.LOCAL_IP_PREFIX] diff --git a/vmware_nsx/plugins/common/plugin.py b/vmware_nsx/plugins/common/plugin.py index 0cde95c894..0c526053ac 100644 --- a/vmware_nsx/plugins/common/plugin.py +++ b/vmware_nsx/plugins/common/plugin.py @@ -45,6 +45,7 @@ from neutron_lib.utils import net from vmware_nsx._i18n import _ from vmware_nsx.common import exceptions as nsx_exc 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 @@ -583,6 +584,17 @@ class NsxPluginBase(db_base_plugin_v2.NeutronDbPluginV2, 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 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/nsx_p/plugin.py b/vmware_nsx/plugins/nsx_p/plugin.py index 5fdec62cdf..40c01a5b05 100644 --- a/vmware_nsx/plugins/nsx_p/plugin.py +++ b/vmware_nsx/plugins/nsx_p/plugin.py @@ -13,9 +13,12 @@ # License for the specific language governing permissions and limitations # under the License. +import sys + from oslo_config import cfg from oslo_log import log from oslo_utils import excutils +from oslo_utils import uuidutils import webob.exc from neutron.db import _resource_extend as resource_extend @@ -55,18 +58,34 @@ from vmware_nsx.common import exceptions as nsx_exc from vmware_nsx.common import l3_rpc_agent_api 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.nsx_v3 import utils as v3_utils 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 policy_constants from vmware_nsxlib.v3 import utils as nsxlib_utils LOG = log.getLogger(__name__) +NSX_P_SECURITY_GROUP_TAG = 'os-security-group' +NSX_P_GLOBAL_DOMAIN_ID = policy_constants.DEFAULT_DOMAIN +NSX_P_DEFAULT_GROUP = 'os_default_group' +NSX_P_DEFAULT_GROUP_DESC = 'Default Group for the openstack plugin' +NSX_P_DEFAULT_SECTION = 'os_default_section' +NSX_P_DEFAULT_SECTION_DESC = ('This section is handled by OpenStack to ' + 'contain default rules on security-groups.') +NSX_P_DEFAULT_SECTION_CATEGORY = policy_constants.CATEGORY_APPLICATION +NSX_P_REGULAR_SECTION_CATEGORY = policy_constants.CATEGORY_ENVIRONMENT +NSX_P_PROVIDER_SECTION_CATEGORY = policy_constants.CATEGORY_INFRASTRUCTURE @resource_extend.has_resource_extenders @@ -74,6 +93,7 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, addr_pair_db.AllowedAddressPairsMixin, nsx_plugin_common.NsxPluginBase, extend_sg_rule.ExtendedSecurityGroupRuleMixin, + extend_sg.ExtendedSecurityGroupPropertiesMixin, securitygroups_db.SecurityGroupDbMixin, external_net_db.External_net_db_mixin, extraroute_db.ExtraRoute_db_mixin, @@ -106,6 +126,8 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, "extraroute", "router", "subnet_allocation", + "security-group-logging", + "provider-security-group", "port-security-groups-filtering"] @resource_registry.tracked_resources( @@ -156,10 +178,6 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, "%(ver)s") % {'ver': self._nsx_version}) raise nsx_exc.NsxPluginException(err_msg=msg) - def _prepare_default_rules(self): - #TODO(asarfaty): implement - pass - @staticmethod def plugin_type(): return projectpluginmap.NsxPlugins.NSX_P @@ -618,47 +636,381 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, super(NsxPolicyPlugin, self).disassociate_floatingips( context, port_id, do_notify=False) - def _create_security_group_backend_resources(self, secgroup): - # TODO(asarfaty): implement - pass + def _prepare_default_rules(self): + """Create a default group & communication map in the default domain""" + # Run this code only on one worker at the time + with locking.LockManager.get_lock('nsx_p_prepare_default_rules'): + # Return if the objects were already created + try: + self.nsxpolicy.comm_map.get(NSX_P_GLOBAL_DOMAIN_ID, + NSX_P_DEFAULT_SECTION) + self.nsxpolicy.group.get(NSX_P_GLOBAL_DOMAIN_ID, + NSX_P_DEFAULT_GROUP) + except nsx_lib_exc.ResourceNotFound: + # prevent logger from logging this exception + sys.exc_clear() + LOG.info("Going to create default group & " + "communication map under the default domain") + else: + return + + # Create the default group membership criteria to match all neutron + # ports by scope & tag + scope_and_tag = "%s:%s" % (NSX_P_SECURITY_GROUP_TAG, + NSX_P_DEFAULT_SECTION) + conditions = [self.nsxpolicy.group.build_condition( + cond_val=scope_and_tag, + cond_key=policy_constants.CONDITION_KEY_TAG, + cond_member_type=policy_constants.CONDITION_MEMBER_PORT)] + # Create the default OpenStack group + # (This will not fail if the group already exists) + try: + self.nsxpolicy.group.create_or_overwrite_with_conditions( + name=NSX_P_DEFAULT_GROUP, + domain_id=NSX_P_GLOBAL_DOMAIN_ID, + group_id=NSX_P_DEFAULT_GROUP, + description=NSX_P_DEFAULT_GROUP_DESC, + conditions=conditions) + + except Exception as e: + msg = (_("Failed to create NSX default group: %(e)s") % { + 'e': e}) + raise nsx_exc.NsxPluginException(err_msg=msg) + + # create default section and rules + logged = cfg.CONF.nsx_p.log_security_groups_blocked_traffic + rule_id = 1 + dhcp_client_rule = self.nsxpolicy.comm_map.build_entry( + 'DHCP Reply', NSX_P_GLOBAL_DOMAIN_ID, + NSX_P_DEFAULT_SECTION, + rule_id, sequence_number=rule_id, + service_ids=['DHCP-Client'], + action=policy_constants.ACTION_ALLOW, + source_groups=None, + dest_groups=[NSX_P_DEFAULT_GROUP], + direction=nsxlib_consts.IN, + logged=logged) + rule_id += 1 + dhcp_server_rule = self.nsxpolicy.comm_map.build_entry( + 'DHCP Request', NSX_P_GLOBAL_DOMAIN_ID, + NSX_P_DEFAULT_SECTION, + rule_id, sequence_number=rule_id, + service_ids=['DHCP-Server'], + action=policy_constants.ACTION_ALLOW, + source_groups=[NSX_P_DEFAULT_GROUP], + dest_groups=None, + direction=nsxlib_consts.OUT, + logged=logged) + rule_id += 1 + block_rule = self.nsxpolicy.comm_map.build_entry( + 'Block All', NSX_P_GLOBAL_DOMAIN_ID, + NSX_P_DEFAULT_SECTION, + rule_id, sequence_number=rule_id, service_ids=None, + action=policy_constants.ACTION_DENY, + source_groups=None, + dest_groups=[NSX_P_DEFAULT_GROUP], + direction=nsxlib_consts.IN_OUT, + logged=logged) + rules = [dhcp_client_rule, dhcp_server_rule, block_rule] + try: + # This will not fail if the map already exists + self.nsxpolicy.comm_map.create_with_entries( + name=NSX_P_DEFAULT_SECTION, + domain_id=NSX_P_GLOBAL_DOMAIN_ID, + map_id=NSX_P_DEFAULT_SECTION, + description=NSX_P_DEFAULT_SECTION_DESC, + category=NSX_P_DEFAULT_SECTION_CATEGORY, + entries=rules) + except Exception as e: + msg = (_("Failed to create NSX default communication map: " + "%(e)s") % {'e': e}) + raise nsx_exc.NsxPluginException(err_msg=msg) + + # create exclude port group + # TODO(asarfaty): add this while handling port security disabled + + def _create_security_group_backend_resources(self, secgroup, domain_id): + """Create communication map (=section) and group (=NS group) + + Both will have the security group id as their NSX id. + """ + sg_id = secgroup['id'] + tags = self.nsxpolicy.build_v3_tags_payload( + secgroup, resource_type='os-neutron-secgr-id', + project_name=secgroup['tenant_id']) + nsx_name = utils.get_name_and_uuid(secgroup['name'] or 'securitygroup', + sg_id) + # Create the groups membership criteria for ports by scope & tag + scope_and_tag = "%s:%s" % (NSX_P_SECURITY_GROUP_TAG, sg_id) + condition = self.nsxpolicy.group.build_condition( + cond_val=scope_and_tag, + cond_key=policy_constants.CONDITION_KEY_TAG, + cond_member_type=policy_constants.CONDITION_MEMBER_PORT) + # Create the group + try: + self.nsxpolicy.group.create_or_overwrite_with_conditions( + nsx_name, domain_id, group_id=sg_id, + description=secgroup.get('description'), + conditions=[condition], tags=tags) + except Exception as e: + msg = (_("Failed to create NSX group for SG %(sg)s: " + "%(e)s") % {'sg': sg_id, 'e': e}) + raise nsx_exc.NsxPluginException(err_msg=msg) + + category = NSX_P_REGULAR_SECTION_CATEGORY + if secgroup.get(provider_sg.PROVIDER) is True: + category = NSX_P_PROVIDER_SECTION_CATEGORY + # create the communication map (=section) without and entries (=rules) + try: + self.nsxpolicy.comm_map.create_or_overwrite_map_only( + nsx_name, domain_id, map_id=sg_id, + description=secgroup.get('description'), + tags=tags, category=category) + except Exception as e: + msg = (_("Failed to create NSX communication map for SG %(sg)s: " + "%(e)s") % {'sg': sg_id, 'e': e}) + self.nsxpolicy.group.delete(domain_id, sg_id) + raise nsx_exc.NsxPluginException(err_msg=msg) + + def _get_rule_service_id(self, sg_rule): + """Return the NSX Policy service id matching the SG rule""" + srv = None + l4_protocol = nsxlib_utils.get_l4_protocol_name(sg_rule['protocol']) + srv_name = 'Service for OS rule %s' % sg_rule['id'] + + if l4_protocol in [nsxlib_consts.TCP, nsxlib_consts.UDP]: + # If port_range_min is not specified then we assume all ports are + # matched, relying on neutron to perform validation. + if sg_rule['port_range_min'] is None: + destination_ports = [] + elif sg_rule['port_range_min'] != sg_rule['port_range_max']: + # NSX API requires a non-empty range (e.g - '22-23') + destination_ports = ['%(port_range_min)s-%(port_range_max)s' + % sg_rule] + else: + destination_ports = ['%(port_range_min)s' % sg_rule] + + srv = self.nsxpolicy.service.create_or_overwrite( + srv_name, service_id=sg_rule['id'], + description=sg_rule.get('description'), + protocol=l4_protocol, + dest_ports=destination_ports) + elif l4_protocol in [nsxlib_consts.ICMPV4, nsxlib_consts.ICMPV6]: + # Validate the icmp type & code + version = 4 if l4_protocol == nsxlib_consts.ICMPV4 else 6 + icmp_type = sg_rule['port_range_min'] + icmp_code = sg_rule['port_range_max'] + nsxlib_utils.validate_icmp_params( + icmp_type, icmp_code, icmp_version=version, strict=True) + + srv = self.nsxpolicy.icmp_service.create_or_overwrite( + srv_name, service_id=sg_rule['id'], + description=sg_rule.get('description'), + version=version, + icmp_type=icmp_type, + icmp_code=icmp_code) + elif l4_protocol: + srv = self.nsxpolicy.ip_protocol_service.create_or_overwrite( + srv_name, service_id=sg_rule['id'], + description=sg_rule.get('description'), + protocol_number=l4_protocol) + + if srv: + return srv['id'] + + def _get_sg_rule_remote_ip_group_id(self, sg_rule): + return '%s_remote_group' % sg_rule['id'] + + def _get_sg_rule_local_ip_group_id(self, sg_rule): + return '%s_local_group' % sg_rule['id'] + + def _create_security_group_backend_rule(self, domain_id, map_id, sg_rule, + secgroup_logging): + # The id of the map and group is the same as the security group id + this_group_id = map_id + # There is no rule name in neutron. Using ID instead + nsx_name = sg_rule['id'] + direction = (nsxlib_consts.IN if sg_rule.get('direction') == 'ingress' + else nsxlib_consts.OUT) + self._fix_sg_rule_dict_ips(sg_rule) + source = None + destination = this_group_id + if sg_rule.get('remote_group_id'): + # This is the ID of a security group that already exists, + # so it should be known to the policy manager + source = sg_rule.get('remote_group_id') + elif sg_rule.get('remote_ip_prefix'): + # Create a group for the remote IPs + remote_ip = sg_rule['remote_ip_prefix'] + remote_group_id = self._get_sg_rule_remote_ip_group_id(sg_rule) + tags = self.nsxpolicy.build_v3_tags_payload( + sg_rule, resource_type='os-neutron-sgrule-id', + project_name=sg_rule['tenant_id']) + expr = self.nsxpolicy.group.build_ip_address_expression( + [remote_ip]) + self.nsxpolicy.group.create_or_overwrite_with_conditions( + remote_group_id, domain_id, group_id=remote_group_id, + description='%s for OS rule %s' % (remote_ip, sg_rule['id']), + conditions=[expr], tags=tags) + source = remote_group_id + if sg_rule.get(sg_prefix.LOCAL_IP_PREFIX): + # Create a group for the local ips + local_ip = sg_rule[sg_prefix.LOCAL_IP_PREFIX] + local_group_id = self._get_sg_rule_local_ip_group_id(sg_rule) + tags = self.nsxpolicy.build_v3_tags_payload( + sg_rule, resource_type='os-neutron-sgrule-id', + project_name=sg_rule['tenant_id']) + expr = self.nsxpolicy.group.build_ip_address_expression( + [local_ip]) + self.nsxpolicy.group.create_or_overwrite_with_conditions( + local_group_id, domain_id, group_id=local_group_id, + description='%s for OS rule %s' % (local_ip, sg_rule['id']), + conditions=[expr], tags=tags) + destination = local_group_id + + if direction == nsxlib_consts.OUT: + # Swap source and destination + source, destination = destination, source + + service = self._get_rule_service_id(sg_rule) + logging = (cfg.CONF.nsx_p.log_security_groups_allowed_traffic or + secgroup_logging) + self.nsxpolicy.comm_map.create_entry( + nsx_name, domain_id, map_id, entry_id=sg_rule['id'], + description=sg_rule.get('description'), + service_ids=[service] if service else None, + action=policy_constants.ACTION_ALLOW, + source_groups=[source] if source else None, + dest_groups=[destination] if destination else None, + direction=direction, logged=logging) + + def _create_project_domain(self, project_id): + """Return the NSX domain id of a neutron project + + The ID of the created domain will be the same as the project ID + so there is no need to keep it in the neutron DB + """ + try: + domain = self.nsxpolicy.domain.create_or_overwrite( + name=project_id, + domain_id=project_id, + description="Domain for OS project %s" % project_id) + domain_id = domain['id'] + except Exception as e: + msg = (_("Failed to create NSX domain for project %(proj)s: " + "%(e)s") % {'proj': project_id, 'e': e}) + raise nsx_exc.NsxPluginException(err_msg=msg) + LOG.info("NSX Domain was created for project %s", project_id) + return domain_id def create_security_group(self, context, security_group, default_sg=False): secgroup = security_group['security_group'] + # Make sure the ID is initialized, as it is used for the backend + # objects too + secgroup['id'] = secgroup.get('id') or uuidutils.generate_uuid() + project_id = secgroup['tenant_id'] if not default_sg: - tenant_id = secgroup['tenant_id'] - self._ensure_default_security_group(context, tenant_id) + self._ensure_default_security_group(context, project_id) + else: + # create the NSX policy domain for this new project + self._create_project_domain(project_id) - self._create_security_group_backend_resources(secgroup) + # create the Neutron SG with db_api.context_manager.writer.using(context): - secgroup_db = ( - super(NsxPolicyPlugin, self).create_security_group( - context, security_group, default_sg)) + if secgroup.get(provider_sg.PROVIDER) is True: + secgroup_db = self.create_provider_security_group( + context, security_group) + else: + secgroup_db = ( + super(NsxPolicyPlugin, self).create_security_group( + context, security_group, default_sg)) - # TODO(asarfaty) save NSX->Neutron mappings self._process_security_group_properties_create(context, secgroup_db, secgroup, default_sg) + try: + # Create Group & communication map on the NSX + self._create_security_group_backend_resources( + secgroup, project_id) + + # Add the security-group rules + sg_rules = secgroup_db['security_group_rules'] + secgroup_logging = secgroup.get(sg_logging.LOGGING, False) + for sg_rule in sg_rules: + self._create_security_group_backend_rule( + project_id, secgroup_db['id'], sg_rule, secgroup_logging) + except Exception as e: + with excutils.save_and_reraise_exception(): + LOG.exception("Failed to create backend SG rules " + "for security-group %(name)s (%(id)s), " + "rolling back changes. Error: %(e)s", + {'name': secgroup_db['name'], + 'id': secgroup_db['id'], + 'e': e}) + # rollback SG creation (which will also delete the backend + # objects) + super(NsxPolicyPlugin, self).delete_security_group( + context, secgroup['id']) + return secgroup_db - def update_security_group(self, context, id, security_group): - orig_secgroup = self.get_security_group( - context, id, fields=['id', 'name', 'description']) - LOG.debug("Updating SG %s -> %s", orig_secgroup, - security_group['security_group']) + def update_security_group(self, context, sg_id, security_group): + self._prevent_non_admin_edit_provider_sg(context, sg_id) + sg_data = security_group['security_group'] + + # update the neutron security group with db_api.context_manager.writer.using(context): secgroup_res = super(NsxPolicyPlugin, self).update_security_group( - context, id, security_group) + context, sg_id, security_group) self._process_security_group_properties_update( - context, secgroup_res, security_group['security_group']) - #TODO(asarfaty): Update the NSX backend + context, secgroup_res, sg_data) + + # Update the name and description on NSX backend + if 'name' in sg_data or 'description' in sg_data: + nsx_name = utils.get_name_and_uuid( + secgroup_res['name'] or 'securitygroup', sg_id) + domain_id = secgroup_res['tenant_id'] + try: + self.nsxpolicy.group.create_or_overwrite( + nsx_name, domain_id, sg_id, + description=secgroup_res.get('description')) + self.nsxpolicy.comm_map.create_or_overwrite_map_only( + nsx_name, domain_id, sg_id, + description=secgroup_res.get('description')) + except Exception as e: + LOG.warning("Failed to update SG %s NSX resources: %s", + sg_id, e) + # Go on with the update anyway (it's just the name & desc) + + # If the logging of the SG changed - update the backend rules + if sg_logging.LOGGING in sg_data: + logged = (sg_data[sg_logging.LOGGING] or + cfg.CONF.nsx_p.log_security_groups_allowed_traffic) + self.nsxpolicy.comm_map.update_entries_logged(domain_id, sg_id, + logged) + return secgroup_res - def delete_security_group(self, context, id): - super(NsxPolicyPlugin, self).delete_security_group(context, id) - #TODO(asarfaty): Update the nSX backend + def delete_security_group(self, context, sg_id): + self._prevent_non_admin_edit_provider_sg(context, sg_id) + sg = self.get_security_group(context, sg_id) + + super(NsxPolicyPlugin, self).delete_security_group(context, sg_id) + + domain_id = sg['tenant_id'] + try: + self.nsxpolicy.comm_map.delete(domain_id, sg_id) + self.nsxpolicy.group.delete(domain_id, sg_id) + for rule in sg['security_group_rules']: + self._delete_security_group_rule_backend_resources( + context, domain_id, rule) + except Exception as e: + LOG.warning("Failed to delete SG %s NSX resources: %s", + sg_id, e) + # Go on with the deletion anyway def create_security_group_rule(self, context, security_group_rule): bulk_rule = {'security_group_rules': [security_group_rule]} @@ -666,9 +1018,10 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, def create_security_group_rule_bulk(self, context, security_group_rules): sg_rules = security_group_rules['security_group_rules'] - for r in sg_rules: - # TODO(asarfaty): create rules at the NSX - pass + # Tenant & security group are the same for all rules in the bulk + example_rule = sg_rules[0]['security_group_rule'] + sg_id = example_rule['security_group_id'] + self._prevent_non_admin_edit_provider_sg(context, sg_id) with db_api.context_manager.writer.using(context): rules_db = (super(NsxPolicyPlugin, @@ -677,8 +1030,63 @@ class NsxPolicyPlugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, for i, r in enumerate(sg_rules): self._process_security_group_rule_properties( context, rules_db[i], r['security_group_rule']) + + domain_id = example_rule['tenant_id'] + secgroup_logging = self._is_security_group_logged(context, sg_id) + for sg_rule in sg_rules: + # create the NSX rule + rule_data = sg_rule['security_group_rule'] + self._check_local_ip_prefix(context, rule_data) + rule_data['id'] = rule_data.get('id') or uuidutils.generate_uuid() + self._create_security_group_backend_rule( + domain_id, sg_id, rule_data, secgroup_logging) + return rules_db - def delete_security_group_rule(self, context, id): - #TODO(asarfaty): Update the nSX backend - super(NsxPolicyPlugin, self).delete_security_group_rule(context, id) + def _delete_security_group_rule_backend_resources( + self, context, domain_id, rule_db): + rule_id = rule_db['id'] + # try to delete the service of this rule, if exists + if rule_db['protocol']: + try: + self.nsxpolicy.service.delete(rule_id) + except nsx_lib_exc.ResourceNotFound: + LOG.warning("Failed to delete SG rule %s service", rule_id) + + # Try to delete the remote ip prefix group, if exists + if rule_db['remote_ip_prefix']: + try: + remote_group_id = self._get_sg_rule_remote_ip_group_id(rule_db) + self.nsxpolicy.group.delete(domain_id, remote_group_id) + except nsx_lib_exc.ResourceNotFound: + LOG.warning("Failed to delete SG rule %s remote ip prefix " + "group", rule_id) + + # Try to delete the local ip prefix group, if exists + if self._get_security_group_rule_local_ip(context, rule_id): + try: + local_group_id = self._get_sg_rule_local_ip_group_id(rule_db) + self.nsxpolicy.group.delete(domain_id, local_group_id) + except nsx_lib_exc.ResourceNotFound: + LOG.warning("Failed to delete SG rule %s local ip prefix " + "group", rule_id) + + def delete_security_group_rule(self, context, rule_id): + rule_db = self._get_security_group_rule(context, rule_id) + sg_id = rule_db['security_group_id'] + self._prevent_non_admin_edit_provider_sg(context, sg_id) + domain_id = rule_db['tenant_id'] + + # Delete the rule itself + try: + self.nsxpolicy.comm_map.delete_entry(domain_id, sg_id, rule_id) + except Exception as e: + LOG.warning("Failed to delete SG rule %s NSX resources: %s", + rule_id, e) + # Go on with the deletion anyway + + self._delete_security_group_rule_backend_resources( + context, domain_id, rule_db) + + super(NsxPolicyPlugin, self).delete_security_group_rule( + context, rule_id) diff --git a/vmware_nsx/plugins/nsx_v3/plugin.py b/vmware_nsx/plugins/nsx_v3/plugin.py index 7331692bbd..948f8b5136 100644 --- a/vmware_nsx/plugins/nsx_v3/plugin.py +++ b/vmware_nsx/plugins/nsx_v3/plugin.py @@ -4747,6 +4747,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, ruleid_2_remote_nsgroup_map = {} _sg_rules = copy.deepcopy(sg_rules) for sg_rule in _sg_rules: + self._fix_sg_rule_dict_ips(sg_rule) remote_nsgroup_id = None remote_group_id = sg_rule.get('remote_group_id') # skip unnecessary db access when possible @@ -4756,14 +4757,6 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin, remote_nsgroup_id = nsx_db.get_nsx_security_group_id( context.session, remote_group_id) ruleid_2_remote_nsgroup_map[sg_rule['id']] = remote_nsgroup_id - # 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('local_ip_prefix') and - sg_rule['local_ip_prefix'].startswith('0.0.0.0/')): - sg_rule['local_ip_prefix'] = None return self.nsxlib.firewall_section.create_section_rules( section_id, nsgroup_id, diff --git a/vmware_nsx/tests/unit/extensions/test_provider_security_groups.py b/vmware_nsx/tests/unit/extensions/test_provider_security_groups.py index 7550e88974..6635c91231 100644 --- a/vmware_nsx/tests/unit/extensions/test_provider_security_groups.py +++ b/vmware_nsx/tests/unit/extensions/test_provider_security_groups.py @@ -24,6 +24,7 @@ from neutron_lib import context from vmware_nsx.db import extended_security_group from vmware_nsx.extensions import providersecuritygroup as provider_sg +from vmware_nsx.tests.unit.nsx_p import test_plugin as test_nsxp_plugin from vmware_nsx.tests.unit.nsx_v import test_plugin as test_nsxv_plugin from vmware_nsx.tests.unit.nsx_v3 import test_plugin as test_nsxv3_plugin @@ -390,3 +391,38 @@ class TestNSXvProviderSecurityGroup(test_nsxv_plugin.NsxVPluginV2TestCase, create_rule_m.assert_called_with(mock.ANY, mock.ANY, logged=mock.ANY, action='deny') + + +class TestNSXpProviderSecurityGrp(test_nsxp_plugin.NsxPPluginTestCaseMixin, + ProviderSecurityGroupExtTestCase): + + # Temporarily skip all port related tests until the plugin supports it + def test_update_port_security_groups(self): + self.skipTest('Temporarily not supported') + + def test_update_port_remove_provider_sg_with_empty_list(self): + self.skipTest('Temporarily not supported') + + def test_update_port_security_groups_only(self): + self.skipTest('Temporarily not supported') + + def test_create_port_with_no_provider_sg(self): + self.skipTest('Temporarily not supported') + + def test_create_port_gets_multi_provider_sg(self): + self.skipTest('Temporarily not supported') + + def test_cannot_update_port_with_provider_group_as_sec_group(self): + self.skipTest('Temporarily not supported') + + def test_update_port_remove_provider_sg_with_none(self): + self.skipTest('Temporarily not supported') + + def test_create_port_gets_provider_sg(self): + self.skipTest('Temporarily not supported') + + def test_cannot_update_port_with_different_tenant_provider_secgroup(self): + self.skipTest('Temporarily not supported') + + def test_cannot_update_port_with_sec_group_as_provider(self): + self.skipTest('Temporarily not supported') diff --git a/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py b/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py index 8441c5eca8..70881d5874 100644 --- a/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py +++ b/vmware_nsx/tests/unit/extensions/test_secgroup_rule_local_ip_prefix.py @@ -29,6 +29,7 @@ from neutron_lib.plugins import directory from vmware_nsx.db import extended_security_group_rule as ext_rule_db from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as ext_loip from vmware_nsx.plugins.nsx_v.vshield import securitygroup_utils +from vmware_nsx.tests.unit.nsx_p import test_plugin as test_nsxp_plugin from vmware_nsx.tests.unit.nsx_v import test_plugin as test_nsxv_plugin from vmware_nsx.tests.unit.nsx_v3 import test_plugin as test_nsxv3_plugin @@ -185,3 +186,8 @@ class TestNSXv3ExtendedSGRule(test_nsxv3_plugin.NsxV3PluginTestCaseMixin, 'ALLOW', # action sg_rules, # sg_rules mock.ANY) # ruleid_2_remote_nsgroup_map + + +class TestNSXpExtendedSGRule(test_nsxp_plugin.NsxPPluginTestCaseMixin, + LocalIPPrefixExtTestCase): + pass diff --git a/vmware_nsx/tests/unit/nsx_p/__init__.py b/vmware_nsx/tests/unit/nsx_p/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/vmware_nsx/tests/unit/nsx_p/test_plugin.py b/vmware_nsx/tests/unit/nsx_p/test_plugin.py new file mode 100644 index 0000000000..b5d9d5b655 --- /dev/null +++ b/vmware_nsx/tests/unit/nsx_p/test_plugin.py @@ -0,0 +1,116 @@ +# Copyright (c) 2018 OpenStack Foundation. +# +# 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 mock + +from neutron.tests.unit.db import test_db_base_plugin_v2 +from neutron.tests.unit.extensions import test_securitygroup + +from vmware_nsxlib.v3 import nsx_constants + + +PLUGIN_NAME = 'vmware_nsx.plugin.NsxPolicyPlugin' + + +class NsxPPluginTestCaseMixin( + test_db_base_plugin_v2.NeutronDbPluginV2TestCase): + + def setUp(self, plugin=PLUGIN_NAME, + ext_mgr=None, + service_plugins=None, **kwargs): + + self._mock_nsx_policy_backend_calls() + self.setup_conf_overrides() + super(NsxPPluginTestCaseMixin, self).setUp(plugin=plugin, + ext_mgr=ext_mgr) + + def _mock_nsx_policy_backend_calls(self): + mock.patch( + "vmware_nsxlib.v3.NsxPolicyLib.get_version", + return_value=nsx_constants.NSX_VERSION_2_4_0).start() + mock.patch( + "vmware_nsxlib.v3.client.RESTClient.get").start() + mock.patch( + "vmware_nsxlib.v3.client.RESTClient.patch").start() + mock.patch( + "vmware_nsxlib.v3.client.RESTClient.delete").start() + mock.patch("vmware_nsxlib.v3.policy_resources." + "NsxPolicyCommunicationMapApi._get_last_seq_num", + return_value=-1).start() + + def setup_conf_overrides(self): + #TODO(asarfaty): will be needed in the future + #cfg.CONF.set_override('default_overlay_tz', NSX_TZ_NAME, 'nsx_p') + #cfg.CONF.set_override('native_dhcp_metadata', False, 'nsx_p') + #cfg.CONF.set_override('dhcp_profile', + # NSX_DHCP_PROFILE_ID, 'nsx_p') + #cfg.CONF.set_override('metadata_proxy', + # NSX_METADATA_PROXY_ID, 'nsx_p') + pass + + +class NsxPTestSecurityGroup(NsxPPluginTestCaseMixin, + test_securitygroup.TestSecurityGroups, + test_securitygroup.SecurityGroupDBTestCase): + + def setUp(self, plugin=PLUGIN_NAME, ext_mgr=None): + super(NsxPTestSecurityGroup, self).setUp(plugin=plugin, + ext_mgr=ext_mgr) + + def test_create_security_group_rule_icmp_with_type_and_code(self): + """No non-zero icmp codes are currently supported by the NSX""" + self.skipTest('not supported') + + def test_create_security_group_rule_icmp_with_type(self): + name = 'webservers' + description = 'my webservers' + with self.security_group(name, description) as sg: + security_group_id = sg['security_group']['id'] + direction = "ingress" + remote_ip_prefix = "10.0.0.0/24" + protocol = "icmp" + # port_range_min (ICMP type) is greater than port_range_max + # (ICMP code) in order to confirm min <= max port check is + # not called for ICMP. + port_range_min = 14 + port_range_max = None + keys = [('remote_ip_prefix', remote_ip_prefix), + ('security_group_id', security_group_id), + ('direction', direction), + ('protocol', protocol), + ('port_range_min', port_range_min), + ('port_range_max', port_range_max)] + with self.security_group_rule(security_group_id, direction, + protocol, port_range_min, + port_range_max, + remote_ip_prefix) as rule: + for k, v, in keys: + self.assertEqual(rule['security_group_rule'][k], v) + + # Temporarily skip all port related tests until the plugin supports it + def test_create_port_with_no_security_groups(self): + self.skipTest('Temporarily not supported') + + def test_create_delete_security_group_port_in_use(self): + self.skipTest('Temporarily not supported') + + def test_create_port_with_multiple_security_groups(self): + self.skipTest('Temporarily not supported') + + def test_list_ports_security_group(self): + self.skipTest('Temporarily not supported') + + def test_update_port_with_security_group(self): + self.skipTest('Temporarily not supported')