diff --git a/doc/source/admin_util.rst b/doc/source/admin_util.rst index f509480aa5..d81c6d6c7b 100644 --- a/doc/source/admin_util.rst +++ b/doc/source/admin_util.rst @@ -279,9 +279,10 @@ Security Groups, Firewall and Spoofguard - Spoofguard support:: - nsxadmin -r spoofguard-policy -o list-mismatches nsxadmin -r spoofguard-policy -o clean --property policy-id=spoofguardpolicy-10 nsxadmin -r spoofguard-policy -o list --property reverse (entries defined on NSXv and not in Neutron) + nsxadmin -r spoofguard-policy -o list-mismatches (--property network=) - List spoofguard policies with mismatching ips or mac, globally or for a specific network + nsxadmin -r spoofguard-policy -o fix-mismatch --property port= - Fix the spoofgurad ips of a neutron port Metadata ~~~~~~~~ diff --git a/vmware_nsx/db/nsxv_db.py b/vmware_nsx/db/nsxv_db.py index aa65503a01..ebc7620685 100644 --- a/vmware_nsx/db/nsxv_db.py +++ b/vmware_nsx/db/nsxv_db.py @@ -663,6 +663,17 @@ def get_spoofguard_policy_id(session, network_id): network_id) +def get_spoofguard_policy_network_id(session, policy_id): + try: + mapping = (session.query( + nsxv_models.NsxvSpoofGuardPolicyNetworkMapping). + filter_by(policy_id=policy_id).one()) + return mapping['network_id'] + except exc.NoResultFound: + LOG.debug("SpoofGuard Policy %s was not found in Neutron DB", + policy_id) + + def get_nsxv_spoofguard_policy_network_mappings(session, filters=None, like_filters=None): session = db_api.get_reader_session() diff --git a/vmware_nsx/plugins/nsx_v/vshield/vcns.py b/vmware_nsx/plugins/nsx_v/vshield/vcns.py index 245199b87c..a6a9e7deda 100644 --- a/vmware_nsx/plugins/nsx_v/vshield/vcns.py +++ b/vmware_nsx/plugins/nsx_v/vshield/vcns.py @@ -812,6 +812,10 @@ class Vcns(object): uri = '%s/policies/%s' % (SPOOFGUARD_PREFIX, policy_id) return self.do_request(HTTP_GET, uri, decode=True) + def get_spoofguard_policy_data(self, policy_id, list_type='ALL'): + uri = '%s/%s?list=%s' % (SPOOFGUARD_PREFIX, policy_id, list_type) + return self.do_request(HTTP_GET, uri, decode=True) + def get_spoofguard_policies(self): uri = '%s/policies/' % SPOOFGUARD_PREFIX return self.do_request(HTTP_GET, uri, decode=True) diff --git a/vmware_nsx/shell/admin/plugins/nsxv/resources/spoofguard_policy.py b/vmware_nsx/shell/admin/plugins/nsxv/resources/spoofguard_policy.py index bd24d54a43..725e31f6a2 100644 --- a/vmware_nsx/shell/admin/plugins/nsxv/resources/spoofguard_policy.py +++ b/vmware_nsx/shell/admin/plugins/nsxv/resources/spoofguard_policy.py @@ -24,6 +24,8 @@ from neutron_lib.callbacks import registry from neutron_lib import exceptions from vmware_nsx.db import nsxv_db +from vmware_nsx.extensions import ( + vnicindex as ext_vnic_idx) from oslo_log import log as logging @@ -36,6 +38,12 @@ def get_spoofguard_policies(): return nsxv.get_spoofguard_policies()[1].get("policies") +def get_spoofguard_policy_data(policy_id): + nsxv = utils.get_nsxv_client() + return nsxv.get_spoofguard_policy_data(policy_id)[1].get( + 'spoofguardList', []) + + @admin_utils.output_header def nsx_list_spoofguard_policies(resource, event, trigger, **kwargs): """List spoofguard policies from NSXv backend""" @@ -100,6 +108,134 @@ def nsx_list_missing_spoofguard_policies(resource, event, trigger, constants.SPOOFGUARD_POLICY, missing_policies, ['policy_id'])) +def get_port_vnic_id(plugin, port): + vnic_idx = port.get(ext_vnic_idx.VNIC_INDEX) + device_id = port.get('device_id') + return plugin._get_port_vnic_id(vnic_idx, device_id) + + +def nsx_list_mismatch_addresses_for_net(context, plugin, network_id, + policy_id): + policy_data = get_spoofguard_policy_data(policy_id) + missing = [] + # Get all neutron compute ports on this network + port_filters = {'network_id': [network_id]} + neutron_ports = plugin.get_ports(context, filters=port_filters) + comp_ports = [port for port in neutron_ports + if port.get('device_owner', '').startswith('compute:')] + + for port in comp_ports: + if not port['port_security_enabled']: + # This port is not in spoofguard + continue + error_data = None + port_ips = [] + for pair in port.get('allowed_address_pairs'): + port_ips.append(pair['ip_address']) + for fixed in port.get('fixed_ips'): + port_ips.append(fixed['ip_address']) + if not port_ips: + continue + port_ips.sort() + mac_addr = port['mac_address'] + vnic_id = get_port_vnic_id(plugin, port) + + # look for this port in the spoofguard data + found_port = False + for spd in policy_data: + if spd['id'] == vnic_id: + found_port = True + actual_ips = spd.get('publishedIpAddress', + {}).get('ipAddresses', []) + actual_ips.sort() + if actual_ips != port_ips: + error_data = ('Different IPs (%s/%s)' % ( + len(actual_ips), len(port_ips))) + elif spd.get('publishedMacAddress') != mac_addr: + error_data = ('Different MAC address (%s/%s)' % ( + spd.get('publishedMacAddress'), mac_addr)) + continue + + if not found_port: + error_data = 'Port missing from SG policy' + + if error_data: + missing.append({'network': network_id, + 'policy': policy_id, + 'port': port['id'], + 'data': error_data}) + return missing + + +@admin_utils.output_header +def nsx_list_mismatch_addresses(resource, event, trigger, **kwargs): + """List missing spoofguard policies approved addresses on NSXv. + + Address pairs defined on neutron compute ports that are missing from the + NSX-V spoofguard policy of a specific/all networks. + """ + network_id = None + if kwargs.get('property'): + properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) + network_id = properties.get('network') + + spgapi = utils.NeutronDbClient() + + if network_id: + policy_id = nsxv_db.get_spoofguard_policy_id( + spgapi.context.session, network_id) + if not policy_id: + LOG.error("Could not find spoofguard policy for neutron network " + "%s", network_id) + return + with utils.NsxVPluginWrapper() as plugin: + missing_data = nsx_list_mismatch_addresses_for_net( + spgapi.context, plugin, network_id, policy_id) + else: + with utils.NsxVPluginWrapper() as plugin: + missing_data = [] + # Go over all the networks with spoofguard policies + mappings = get_spoofguard_policy_network_mappings() + for entry in mappings: + missing_data.extend(nsx_list_mismatch_addresses_for_net( + spgapi.context, plugin, entry['network_id'], + entry['policy_id'])) + + if missing_data: + LOG.info(formatters.output_formatter( + constants.SPOOFGUARD_POLICY, missing_data, + ['network', 'policy', 'port', 'data'])) + else: + LOG.info("No mismatches found.") + + +@admin_utils.output_header +def nsx_fix_mismatch_addresses(resource, event, trigger, **kwargs): + """Fix missing spoofguard policies approved addresses for a port.""" + + port_id = None + if kwargs.get('property'): + properties = admin_utils.parse_multi_keyval_opt(kwargs['property']) + port_id = properties.get('port') + if not port_id: + usage_msg = ("Need to specify the id of the neutron port. " + "Add --property port=") + LOG.error(usage_msg) + return + + spgapi = utils.NeutronDbClient() + with utils.NsxVPluginWrapper() as plugin: + try: + port = plugin.get_port(spgapi.context, port_id) + except exceptions.PortNotFound: + LOG.error("Could not find neutron port %s", port_id) + return + vnic_id = get_port_vnic_id(plugin, port) + plugin._update_vnic_assigned_addresses( + spgapi.context.session, port, vnic_id) + LOG.info("Done.") + + def nsx_clean_spoofguard_policy(resource, event, trigger, **kwargs): """Delete spoofguard policy""" errmsg = ("Need to specify policy-id. Add --property " @@ -143,6 +279,12 @@ registry.subscribe(nsx_list_spoofguard_policies, registry.subscribe(nsx_list_missing_spoofguard_policies, constants.SPOOFGUARD_POLICY, shell.Operations.LIST.value) +registry.subscribe(nsx_list_mismatch_addresses, + constants.SPOOFGUARD_POLICY, + shell.Operations.LIST_MISMATCHES.value) +registry.subscribe(nsx_fix_mismatch_addresses, + constants.SPOOFGUARD_POLICY, + shell.Operations.FIX_MISMATCH.value) registry.subscribe(nsx_clean_spoofguard_policy, constants.SPOOFGUARD_POLICY, shell.Operations.CLEAN.value) diff --git a/vmware_nsx/shell/resources.py b/vmware_nsx/shell/resources.py index 2e8864abbb..55fe500667 100644 --- a/vmware_nsx/shell/resources.py +++ b/vmware_nsx/shell/resources.py @@ -176,7 +176,9 @@ nsxv_resources = { [Operations.LIST.value]), constants.SPOOFGUARD_POLICY: Resource(constants.SPOOFGUARD_POLICY, [Operations.LIST.value, - Operations.CLEAN.value]), + Operations.CLEAN.value, + Operations.LIST_MISMATCHES.value, + Operations.FIX_MISMATCH.value]), constants.DHCP_BINDING: Resource(constants.DHCP_BINDING, [Operations.LIST.value, Operations.NSX_UPDATE.value, diff --git a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py index c6026f590e..078155ac93 100644 --- a/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py +++ b/vmware_nsx/tests/unit/nsx_v/vshield/fake_vcns.py @@ -1141,6 +1141,9 @@ class FakeVcns(object): raise exceptions.VcnsGeneralException( _("Spoofguard policy not found")) + def get_spoofguard_policy_data(self, policy_id, list_type='INACTIVE'): + return None, {'spoofguardList': []} + def get_spoofguard_policies(self): return None, {'policies': self._spoofguard_policies}