NSX|P: VPNaaS driver

Change-Id: I3dae7c34527f7f65f37cf03e699007141865a090
This commit is contained in:
Adit Sarfaty 2019-04-11 10:58:26 +03:00 committed by Salvatore Orlando
parent 74c9a66e00
commit 55b0cf16e8
15 changed files with 2213 additions and 479 deletions

View File

@ -306,6 +306,20 @@ Add octavia and python-octaviaclient repos as external repositories and configur
network_driver = allowed_address_pairs_driver
Neutron VPNaaS
~~~~~~~~~~~~~~
Add neutron-vpnaas repo as an external repository and configure following flags in ``local.conf``::
[[local|localrc]]
NEUTRON_VPNAAS_SERVICE_PROVIDER=VPN:vmware:vmware_nsx.services.vpnaas.nsxp.ipsec_driver.NSXpIPsecVpnDriver:default
Q_SERVICE_PLUGIN_CLASSES+=,vmware_nsx_vpnaas
[[post-config|$NEUTRON_CONF]]
[DEFAULT]
api_extensions_path = $DEST/neutron-vpnaas/neutron_vpnaas/extensions
NSX-TVD
-------

View File

@ -86,7 +86,7 @@ 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
from vmware_nsx.services.qos.common import utils as qos_com_utils
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts

View File

@ -1207,6 +1207,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
port['device_owner'] in [const.DEVICE_OWNER_DHCP]):
msg = (_('Can not delete DHCP port %s') % port_id)
raise n_exc.BadRequest(resource='port', msg=msg)
if not force_delete_vpn:
self._assert_on_vpn_port_change(port)
if self._is_backend_port(context, port_data):
self._delete_port_on_backend(context, net_id, port_id)
@ -1450,11 +1452,12 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
router = self._get_router(context, router_id)
snat_exist = router.enable_snat
fw_exist = self._router_has_edge_fw_rules(context, router)
vpn_exist = self.service_router_has_vpnaas(context, router_id)
lb_exist = False
if not (fw_exist or snat_exist):
if not (fw_exist or snat_exist or vpn_exist):
lb_exist = self.service_router_has_loadbalancers(
context, router_id)
return snat_exist or lb_exist or fw_exist
return snat_exist or lb_exist or fw_exist or vpn_exist
def service_router_has_loadbalancers(self, context, router_id):
tags_to_search = [{'scope': lb_const.LR_ROUTER_TYPE, 'tag': router_id}]
@ -1464,6 +1467,15 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
)['results']
return True if router_lb_services else False
def service_router_has_vpnaas(self, context, router_id):
"""Return True if there is a vpn service attached to this router"""
vpn_plugin = directory.get_plugin(plugin_const.VPN)
if vpn_plugin:
filters = {'router_id': [router_id]}
if vpn_plugin.get_vpnservices(context.elevated(), filters=filters):
return True
return False
def verify_sr_at_backend(self, router_id):
"""Check if the backend Tier1 has a service router or not"""
if self.nsxpolicy.tier1.get_edge_cluster_path(router_id):
@ -1734,9 +1746,17 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
router_data = router['router']
self._assert_on_router_admin_state(router_data)
vpn_driver = None
if validators.is_attr_set(gw_info):
self._validate_update_router_gw(context, router_id, gw_info)
# VPNaaS need to be notified on router GW changes (there is
# currently no matching upstream registration for this)
vpn_plugin = directory.get_plugin(plugin_const.VPN)
if vpn_plugin:
vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider]
vpn_driver.validate_router_gw_info(context, router_id, gw_info)
routes_added = []
routes_removed = []
if 'routes' in router_data:
@ -1782,7 +1802,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
except Exception as e:
LOG.error("Rollback router %s changes failed to add "
"static routes: %s", router_id, e)
if vpn_driver:
# Update vpn advertisement if GW was updated
vpn_driver.update_router_advertisement(context, router_id)
return updated_router
def _get_gateway_addr_from_subnet(self, subnet):
@ -2814,3 +2836,29 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
if len(port_tags) != orig_len:
self.nsxpolicy.segment_port.update(
segment_id, port_id, tags=port_tags)
def get_extra_fw_rules(self, context, router_id, port_id):
"""Return firewall rules that should be added to the router firewall
This method should return a list of allow firewall rules that are
required in order to enable different plugin features with north/south
traffic.
The returned rules will be added after the FWaaS rules, and before the
default drop rule.
Only rules relevant for port_id router interface port should be
returned, and the rules should be ingress/egress
(but not both) and include the source/dest nsx logical port.
"""
extra_rules = []
# VPN rules:
vpn_plugin = directory.get_plugin(plugin_const.VPN)
if vpn_plugin:
vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider]
vpn_rules = (
vpn_driver._generate_ipsecvpn_firewall_rules(
self.plugin_type(), context, router_id=router_id))
if vpn_rules:
extra_rules.extend(vpn_rules)
return extra_rules

View File

@ -2567,6 +2567,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
port should be returned, and the rules should be ingress/egress
(but not both) and include the source/dest nsx logical port.
"""
# TODO(asarfaty) support only cases with port_id, as FWaaS v1 is no
# longer supported
extra_rules = []
# DHCP relay rules:

View File

@ -255,7 +255,7 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
return translated_rules
def _get_port_translated_rules(self, project_id, router_id, neutron_net_id,
firewall_group):
firewall_group, plugin_rules):
"""Return the list of translated FWaaS rules per port
Add the egress/ingress rules of this port +
default drop rules in each direction for this port.
@ -272,6 +272,10 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
project_id, router_id, net_group_id,
firewall_group['egress_rule_list'], is_ingress=False))
# Add the per-port plugin rules
if plugin_rules and isinstance(plugin_rules, list):
port_rules.extend(plugin_rules)
# Add ingress/egress block rules for this port
port_rules.extend([
self.nsxpolicy.gateway_policy.build_entry(
@ -323,10 +327,16 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
fwg = self.get_port_fwg(context, port['id'])
if fwg:
router_with_fw = True
# Add plugin additional allow rules
plugin_rules = self.core_plugin.get_extra_fw_rules(
context, router_id, port['id'])
# Add the FWaaS rules for this port:ingress/egress firewall
# rules + default ingress/egress drop rule for this port
fw_rules.extend(self._get_port_translated_rules(
project_id, router_id, port['network_id'], fwg))
project_id, router_id, port['network_id'], fwg,
plugin_rules))
# Add a default allow-all rule to all other traffic & ports
fw_rules.append(self._get_default_backend_rule(router_id))

View File

@ -0,0 +1,173 @@
# Copyright 2019 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.
import netaddr
from oslo_log import log as logging
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants
from neutron_lib import context as n_context
from neutron_lib import exceptions as nexception
from neutron_lib.plugins import directory
from neutron_vpnaas.services.vpn import service_drivers
from vmware_nsx.extensions import projectpluginmap
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
LOG = logging.getLogger(__name__)
IPSEC = 'ipsec'
class RouterWithSNAT(nexception.BadRequest):
message = _("Router %(router_id)s has a VPN service and cannot enable "
"SNAT")
class RouterWithOverlapNoSnat(nexception.BadRequest):
message = _("Router %(router_id)s has a subnet overlapping with a VPN "
"local subnet, and cannot disable SNAT")
class RouterOverlapping(nexception.BadRequest):
message = _("Router %(router_id)s interface is overlapping with a VPN "
"local subnet and cannot be added")
class NSXcommonIPsecVpnDriver(service_drivers.VpnDriver):
def __init__(self, service_plugin, validator):
self.vpn_plugin = service_plugin
self._core_plugin = directory.get_plugin()
if self._core_plugin.is_tvd_plugin():
# TVD only supports nsx-T, and not nsx-P
self._core_plugin = self._core_plugin.get_plugin_by_type(
projectpluginmap.NsxPlugins.NSX_T)
super(NSXcommonIPsecVpnDriver, self).__init__(
service_plugin, validator)
registry.subscribe(
self._verify_overlap_subnet, resources.ROUTER_INTERFACE,
events.BEFORE_CREATE)
@property
def l3_plugin(self):
return self._core_plugin
@property
def service_type(self):
return IPSEC
def _get_dpd_profile_name(self, connection):
return (connection['name'] or connection['id'])[:240] + '-dpd-profile'
def _find_vpn_service_port(self, context, router_id):
"""Look for the neutron port created for the vpnservice of a router"""
filters = {'device_id': ['router-' + router_id],
'device_owner': [ipsec_utils.VPN_PORT_OWNER]}
ports = self.l3_plugin.get_ports(context, filters=filters)
if ports:
return ports[0]
def _get_tier0_uuid(self, context, router_id):
router_db = self._core_plugin._get_router(context, router_id)
return self._core_plugin._get_tier0_uuid_by_router(context, router_db)
def _get_service_local_address(self, context, vpnservice):
"""Find/Allocate a port on the external network
to allocate the ip to be used as the local ip of this service
"""
router_id = vpnservice['router_id']
# check if this router already have an IP
port = self._find_vpn_service_port(context, router_id)
if not port:
# create a new port, on the external network of the router
# Note(asarfaty): using a unique device owner and device id to
# make sure tis port will be ignored in certain queries
ext_net = vpnservice['router']['gw_port']['network_id']
port_data = {
'port': {
'network_id': ext_net,
'name': 'VPN local address port',
'admin_state_up': True,
'device_id': 'router-' + router_id,
'device_owner': ipsec_utils.VPN_PORT_OWNER,
'fixed_ips': constants.ATTR_NOT_SPECIFIED,
'mac_address': constants.ATTR_NOT_SPECIFIED,
'port_security_enabled': False,
'tenant_id': vpnservice['tenant_id']}}
port = self.l3_plugin.base_create_port(context, port_data)
# return the port ip(v4) as the local address
for fixed_ip in port['fixed_ips']:
if (len(port['fixed_ips']) == 1 or
netaddr.IPNetwork(fixed_ip['ip_address']).version == 4):
return fixed_ip['ip_address']
def _update_status(self, context, vpn_service_id, ipsec_site_conn_id,
status, updated_pending_status=True):
vpn_status = {'id': vpn_service_id,
'updated_pending_status': updated_pending_status,
'status': status,
'ipsec_site_connections': {}}
if ipsec_site_conn_id:
ipsec_site_conn = {
'status': status,
'updated_pending_status': updated_pending_status}
vpn_status['ipsec_site_connections'] = {
ipsec_site_conn_id: ipsec_site_conn}
status_list = [vpn_status]
self.service_plugin.update_status_by_agent(context, status_list)
def _check_subnets_overlap_with_all_conns(self, context, subnets):
# find all vpn services with connections
filters = {'status': [constants.ACTIVE, constants.DOWN]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context, filters=filters)
# Check if any of the connections overlap with the given subnets
for conn in connections:
local_cidrs = self.validator._get_local_cidrs(context, conn)
if netaddr.IPSet(subnets) & netaddr.IPSet(local_cidrs):
return False
return True
def _verify_overlap_subnet(self, resource, event, trigger, **kwargs):
"""Upon router interface creation validation overlapping with vpn"""
router_db = kwargs.get('router_db')
port = kwargs.get('port')
if not port or not router_db:
LOG.warning("NSX V3 VPNaaS ROUTER_INTERFACE BEFORE_CREATE "
"callback didn't get all the relevant information")
return
if router_db.enable_snat:
# checking only no-snat routers
return
admin_con = n_context.get_admin_context()
# Get the (ipv4) subnet of the interface
subnet_id = None
for fixed_ip in port['fixed_ips']:
if netaddr.IPNetwork(fixed_ip['ip_address']).version == 4:
subnet_id = fixed_ip.get('subnet_id')
break
if subnet_id:
subnet = self._core_plugin.get_subnet(admin_con, subnet_id)
# find all vpn services with connections
if not self._check_subnets_overlap_with_all_conns(
admin_con, [subnet['cidr']]):
raise RouterOverlapping(router_id=kwargs.get('router_id'))

View File

@ -27,10 +27,23 @@ AUTH_ALGORITHM_MAP = {
'sha256': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA256,
}
AUTH_ALGORITHM_MAP_P = {
'sha1': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA1,
'sha256': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA256,
'sha384': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA2_384,
'sha512': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA2_512,
}
PFS_MAP = {
'group14': vpn_ipsec.DHGroupTypes.DH_GROUP_14
}
PFS_MAP_P = {
'group2': vpn_ipsec.DHGroupTypes.DH_GROUP_2,
'group5': vpn_ipsec.DHGroupTypes.DH_GROUP_5,
'group14': vpn_ipsec.DHGroupTypes.DH_GROUP_14,
}
IKE_VERSION_MAP = {
'v1': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V1,
'v2': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V2,

View File

@ -0,0 +1,400 @@
# Copyright 2019 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.
import netaddr
from oslo_config import cfg
from oslo_log import log as logging
from neutron_lib import constants
from neutron_vpnaas.db.vpn import vpn_validator
from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.extensions import projectpluginmap
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
from vmware_nsxlib.v3 import vpn_ipsec
LOG = logging.getLogger(__name__)
class IPsecCommonValidator(vpn_validator.VpnReferenceValidator):
"""Validator methods for Vmware NSX-V3 & Policy VPN support"""
def __init__(self, service_plugin):
super(IPsecCommonValidator, self).__init__()
self.vpn_plugin = service_plugin
self._core_plugin = self.core_plugin
if self._core_plugin.is_tvd_plugin():
# TVD currently supports only NSX-T and not NSX-P
self._core_plugin = self._core_plugin.get_plugin_by_type(
projectpluginmap.NsxPlugins.NSX_T)
self.check_backend_version()
def check_backend_version(self):
pass
def _validate_backend_version(self):
pass
def _validate_policy_lifetime(self, policy_info, policy_type):
"""NSX supports only units=seconds"""
lifetime = policy_info.get('lifetime')
if not lifetime:
return
if lifetime.get('units') != 'seconds':
msg = _("Unsupported policy lifetime %(val)s in %(pol)s policy. "
"Only seconds lifetime is supported.") % {
'val': lifetime, 'pol': policy_type}
raise nsx_exc.NsxVpnValidationError(details=msg)
value = lifetime.get('value')
if policy_type == 'IKE':
limits = vpn_ipsec.IkeSALifetimeLimits
else:
limits = vpn_ipsec.IPsecSALifetimeLimits
if (value and (value < limits.SA_LIFETIME_MIN or
value > limits.SA_LIFETIME_MAX)):
msg = _("Unsupported policy lifetime %(value)s in %(pol)s policy. "
"Value range is [%(min)s-%(max)s].") % {
'value': value,
'pol': policy_type,
'min': limits.SA_LIFETIME_MIN,
'max': limits.SA_LIFETIME_MAX}
raise nsx_exc.NsxVpnValidationError(details=msg)
@property
def auth_algorithm_map(self):
pass
@property
def pfs_map(self):
pass
def _validate_policy_auth_algorithm(self, policy_info, policy_type):
"""NSX supports only SHA1 and SHA256"""
auth = policy_info.get('auth_algorithm')
if auth and auth not in self.auth_algorithm_map:
msg = _("Unsupported auth_algorithm: %(algo)s in %(pol)s policy. "
"Please select one of the following supported algorithms: "
"%(supported_algos)s") % {
'pol': policy_type,
'algo': auth,
'supported_algos':
self.auth_algorithm_map.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_policy_encryption_algorithm(self, policy_info, policy_type):
encryption = policy_info.get('encryption_algorithm')
if (encryption and
encryption not in ipsec_utils.ENCRYPTION_ALGORITHM_MAP):
msg = _("Unsupported encryption_algorithm: %(algo)s in %(pol)s "
"policy. Please select one of the following supported "
"algorithms: %(supported_algos)s") % {
'algo': encryption,
'pol': policy_type,
'supported_algos':
ipsec_utils.ENCRYPTION_ALGORITHM_MAP.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_policy_pfs(self, policy_info, policy_type):
pfs = policy_info.get('pfs')
if pfs and pfs not in self.pfs_map:
msg = _("Unsupported pfs: %(pfs)s in %(pol)s policy. Please "
"select one of the following pfs: "
"%(supported_pfs)s") % {
'pfs': pfs,
'pol': policy_type,
'supported_pfs':
self.pfs_map.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_dpd(self, connection):
dpd_info = connection.get('dpd')
if not dpd_info:
return
action = dpd_info.get('action')
if action not in ipsec_utils.DPD_ACTION_MAP.keys():
msg = _("Unsupported DPD action: %(action)s! Currently only "
"%(supported)s is supported.") % {
'action': action,
'supported': ipsec_utils.DPD_ACTION_MAP.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
timeout = dpd_info.get('timeout')
if (timeout < vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MIN or
timeout > vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MAX):
msg = _("Unsupported DPD timeout: %(timeout)s. Value range is "
"[%(min)s-%(max)s].") % {
'timeout': timeout,
'min': vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MIN,
'max': vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MAX}
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_psk(self, connection):
if 'psk' in connection and not connection['psk']:
msg = _("'psk' cannot be empty or null when authentication "
"mode is psk")
raise nsx_exc.NsxVpnValidationError(details=msg)
def _get_local_cidrs(self, context, ipsec_site_conn):
vpnservice_id = ipsec_site_conn.get('vpnservice_id')
vpnservice = self.vpn_plugin._get_vpnservice(context, vpnservice_id)
if vpnservice['subnet']:
local_cidrs = [vpnservice['subnet']['cidr']]
else:
# local endpoint group
local_cidrs = []
self.vpn_plugin.get_endpoint_info(context, ipsec_site_conn)
subnets_ids = ipsec_site_conn['local_epg_subnets']['endpoints']
for sub in subnets_ids:
subnet = self.l3_plugin.get_subnet(context, sub)
local_cidrs.append(subnet['cidr'])
return local_cidrs
def _get_peer_cidrs(self, context, ipsec_site_conn):
if ipsec_site_conn['peer_cidrs']:
return ipsec_site_conn['peer_cidrs']
else:
# peer endpoint group
self.vpn_plugin.get_endpoint_info(context, ipsec_site_conn)
return ipsec_site_conn['peer_epg_cidrs']['endpoints']
def _check_policy_rules_overlap(self, context, ipsec_site_conn):
"""validate no overlapping policy rules
The nsx does not support overlapping policy rules cross
all tenants, and tier0 routers
"""
connections = self.vpn_plugin.get_ipsec_site_connections(
context.elevated())
if not connections:
return
local_cidrs = self._get_local_cidrs(context, ipsec_site_conn)
peer_cidrs = self._get_peer_cidrs(context, ipsec_site_conn)
for conn in connections:
# skip this connection and connections in ERROR state
if (conn['id'] == ipsec_site_conn.get('id') or
conn['status'] == constants.ERROR):
continue
conn_peer_cidrs = self._get_peer_cidrs(context.elevated(), conn)
if netaddr.IPSet(conn_peer_cidrs) & netaddr.IPSet(peer_cidrs):
# check if the local cidr also overlaps
conn_local_cidr = self._get_local_cidrs(
context.elevated(), conn)
if netaddr.IPSet(conn_local_cidr) & netaddr.IPSet(local_cidrs):
msg = (_("Cannot create a connection with overlapping "
"local and peer cidrs (%(local)s and %(peer)s) "
"as connection %(id)s") % {'local': local_cidrs,
'peer': peer_cidrs,
'id': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
def _check_unique_addresses(self, context, ipsec_site_conn):
"""Validate no repeating local & peer addresses (of all tenants)
The nsx does not support it cross all tenants, and tier0 routers
"""
vpnservice_id = ipsec_site_conn.get('vpnservice_id')
local_addr = self._get_service_local_address(context, vpnservice_id)
peer_address = ipsec_site_conn.get('peer_address')
filters = {'peer_address': [peer_address]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context.elevated(), filters=filters)
for conn in connections:
# skip this connection and connections in ERROR state
if (conn['id'] == ipsec_site_conn.get('id') or
conn['status'] == constants.ERROR):
continue
# this connection has the same peer addr as ours.
# check the service local address
srv_id = conn.get('vpnservice_id')
srv_local = self._get_service_local_address(
context.elevated(), srv_id)
if srv_local == local_addr:
msg = (_("Cannot create another connection with the same "
"local address %(local)s and peer address %(peer)s "
"as connection %(id)s") % {'local': local_addr,
'peer': peer_address,
'id': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
def _check_advertisment_overlap(self, context, ipsec_site_conn):
"""Validate there is no overlapping advertisement of networks
The plugin advertise all no-snat routers networks + vpn local
networks.
The NSX does not allow different Tier1 router to advertise the
same subnets.
"""
admin_con = context.elevated()
srv_id = ipsec_site_conn.get('vpnservice_id')
srv = self.vpn_plugin._get_vpnservice(admin_con, srv_id)
this_router = srv['router_id']
local_cidrs = self._get_local_cidrs(context, ipsec_site_conn)
# get all subnets of no-snat routers
all_routers = self._core_plugin.get_routers(admin_con)
nosnat_routers = [rtr for rtr in all_routers
if (rtr['id'] != this_router and
rtr.get('external_gateway_info') and
not rtr['external_gateway_info'].get(
'enable_snat',
cfg.CONF.enable_snat_by_default))]
for rtr in nosnat_routers:
if rtr['id'] == this_router:
continue
# go over the subnets of this router
subnets = self._core_plugin._find_router_subnets_cidrs(
admin_con, rtr['id'])
if subnets and netaddr.IPSet(subnets) & netaddr.IPSet(local_cidrs):
msg = (_("Cannot create connection with overlapping local "
"cidrs %(local)s which was already advertised by "
"no-snat router %(rtr)s") % {'local': subnets,
'rtr': rtr['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
# add all vpn local subnets
connections = self.vpn_plugin.get_ipsec_site_connections(admin_con)
for conn in connections:
# skip this connection and connections in ERROR state
if (conn['id'] == ipsec_site_conn.get('id') or
conn['status'] == constants.ERROR):
continue
# check the service local address
conn_srv_id = conn.get('vpnservice_id')
conn_srv = self.vpn_plugin._get_vpnservice(admin_con, conn_srv_id)
if conn_srv['router_id'] == this_router:
continue
conn_cidrs = self._get_local_cidrs(context, conn)
if netaddr.IPSet(conn_cidrs) & netaddr.IPSet(local_cidrs):
msg = (_("Cannot create connection with overlapping local "
"cidr %(local)s which was already advertised by "
"router %(rtr)s and connection %(conn)s") % {
'local': conn_cidrs,
'rtr': conn_srv['router_id'],
'conn': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
def validate_ipsec_site_connection(self, context, ipsec_site_conn):
"""Called upon create/update of a connection"""
self._validate_backend_version()
self._validate_dpd(ipsec_site_conn)
self._validate_psk(ipsec_site_conn)
ike_policy_id = ipsec_site_conn.get('ikepolicy_id')
if ike_policy_id:
ikepolicy = self.vpn_plugin.get_ikepolicy(context,
ike_policy_id)
self.validate_ike_policy(context, ikepolicy)
ipsec_policy_id = ipsec_site_conn.get('ipsecpolicy_id')
if ipsec_policy_id:
ipsecpolicy = self.vpn_plugin.get_ipsecpolicy(context,
ipsec_policy_id)
self.validate_ipsec_policy(context, ipsecpolicy)
if ipsec_site_conn.get('vpnservice_id'):
self._check_advertisment_overlap(context, ipsec_site_conn)
self._check_unique_addresses(context, ipsec_site_conn)
self._check_policy_rules_overlap(context, ipsec_site_conn)
#TODO(asarfaty): IPv6 is not yet supported. add validation
def _get_service_local_address(self, context, vpnservice_id):
"""The local address of the service is assigned upon creation
From the attached external network pool
"""
vpnservice = self.vpn_plugin._get_vpnservice(context,
vpnservice_id)
return vpnservice['external_v4_ip']
def _validate_t0_ha_mode(self, tier0_uuid):
pass
def _validate_router(self, context, router_id):
# Verify that the router gw network is connected to an active-standby
# Tier0 router
router_db = self._core_plugin._get_router(context, router_id)
tier0_uuid = self._core_plugin._get_tier0_uuid_by_router(context,
router_db)
self._validate_t0_ha_mode(tier0_uuid)
def _support_endpoint_groups(self):
"""Can be implemented by each plugin"""
return False
def validate_vpnservice(self, context, vpnservice):
"""Called upon create/update of a service"""
self._validate_backend_version()
# Call general validations
super(IPsecCommonValidator, self).validate_vpnservice(
context, vpnservice)
# Call specific NSX validations
self._validate_router(context, vpnservice['router_id'])
if not self._support_endpoint_groups() and not vpnservice['subnet_id']:
# we currently do not support multiple subnets so a subnet must
# be defined
msg = _("Subnet must be defined in a service")
raise nsx_exc.NsxVpnValidationError(details=msg)
#TODO(asarfaty): IPv6 is not yet supported. add validation
def validate_ipsec_policy(self, context, ipsec_policy):
# Call general validations
super(IPsecCommonValidator, self).validate_ipsec_policy(
context, ipsec_policy)
# Call specific NSX validations
self._validate_policy_lifetime(ipsec_policy, "IPSec")
self._validate_policy_auth_algorithm(ipsec_policy, "IPSec")
self._validate_policy_encryption_algorithm(ipsec_policy, "IPSec")
self._validate_policy_pfs(ipsec_policy, "IPSec")
# Ensure IPSec policy encap mode is tunnel
mode = ipsec_policy.get('encapsulation_mode')
if mode and mode not in ipsec_utils.ENCAPSULATION_MODE_MAP.keys():
msg = _("Unsupported encapsulation mode: %s. Only 'tunnel' mode "
"is supported.") % mode
raise nsx_exc.NsxVpnValidationError(details=msg)
# Ensure IPSec policy transform protocol is esp
prot = ipsec_policy.get('transform_protocol')
if prot and prot not in ipsec_utils.TRANSFORM_PROTOCOL_MAP.keys():
msg = _("Unsupported transform protocol: %s. Only 'esp' protocol "
"is supported.") % prot
raise nsx_exc.NsxVpnValidationError(details=msg)
def validate_ike_policy(self, context, ike_policy):
# Call general validations
super(IPsecCommonValidator, self).validate_ike_policy(
context, ike_policy)
# Call specific NSX validations
self._validate_policy_lifetime(ike_policy, "IKE")
self._validate_policy_auth_algorithm(ike_policy, "IKE")
self._validate_policy_encryption_algorithm(ike_policy, "IKE")
self._validate_policy_pfs(ike_policy, "IKE")
# 'aggressive' phase1-negotiation-mode is not supported
if ike_policy.get('phase1-negotiation-mode', 'main') != 'main':
msg = _("Unsupported phase1-negotiation-mode: %s! Only 'main' is "
"supported.") % ike_policy['phase1-negotiation-mode']
raise nsx_exc.NsxVpnValidationError(details=msg)

View File

@ -0,0 +1,715 @@
# Copyright 2019 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_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from neutron_lib import constants
from neutron_lib import context as n_context
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.services.vpnaas.common_v3 import ipsec_driver as common_driver
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
from vmware_nsx.services.vpnaas.nsxp import ipsec_validator
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as consts
from vmware_nsxlib.v3.policy import constants as policy_constants
LOG = logging.getLogger(__name__)
IPSEC = 'ipsec'
class NSXpIPsecVpnDriver(common_driver.NSXcommonIPsecVpnDriver):
def __init__(self, service_plugin):
validator = ipsec_validator.IPsecNsxPValidator(service_plugin)
super(NSXpIPsecVpnDriver, self).__init__(service_plugin, validator)
self._nsxpolicy = self._core_plugin.nsxpolicy
self._nsx_vpn = self._nsxpolicy.ipsec_vpn
def _get_service_local_cidr_group(self, context, vpnservice, cidrs):
"""Create/Override the group for the local cidrs of a vpnservice
used for the edge firewall rules allowing the vpn traffic.
Return the group id, which is the same as the service id.
"""
group_id = vpnservice['id']
expr = self._nsxpolicy.group.build_ip_address_expression(cidrs)
tags = self._nsxpolicy.build_v3_tags_payload(
vpnservice,
resource_type='os-vpn-service-id',
project_name=context.tenant_name)
self._nsxpolicy.group.create_or_overwrite_with_conditions(
"Local group for VPN service %s" % vpnservice['id'],
policy_constants.DEFAULT_DOMAIN, group_id=group_id,
conditions=[expr], tags=tags)
return group_id
def _delete_service_local_cidr_group(self, vpnservice):
try:
self._nsxpolicy.group.delete(
policy_constants.DEFAULT_DOMAIN, group_id=vpnservice['id'])
except nsx_lib_exc.ResourceNotFound:
# If there is no FWaaS on the router it may not have been created
LOG.debug("Cannot delete local CIDR group for vpnservice %s as "
"it was not found", vpnservice['id'])
def _get_connection_local_cidr_group(self, context, connection, cidrs):
"""Create/Override the group for the local cidrs of a connection
used for the edge firewall rules allowing the vpn traffic.
Return the group id, which is the same as the connection id.
"""
group_id = connection['id']
expr = self._nsxpolicy.group.build_ip_address_expression(cidrs)
tags = self._nsxpolicy.build_v3_tags_payload(
connection,
resource_type='os-vpn-connection-id',
project_name=context.tenant_name)
self._nsxpolicy.group.create_or_overwrite_with_conditions(
"Local group for VPN connection %s" % connection['id'],
policy_constants.DEFAULT_DOMAIN, group_id=group_id,
conditions=[expr], tags=tags)
return group_id
def _delete_connection_local_cidr_group(self, connection):
try:
self._nsxpolicy.group.delete(
policy_constants.DEFAULT_DOMAIN, group_id=connection['id'])
except nsx_lib_exc.ResourceNotFound:
# If there is no FWaaS on the router it may not have been created
LOG.debug("Cannot delete local CIDR group for connection %s as "
"it was not found", connection['id'])
def _get_peer_cidr_group(self, context, conn):
"""Create/Override the group for the peer cidrs of a connection
used for the edge firewall rules allowing the vpn traffic.
Return the group id, which is the same as the connection id.
"""
group_ips = self.validator._get_peer_cidrs(context, conn)
group_id = conn['id']
expr = self._nsxpolicy.group.build_ip_address_expression(group_ips)
tags = self._nsxpolicy.build_v3_tags_payload(
conn,
resource_type='os-vpn-connection-id',
project_name=context.tenant_name)
self._nsxpolicy.group.create_or_overwrite_with_conditions(
"Peer group for VPN connection %s" % conn['id'],
policy_constants.DEFAULT_DOMAIN, group_id=group_id,
conditions=[expr], tags=tags)
return group_id
def _delete_peer_cidr_group(self, conn):
try:
self._nsxpolicy.group.delete(
policy_constants.DEFAULT_DOMAIN, group_id=conn['id'])
except nsx_lib_exc.ResourceNotFound:
# If there is no FWaaS on the router it may not have been created
LOG.debug("Cannot delete peer CIDR group for connection %s as "
"it was not found", conn['id'])
def _generate_ipsecvpn_firewall_rules(self, plugin_type, context,
router_id=None):
"""Return the firewall rules needed to allow vpn traffic"""
fw_rules = []
# get all the active services of this router
filters = {'router_id': [router_id],
'status': [constants.ACTIVE]}
services = self.vpn_plugin.get_vpnservices(
context.elevated(), filters=filters)
if not services:
return fw_rules
for srv in services:
subnet_id = None
if srv['subnet_id']:
subnet = self.l3_plugin.get_subnet(
context.elevated(), srv['subnet_id'])
local_cidrs = [subnet['cidr']]
local_group = self._get_service_local_cidr_group(
context, srv, local_cidrs)
# get all the non-errored connections of this service
filters = {'vpnservice_id': [srv['id']],
'status': [constants.ACTIVE, constants.DOWN]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context.elevated(), filters=filters)
for conn in connections:
if not subnet_id:
# Get local endpoint from group
local_cidrs = self.validator._get_local_cidrs(
context.elevated(), conn)
local_group = self._get_connection_local_cidr_group(
context, conn, local_cidrs)
peer_group = self._get_peer_cidr_group(
context.elevated(), conn)
fw_rules.append(self._nsxpolicy.gateway_policy.build_entry(
'VPN connection ' + conn['id'],
policy_constants.DEFAULT_DOMAIN, router_id,
action=consts.FW_ACTION_ALLOW,
dest_groups=[peer_group],
source_groups=[local_group],
scope=[self._nsxpolicy.tier1.get_path(router_id)],
direction=consts.IN_OUT))
return fw_rules
def _update_firewall_rules(self, context, vpnservice, conn, delete=False):
LOG.debug("Updating vpn firewall rules for router %s",
vpnservice['router_id'])
self._core_plugin.update_router_firewall(
context, vpnservice['router_id'])
# if it is during delete - try to delete the group of this connection
if delete:
self._delete_peer_cidr_group(conn)
def update_router_advertisement(self, context, router_id):
"""Advertise the local subnets of all the services on the router"""
# Do nothing in case of a router with no GW or no-snat router
# (as it is already advertised)
rtr = self.l3_plugin.get_router(context, router_id)
if (not rtr.get('external_gateway_info') or
not rtr['external_gateway_info'].get('enable_snat', True)):
return
LOG.debug("Updating router advertisement rules for router %s",
router_id)
rules = []
# get all the active services of this router
filters = {'router_id': [router_id],
'status': [constants.ACTIVE]}
services = self.vpn_plugin.get_vpnservices(
context.elevated(), filters=filters)
rule_name_pref = 'VPN advertisement service'
for srv in services:
# use only services with non-errored connections
filters = {'vpnservice_id': [srv['id']],
'status': [constants.ACTIVE, constants.DOWN]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context.elevated(), filters=filters)
if not connections:
continue
if srv['subnet_id']:
subnet = self.l3_plugin.get_subnet(
context.elevated(), srv['subnet_id'])
local_cidrs = [subnet['cidr']]
else:
# get all connections local endpoints cidrs
local_cidrs = []
for conn in connections:
local_cidrs.extend(
self.validator._get_local_cidrs(
context.elevated(), conn))
rules.append(self._nsxpolicy.tier1.build_advertisement_rule(
"%s %s" % (rule_name_pref, srv['id']),
policy_constants.ADV_RULE_PERMIT,
policy_constants.ADV_RULE_OPERATOR_GE,
[policy_constants.ADV_RULE_TIER1_IPSEC_LOCAL_ENDPOINT],
local_cidrs))
self._nsxpolicy.tier1.update_advertisement_rules(
router_id, rules, name_prefix=rule_name_pref)
def _nsx_tags(self, context, object):
return self._nsxpolicy.build_v3_tags_payload(
object, resource_type='os-vpn-connection-id',
project_name=context.tenant_name)
def _create_ike_profile(self, context, connection):
"""Create an ike profile for a connection
Creating/overwriting IKE profile based on the openstack ike policy
upon connection creation.
There is no driver callback for profiles creation so it has to be
done on connection creation.
"""
ike_policy_id = connection['ikepolicy_id']
ikepolicy = self.vpn_plugin.get_ikepolicy(context, ike_policy_id)
tags = self._nsxpolicy.build_v3_tags_payload(
ikepolicy, resource_type='os-vpn-ikepol-id',
project_name=context.tenant_name)
try:
profile_id = self._nsx_vpn.ike_profile.create_or_overwrite(
ikepolicy['name'] or ikepolicy['id'],
profile_id=ikepolicy['id'],
description=ikepolicy['description'],
encryption_algorithms=[ipsec_utils.ENCRYPTION_ALGORITHM_MAP[
ikepolicy['encryption_algorithm']]],
digest_algorithms=[ipsec_utils.AUTH_ALGORITHM_MAP_P[
ikepolicy['auth_algorithm']]],
ike_version=ipsec_utils.IKE_VERSION_MAP[
ikepolicy['ike_version']],
dh_groups=[ipsec_utils.PFS_MAP_P[ikepolicy['pfs']]],
sa_life_time=ikepolicy['lifetime']['value'],
tags=tags)
except nsx_lib_exc.ManagerError as e:
msg = _("Failed to create an ike profile: %s") % e
raise nsx_exc.NsxPluginException(err_msg=msg)
return profile_id
def _delete_ike_profile(self, ikeprofile_id):
try:
self._nsx_vpn.ike_profile.delete(ikeprofile_id)
except nsx_lib_exc.ResourceInUse:
# Still in use by another connection
LOG.info("IKE profile %s cannot be deleted yet, because "
"another connection still uses it", ikeprofile_id)
def _create_ipsec_profile(self, context, connection):
"""Create a tunnel profile for a connection
Creating/overwriting tunnel profile based on the openstack ipsec policy
upon connection creation.
There is no driver callback for profiles creation so it has to be
done on connection creation.
"""
ipsec_policy_id = connection['ipsecpolicy_id']
ipsecpolicy = self.vpn_plugin.get_ipsecpolicy(
context, ipsec_policy_id)
tags = self._nsxpolicy.build_v3_tags_payload(
ipsecpolicy, resource_type='os-vpn-ipsecpol-id',
project_name=context.tenant_name)
try:
profile_id = self._nsx_vpn.tunnel_profile.create_or_overwrite(
ipsecpolicy['name'] or ipsecpolicy['id'],
profile_id=ipsecpolicy['id'],
description=ipsecpolicy['description'],
encryption_algorithms=[ipsec_utils.ENCRYPTION_ALGORITHM_MAP[
ipsecpolicy['encryption_algorithm']]],
digest_algorithms=[ipsec_utils.AUTH_ALGORITHM_MAP_P[
ipsecpolicy['auth_algorithm']]],
dh_groups=[ipsec_utils.PFS_MAP_P[ipsecpolicy['pfs']]],
sa_life_time=ipsecpolicy['lifetime']['value'],
tags=tags)
except nsx_lib_exc.ManagerError as e:
msg = _("Failed to create a tunnel profile: %s") % e
raise nsx_exc.NsxPluginException(err_msg=msg)
return profile_id
def _delete_ipsec_profile(self, ipsecprofile_id):
try:
self._nsx_vpn.tunnel_profile.delete(ipsecprofile_id)
except nsx_lib_exc.ResourceInUse:
# Still in use by another connection
LOG.info("Tunnel profile %s cannot be deleted yet, because "
"another connection still uses it", ipsecprofile_id)
def _create_dpd_profile(self, context, connection):
"""Create a DPD profile for a connection
Creating/overwriting DPD profile based on the openstack ipsec
connection configuration upon connection creation.
There is no driver callback for profiles creation so it has to be
done on connection creation.
"""
# TODO(asarfaty) consider reusing profiles based on values
dpd_info = connection['dpd']
try:
profile_id = self._nsx_vpn.dpd_profile.create_or_overwrite(
self._get_dpd_profile_name(connection),
profile_id=connection['id'],
description='neutron dpd profile %s' % connection['id'],
dpd_probe_interval=dpd_info.get('timeout'),
enabled=True if dpd_info.get('action') == 'hold' else False,
tags=self._nsx_tags(context, connection))
except nsx_lib_exc.ManagerError as e:
msg = _("Failed to create a DPD profile: %s") % e
raise nsx_exc.NsxPluginException(err_msg=msg)
return profile_id
def _delete_dpd_profile(self, dpdprofile_id):
self._nsx_vpn.dpd_profile.delete(dpdprofile_id)
def _update_dpd_profile(self, connection):
dpd_info = connection['dpd']
self._nsx_vpn.dpd_profile.update(
connection['id'],
name=self._get_dpd_profile_name(connection),
dpd_probe_interval=dpd_info.get('timeout'),
enabled=True if dpd_info.get('action') == 'hold' else False)
def _create_local_endpoint(self, context, connection, vpnservice):
"""Creating/overwrite an NSX local endpoint for a logical router
This endpoint can be reused by other connections, and will be deleted
when the router vpn service is deleted.
"""
# use the router GW as the local ip
router_id = vpnservice['router']['id']
local_addr = vpnservice['external_v4_ip']
# Add the neutron router-id to the tags to help search later
tags = self._nsxpolicy.build_v3_tags_payload(
{'id': router_id, 'project_id': vpnservice['project_id']},
resource_type='os-neutron-router-id',
project_name=context.tenant_name)
try:
ep_client = self._nsx_vpn.local_endpoint
local_endpoint_id = ep_client.create_or_overwrite(
'Local endpoint for OS VPNaaS on router %s' % router_id,
router_id,
router_id,
endpoint_id=router_id,
local_address=local_addr,
tags=tags)
except nsx_lib_exc.ManagerError as e:
msg = _("Failed to create a local endpoint: %s") % e
raise nsx_exc.NsxPluginException(err_msg=msg)
return local_endpoint_id
def _delete_local_endpoint(self, vpnservice):
router_id = vpnservice['router']['id']
ctx = n_context.get_admin_context()
port = self._find_vpn_service_port(ctx, router_id)
if port:
self._nsx_vpn.local_endpoint.delete(
router_id, router_id, router_id)
self.l3_plugin.delete_port(ctx, port['id'], force_delete_vpn=True)
def _get_session_rules(self, context, connection):
peer_cidrs = self.validator._get_peer_cidrs(context, connection)
local_cidrs = self.validator._get_local_cidrs(context, connection)
rule = self._nsx_vpn.session.build_rule(
connection['name'] or connection['id'], connection['id'],
source_cidrs=local_cidrs, destination_cidrs=peer_cidrs)
return [rule]
def _create_session(self, context, connection, vpnservice, local_ep_id,
ikeprofile_id, ipsecprofile_id, dpdprofile_id,
rules, enabled=True):
try:
router_id = vpnservice['router_id']
session_id = self._nsx_vpn.session.create_or_overwrite(
connection['name'] or connection['id'],
tier1_id=router_id,
vpn_service_id=router_id,
session_id=connection['id'],
description=connection['description'],
peer_address=connection['peer_address'],
peer_id=connection['peer_id'],
psk=connection['psk'],
rules=rules,
dpd_profile_id=dpdprofile_id,
ike_profile_id=ikeprofile_id,
tunnel_profile_id=ipsecprofile_id,
local_endpoint_id=local_ep_id,
enabled=enabled,
tags=self._nsx_tags(context, connection))
except nsx_lib_exc.ManagerError as e:
msg = _("Failed to create a session: %s") % e
raise nsx_exc.NsxPluginException(err_msg=msg)
return session_id
def _update_session(self, connection, vpnservice, rules=None,
enabled=True):
router_id = vpnservice['router_id']
args = {'enabled': enabled}
if rules is not None:
args['rules'] = rules
self._nsx_vpn.session.update(
router_id, router_id, connection['id'],
name=connection['name'] or connection['id'],
description=connection['description'],
peer_address=connection['peer_address'],
peer_id=connection['peer_id'],
psk=connection['psk'],
**args)
def get_ipsec_site_connection_status(self, context, ipsec_site_conn_id):
# find out the router-id of this connection
conn = self.vpn_plugin._get_ipsec_site_connection(
context, ipsec_site_conn_id)
vpnservice_id = conn.vpnservice_id
vpnservice = self.service_plugin._get_vpnservice(
context, vpnservice_id)
router_id = vpnservice['router_id']
# Get the NSX detailed status
try:
status_result = self._nsx_vpn.session.get_status(
router_id, router_id, ipsec_site_conn_id)
if status_result and 'results' in status_result:
status = status_result['results'][0].get('runtime_status', '')
# NSX statuses are UP, DOWN, DEGRADE
# VPNaaS connection status should be ACTIVE or DOWN
if status == 'UP':
return 'ACTIVE'
elif status == 'DOWN' or status == 'DEGRADED':
return 'DOWN'
except nsx_lib_exc.ResourceNotFound:
LOG.debug("Status for VPN session %s was not found",
ipsec_site_conn_id)
def _delete_session(self, vpnservice, session_id):
router_id = vpnservice['router_id']
self._nsx_vpn.session.delete(router_id, router_id, session_id)
def create_ipsec_site_connection(self, context, ipsec_site_conn):
LOG.debug('Creating ipsec site connection %(conn_info)s.',
{"conn_info": ipsec_site_conn})
# Note(asarfaty) the plugin already calls the validator
# which also validated the policies and service
ikeprofile_id = None
ipsecprofile_id = None
dpdprofile_id = None
session_id = None
vpnservice_id = ipsec_site_conn['vpnservice_id']
vpnservice = self.service_plugin._get_vpnservice(
context, vpnservice_id)
ipsec_id = ipsec_site_conn["id"]
try:
# create the ike profile
ikeprofile_id = self._create_ike_profile(
context, ipsec_site_conn)
LOG.debug("Created NSX ike profile %s", ikeprofile_id)
# create the ipsec profile
ipsecprofile_id = self._create_ipsec_profile(
context, ipsec_site_conn)
LOG.debug("Created NSX ipsec profile %s", ipsecprofile_id)
# create the dpd profile
dpdprofile_id = self._create_dpd_profile(
context, ipsec_site_conn)
LOG.debug("Created NSX dpd profile %s", dpdprofile_id)
# create or reuse a local endpoint using the vpn service
local_ep_id = self._create_local_endpoint(
context, ipsec_site_conn, vpnservice)
# Finally: create the session with policy rules
rules = self._get_session_rules(context, ipsec_site_conn)
connection_enabled = (vpnservice['admin_state_up'] and
ipsec_site_conn['admin_state_up'])
self._create_session(
context, ipsec_site_conn, vpnservice,
local_ep_id, ikeprofile_id,
ipsecprofile_id, dpdprofile_id, rules,
enabled=connection_enabled)
self._update_status(context, vpnservice_id, ipsec_id,
constants.ACTIVE)
except nsx_exc.NsxPluginException:
with excutils.save_and_reraise_exception():
self._update_status(context, vpnservice_id, ipsec_id,
constants.ERROR)
# delete the NSX objects that were already created
# Do not delete reused objects: service, local endpoint
if session_id:
self._delete_session(vpnservice, session_id)
if dpdprofile_id:
self._delete_dpd_profile(dpdprofile_id)
if ipsecprofile_id:
self._delete_ipsec_profile(ipsecprofile_id)
if ikeprofile_id:
self._delete_ike_profile(ikeprofile_id)
# update router firewall rules
self._update_firewall_rules(context, vpnservice, ipsec_site_conn)
# update router advertisement rules
self.update_router_advertisement(context, vpnservice['router_id'])
def delete_ipsec_site_connection(self, context, ipsec_site_conn):
LOG.debug('Deleting ipsec site connection %(site)s.',
{"site": ipsec_site_conn})
vpnservice_id = ipsec_site_conn['vpnservice_id']
vpnservice = self.service_plugin._get_vpnservice(
context, vpnservice_id)
self._delete_session(vpnservice, ipsec_site_conn['id'])
self._delete_dpd_profile(ipsec_site_conn['id'])
self._delete_ipsec_profile(ipsec_site_conn['ipsecpolicy_id'])
self._delete_ike_profile(ipsec_site_conn['ikepolicy_id'])
# update router firewall rules
self._update_firewall_rules(context, vpnservice, ipsec_site_conn,
delete=True)
self._delete_service_local_cidr_group(ipsec_site_conn)
# update router advertisement rules
self.update_router_advertisement(context, vpnservice['router_id'])
def update_ipsec_site_connection(self, context, old_ipsec_conn,
ipsec_site_conn):
LOG.debug('Updating ipsec site connection new %(site)s.',
{"site": ipsec_site_conn})
LOG.debug('Updating ipsec site connection old %(site)s.',
{"site": old_ipsec_conn})
# Note(asarfaty) the plugin already calls the validator
# which also validated the policies and service
# Note(asarfaty): the VPN plugin does not allow changing ike/tunnel
# policy or the service of a connection during update.
vpnservice_id = old_ipsec_conn['vpnservice_id']
vpnservice = self.service_plugin._get_vpnservice(
context, vpnservice_id)
# check if the dpd configuration changed
old_dpd = old_ipsec_conn['dpd']
new_dpd = ipsec_site_conn['dpd']
if (old_dpd['action'] != new_dpd['action'] or
old_dpd['timeout'] != new_dpd['timeout'] or
old_ipsec_conn['name'] != ipsec_site_conn['name']):
self._update_dpd_profile(ipsec_site_conn)
rules = self._get_session_rules(context, ipsec_site_conn)
connection_enabled = (vpnservice['admin_state_up'] and
ipsec_site_conn['admin_state_up'])
try:
self._update_session(ipsec_site_conn, vpnservice, rules,
enabled=connection_enabled)
except nsx_lib_exc.ManagerError as e:
self._update_status(context, vpnservice_id,
ipsec_site_conn['id'],
constants.ERROR)
msg = _("Failed to update VPN session %(id)s: %(error)s") % {
"id": ipsec_site_conn['id'], "error": e}
raise nsx_exc.NsxPluginException(err_msg=msg)
if (ipsec_site_conn['peer_cidrs'] != old_ipsec_conn['peer_cidrs'] or
ipsec_site_conn['peer_ep_group_id'] !=
old_ipsec_conn['peer_ep_group_id']):
# Update firewall
self._update_firewall_rules(context, vpnservice, ipsec_site_conn)
# No service updates. No need to update router advertisement rules
def _create_vpn_service(self, context, vpnservice):
"""Create or overwrite tier1 vpn service
The service is created on the TIER1 router attached to the service
The NSX can keep only one service per tier1 router so we reuse it
"""
router_id = vpnservice['router_id']
tags = self._nsxpolicy.build_v3_tags_payload(
{'id': router_id, 'project_id': vpnservice['project_id']},
resource_type='os-neutron-router-id',
project_name=context.tenant_name)
self._nsx_vpn.service.create_or_overwrite(
'Neutron VPN service for T1 router ' + router_id,
router_id,
vpn_service_id=router_id,
enabled=True,
ike_log_level=ipsec_utils.DEFAULT_LOG_LEVEL,
tags=tags)
def _should_delete_nsx_service(self, context, vpnservice):
# Check that no neutron vpn-service is configured for the same router
router_id = vpnservice['router_id']
filters = {'router_id': [router_id]}
services = self.vpn_plugin.get_vpnservices(
context.elevated(), filters=filters)
if not services:
return True
def _delete_vpn_service(self, context, vpnservice):
router_id = vpnservice['router_id']
try:
self._nsx_vpn.service.delete(router_id, router_id)
except Exception as e:
LOG.error("Failed to delete VPN service %s: %s",
router_id, e)
# check if service router should be deleted
if not self._core_plugin.service_router_has_services(
context.elevated(), router_id):
self._core_plugin.delete_service_router(router_id)
def create_vpnservice(self, context, new_vpnservice):
LOG.info('Creating VPN service %(vpn)s', {'vpn': new_vpnservice})
vpnservice_id = new_vpnservice['id']
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_id)
try:
self.validator.validate_vpnservice(context, vpnservice)
local_address = self._get_service_local_address(
context.elevated(), vpnservice)
except Exception:
with excutils.save_and_reraise_exception():
# Rolling back change on the neutron
self.service_plugin.delete_vpnservice(context, vpnservice_id)
vpnservice['external_v4_ip'] = local_address
self.service_plugin.set_external_tunnel_ips(context,
vpnservice_id,
v4_ip=local_address)
# Make sure this tier1 has service router
router_id = vpnservice['router_id']
if not self._core_plugin.verify_sr_at_backend(router_id):
self._core_plugin.create_service_router(context, router_id)
# create the NSX vpn service
try:
self._create_vpn_service(context, vpnservice)
except nsx_lib_exc.ManagerError as e:
self._update_status(context, vpnservice_id, None, constants.ERROR)
msg = _("Failed to create vpn service: %s") % e
raise nsx_exc.NsxPluginException(err_msg=msg)
# update neutron vpnservice status to active
self._update_status(context, vpnservice_id, None, constants.ACTIVE)
def update_vpnservice(self, context, old_vpnservice, vpnservice):
# Only handle the case of admin-state-up changes
if old_vpnservice['admin_state_up'] != vpnservice['admin_state_up']:
# update all relevant connections
filters = {'vpnservice_id': [vpnservice['id']]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context, filters=filters)
for conn in connections:
connection_enabled = (vpnservice['admin_state_up'] and
conn['admin_state_up'])
self._update_session(conn, vpnservice,
enabled=connection_enabled)
def delete_vpnservice(self, context, vpnservice):
if self._should_delete_nsx_service(context, vpnservice):
self._delete_local_endpoint(vpnservice)
self._delete_vpn_service(context, vpnservice)
self._delete_service_local_cidr_group(vpnservice)
def validate_router_gw_info(self, context, router_id, gw_info):
"""Upon router gw update verify no overlapping subnets to advertise"""
# check if this router has a vpn service
admin_con = context.elevated()
# get all relevant services, except those waiting to be deleted or in
# ERROR state
filters = {'router_id': [router_id],
'status': [constants.ACTIVE, constants.PENDING_CREATE,
constants.INACTIVE, constants.PENDING_UPDATE]}
services = self.vpn_plugin.get_vpnservices(admin_con, filters=filters)
if not services:
# This is a non-vpn router. if snat was disabled, should check
# there is no overlapping with vpn connections advertised
if (gw_info and
not gw_info.get('enable_snat',
cfg.CONF.enable_snat_by_default)):
# get router subnets
subnets = self._core_plugin._find_router_subnets_cidrs(
context, router_id)
# find all vpn services with connections
if not self._check_subnets_overlap_with_all_conns(
admin_con, subnets):
raise common_driver.RouterWithOverlapNoSnat(
router_id=router_id)

View File

@ -0,0 +1,49 @@
# Copyright 2019 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 vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
from vmware_nsx.services.vpnaas.common_v3 import ipsec_validator
LOG = logging.getLogger(__name__)
class IPsecNsxPValidator(ipsec_validator.IPsecCommonValidator):
"""Validator methods for Vmware NSX-Policy VPN support"""
def __init__(self, service_plugin):
super(IPsecNsxPValidator, self).__init__(service_plugin)
self.nsxpolicy = self._core_plugin.nsxpolicy
@property
def auth_algorithm_map(self):
return ipsec_utils.AUTH_ALGORITHM_MAP_P
@property
def pfs_map(self):
return ipsec_utils.PFS_MAP_P
def _validate_t0_ha_mode(self, tier0_uuid):
tier0_router = self.nsxpolicy.tier0.get(tier0_uuid)
if (not tier0_router or
tier0_router.get('ha_mode') != 'ACTIVE_STANDBY'):
msg = _("The router GW should be connected to a TIER-0 router "
"with ACTIVE_STANDBY HA mode")
raise nsx_exc.NsxVpnValidationError(details=msg)
def _support_endpoint_groups(self):
return True

View File

@ -23,14 +23,11 @@ from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants
from neutron_lib import context as n_context
from neutron_lib import exceptions as nexception
from neutron_lib.plugins import directory
from neutron_vpnaas.services.vpn import service_drivers
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.db import db
from vmware_nsx.extensions import projectpluginmap
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils
from vmware_nsx.services.vpnaas.common_v3 import ipsec_driver as common_driver
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_validator
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
from vmware_nsxlib.v3 import nsx_constants as consts
@ -40,50 +37,18 @@ LOG = logging.getLogger(__name__)
IPSEC = 'ipsec'
class RouterWithSNAT(nexception.BadRequest):
message = _("Router %(router_id)s has a VPN service and cannot enable "
"SNAT")
class RouterWithOverlapNoSnat(nexception.BadRequest):
message = _("Router %(router_id)s has a subnet overlapping with a VPN "
"local subnet, and cannot disable SNAT")
class RouterOverlapping(nexception.BadRequest):
message = _("Router %(router_id)s interface is overlapping with a VPN "
"local subnet and cannot be added")
class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
class NSXv3IPsecVpnDriver(common_driver.NSXcommonIPsecVpnDriver):
def __init__(self, service_plugin):
self.vpn_plugin = service_plugin
self._core_plugin = directory.get_plugin()
if self._core_plugin.is_tvd_plugin():
self._core_plugin = self._core_plugin.get_plugin_by_type(
projectpluginmap.NsxPlugins.NSX_T)
self._nsxlib = self._core_plugin.nsxlib
self._nsx_vpn = self._nsxlib.vpn_ipsec
validator = ipsec_validator.IPsecV3Validator(service_plugin)
super(NSXv3IPsecVpnDriver, self).__init__(service_plugin, validator)
self._nsxlib = self._core_plugin.nsxlib
self._nsx_vpn = self._nsxlib.vpn_ipsec
registry.subscribe(
self._delete_local_endpoint, resources.ROUTER_GATEWAY,
events.AFTER_DELETE)
registry.subscribe(
self._verify_overlap_subnet, resources.ROUTER_INTERFACE,
events.BEFORE_CREATE)
@property
def l3_plugin(self):
return self._core_plugin
@property
def service_type(self):
return IPSEC
def _translate_cidr(self, cidr):
return self._nsxlib.firewall_section.get_ip_cidr_reference(
cidr,
@ -170,18 +135,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
self._nsxlib.logical_router.update_advertisement_rules(
logical_router_id, rules, name_prefix=rule_name_pref)
def _update_status(self, context, vpn_service_id, ipsec_site_conn_id,
status, updated_pending_status=True):
ipsec_site_conn = {'status': status,
'updated_pending_status': updated_pending_status}
vpn_status = {'id': vpn_service_id,
'updated_pending_status': updated_pending_status,
'status': status,
'ipsec_site_connections': {ipsec_site_conn_id:
ipsec_site_conn}}
status_list = [vpn_status]
self.service_plugin.update_status_by_agent(context, status_list)
def _nsx_tags(self, context, connection):
return self._nsxlib.build_v3_tags_payload(
connection, resource_type='os-vpn-connection-id',
@ -251,9 +204,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
def _delete_ipsec_profile(self, ipsecprofile_id):
self._nsx_vpn.tunnel_profile.delete(ipsecprofile_id)
def _get_dpd_profile_name(self, connection):
return (connection['name'] or connection['id'])[:240] + '-dpd-profile'
def _create_dpd_profile(self, context, connection):
dpd_info = connection['dpd']
try:
@ -378,14 +328,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
vpnservice['project_id'])
return local_ep_id
def _find_vpn_service_port(self, context, router_id):
"""Look for the neutron port created for the vpnservice of a router"""
filters = {'device_id': ['router-' + router_id],
'device_owner': [ipsec_utils.VPN_PORT_OWNER]}
ports = self.l3_plugin.get_ports(context, filters=filters)
if ports:
return ports[0]
def _delete_local_endpoint_by_router(self, context, router_id):
# delete the local endpoint from the NSX
local_ep_id = self._search_local_endpint(router_id)
@ -403,43 +345,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
ctx = n_context.get_admin_context()
self._delete_local_endpoint_by_router(ctx, router_id)
def _check_subnets_overlap_with_all_conns(self, context, subnets):
# find all vpn services with connections
filters = {'status': [constants.ACTIVE]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context, filters=filters)
for conn in connections:
srv_id = conn.get('vpnservice_id')
srv = self.vpn_plugin._get_vpnservice(context, srv_id)
srv_subnet = self.l3_plugin.get_subnet(
context, srv['subnet_id'])
if netaddr.IPSet(subnets) & netaddr.IPSet([srv_subnet['cidr']]):
return False
return True
def _verify_overlap_subnet(self, resource, event, trigger, **kwargs):
"""Upon router interface creation validation overlapping with vpn"""
router_db = kwargs.get('router_db')
port = kwargs.get('port')
if not port or not router_db:
LOG.warning("NSX V3 VPNaaS ROUTER_INTERFACE BEFORE_CRAETE "
"callback didn't get all the relevant information")
return
if router_db.enable_snat:
# checking only no-snat routers
return
admin_con = n_context.get_admin_context()
subnet_id = port['fixed_ips'][0].get('subnet_id')
if subnet_id:
subnet = self._core_plugin.get_subnet(admin_con, subnet_id)
# find all vpn services with connections
if not self._check_subnets_overlap_with_all_conns(
admin_con, [subnet['cidr']]):
raise RouterOverlapping(router_id=kwargs.get('router_id'))
def validate_router_gw_info(self, context, router_id, gw_info):
"""Upon router gw update - verify no-snat"""
# check if this router has a vpn service
@ -454,7 +359,7 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
# do not allow enable-snat
if (gw_info and
gw_info.get('enable_snat', cfg.CONF.enable_snat_by_default)):
raise RouterWithSNAT(router_id=router_id)
raise common_driver.RouterWithSNAT(router_id=router_id)
else:
# if this is a non-vpn router. if snat was disabled, should check
# there is no overlapping with vpn connections
@ -467,7 +372,8 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
# find all vpn services with connections
if not self._check_subnets_overlap_with_all_conns(
admin_con, subnets):
raise RouterWithOverlapNoSnat(router_id=router_id)
raise common_driver.RouterWithOverlapNoSnat(
router_id=router_id)
def _get_session_rules(self, context, connection, vpnservice):
# TODO(asarfaty): support vpn-endpoint-groups too
@ -701,16 +607,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
return service['id']
def _get_tier0_uuid(self, context, vpnservice):
router_id = vpnservice['router_id']
router_db = self._core_plugin._get_router(context, router_id)
return self._core_plugin._get_tier0_uuid_by_router(context, router_db)
def _get_router_ext_gw(self, context, router_id):
router_db = self._core_plugin.get_router(context, router_id)
gw = router_db['external_gateway_info']
return gw['external_fixed_ips'][0]["ip_address"]
def _find_vpn_service(self, tier0_uuid, validate=True):
# find the service for the tier0 router in the NSX.
# Note(asarfaty) we expect only a small number of services
@ -774,34 +670,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
tier0_uuid = self._get_tier0_uuid(context, vpnservice)
return self._find_vpn_service(tier0_uuid, validate=False)
def _get_service_local_address(self, context, vpnservice):
"""Find/Allocate a port on the external network
to save the ip to be used as the local ip of this service
"""
router_id = vpnservice['router_id']
# check if this router already have an IP
port = self._find_vpn_service_port(context, router_id)
if not port:
# create a new port, on the external network of the router
# Note(asarfaty): using a unique device owner and device id to
# make sure tis port will be ignored in certain queries
ext_net = vpnservice['router']['gw_port']['network_id']
port_data = {
'port': {
'network_id': ext_net,
'name': 'VPN local address port',
'admin_state_up': True,
'device_id': 'router-' + router_id,
'device_owner': ipsec_utils.VPN_PORT_OWNER,
'fixed_ips': constants.ATTR_NOT_SPECIFIED,
'mac_address': constants.ATTR_NOT_SPECIFIED,
'port_security_enabled': False,
'tenant_id': vpnservice['tenant_id']}}
port = self.l3_plugin.base_create_port(context, port_data)
# return the port ip as the local address
return port['fixed_ips'][0]['ip_address']
def create_vpnservice(self, context, vpnservice):
#TODO(asarfaty) support vpn-endpoint-group-create for local & peer
# cidrs too

View File

@ -13,37 +13,25 @@
# License for the specific language governing permissions and limitations
# under the License.
import netaddr
from oslo_config import cfg
from oslo_log import log as logging
from neutron_lib import constants
from neutron_vpnaas.db.vpn import vpn_validator
from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.extensions import projectpluginmap
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
from vmware_nsx.services.vpnaas.common_v3 import ipsec_validator
from vmware_nsxlib.v3 import nsx_constants as consts
from vmware_nsxlib.v3 import vpn_ipsec
LOG = logging.getLogger(__name__)
class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
class IPsecV3Validator(ipsec_validator.IPsecCommonValidator):
"""Validator methods for Vmware NSX-V3 VPN support"""
def __init__(self, service_plugin):
super(IPsecV3Validator, self).__init__()
self.vpn_plugin = service_plugin
super(IPsecV3Validator, self).__init__(service_plugin)
self._core_plugin = self.core_plugin
if self._core_plugin.is_tvd_plugin():
self._core_plugin = self._core_plugin.get_plugin_by_type(
projectpluginmap.NsxPlugins.NSX_T)
self.nsxlib = self._core_plugin.nsxlib
self.check_backend_version()
@property
def nsxlib(self):
return self._core_plugin.nsxlib
def check_backend_version(self):
if not self.nsxlib.feature_supported(consts.FEATURE_IPSEC_VPN):
@ -61,260 +49,15 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
"(version %s)") % self.nsxlib.get_version())
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_policy_lifetime(self, policy_info, policy_type):
"""NSX supports only units=seconds"""
lifetime = policy_info.get('lifetime')
if not lifetime:
return
if lifetime.get('units') != 'seconds':
msg = _("Unsupported policy lifetime %(val)s in %(pol)s policy. "
"Only seconds lifetime is supported.") % {
'val': lifetime, 'pol': policy_type}
raise nsx_exc.NsxVpnValidationError(details=msg)
value = lifetime.get('value')
if policy_type == 'IKE':
limits = vpn_ipsec.IkeSALifetimeLimits
else:
limits = vpn_ipsec.IPsecSALifetimeLimits
if (value and (value < limits.SA_LIFETIME_MIN or
value > limits.SA_LIFETIME_MAX)):
msg = _("Unsupported policy lifetime %(value)s in %(pol)s policy. "
"Value range is [%(min)s-%(max)s].") % {
'value': value,
'pol': policy_type,
'min': limits.SA_LIFETIME_MIN,
'max': limits.SA_LIFETIME_MAX}
raise nsx_exc.NsxVpnValidationError(details=msg)
@property
def auth_algorithm_map(self):
return ipsec_utils.AUTH_ALGORITHM_MAP
def _validate_policy_auth_algorithm(self, policy_info, policy_type):
"""NSX supports only SHA1 and SHA256"""
auth = policy_info.get('auth_algorithm')
if auth and auth not in ipsec_utils.AUTH_ALGORITHM_MAP:
msg = _("Unsupported auth_algorithm: %(algo)s in %(pol)s policy. "
"Please select one of the following supported algorithms: "
"%(supported_algos)s") % {
'pol': policy_type,
'algo': auth,
'supported_algos':
ipsec_utils.AUTH_ALGORITHM_MAP.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
@property
def pfs_map(self):
return ipsec_utils.PFS_MAP
def _validate_policy_encryption_algorithm(self, policy_info, policy_type):
encryption = policy_info.get('encryption_algorithm')
if (encryption and
encryption not in ipsec_utils.ENCRYPTION_ALGORITHM_MAP):
msg = _("Unsupported encryption_algorithm: %(algo)s in %(pol)s "
"policy. Please select one of the following supported "
"algorithms: %(supported_algos)s") % {
'algo': encryption,
'pol': policy_type,
'supported_algos':
ipsec_utils.ENCRYPTION_ALGORITHM_MAP.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_policy_pfs(self, policy_info, policy_type):
pfs = policy_info.get('pfs')
if pfs and pfs not in ipsec_utils.PFS_MAP:
msg = _("Unsupported pfs: %(pfs)s in %(pol)s policy. Please "
"select one of the following pfs: "
"%(supported_pfs)s") % {
'pfs': pfs,
'pol': policy_type,
'supported_pfs':
ipsec_utils.PFS_MAP.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_dpd(self, connection):
dpd_info = connection.get('dpd')
if not dpd_info:
return
action = dpd_info.get('action')
if action not in ipsec_utils.DPD_ACTION_MAP.keys():
msg = _("Unsupported DPD action: %(action)s! Currently only "
"%(supported)s is supported.") % {
'action': action,
'supported': ipsec_utils.DPD_ACTION_MAP.keys()}
raise nsx_exc.NsxVpnValidationError(details=msg)
timeout = dpd_info.get('timeout')
if (timeout < vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MIN or
timeout > vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MAX):
msg = _("Unsupported DPD timeout: %(timeout)s. Value range is "
"[%(min)s-%(max)s].") % {
'timeout': timeout,
'min': vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MIN,
'max': vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MAX}
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_psk(self, connection):
if 'psk' in connection and not connection['psk']:
msg = _("'psk' cannot be empty or null when authentication "
"mode is psk")
raise nsx_exc.NsxVpnValidationError(details=msg)
def _check_policy_rules_overlap(self, context, ipsec_site_conn):
"""validate no overlapping policy rules
The nsx does not support overlapping policy rules cross
all tenants, and tier0 routers
"""
connections = self.vpn_plugin.get_ipsec_site_connections(
context.elevated())
if not connections:
return
vpnservice_id = ipsec_site_conn.get('vpnservice_id')
vpnservice = self.vpn_plugin._get_vpnservice(context, vpnservice_id)
local_cidrs = [vpnservice['subnet']['cidr']]
peer_cidrs = ipsec_site_conn['peer_cidrs']
for conn in connections:
# skip this connection and connections in non active state
if (conn['id'] == ipsec_site_conn.get('id') or
conn['status'] != constants.ACTIVE):
continue
# TODO(asarfaty): support peer groups too
# check if it overlaps with the peer cidrs
conn_peer_cidrs = conn['peer_cidrs']
if netaddr.IPSet(conn_peer_cidrs) & netaddr.IPSet(peer_cidrs):
# check if the local cidr also overlaps
con_service_id = conn.get('vpnservice_id')
con_service = self.vpn_plugin._get_vpnservice(
context.elevated(), con_service_id)
conn_local_cidr = [con_service['subnet']['cidr']]
if netaddr.IPSet(conn_local_cidr) & netaddr.IPSet(local_cidrs):
msg = (_("Cannot create a connection with overlapping "
"local and peer cidrs (%(local)s and %(peer)s) "
"as connection %(id)s") % {'local': local_cidrs,
'peer': peer_cidrs,
'id': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
def _check_unique_addresses(self, context, ipsec_site_conn):
"""Validate no repeating local & peer addresses (of all tenants)
The nsx does not support it cross all tenants, and tier0 routers
"""
vpnservice_id = ipsec_site_conn.get('vpnservice_id')
local_addr = self._get_service_local_address(context, vpnservice_id)
peer_address = ipsec_site_conn.get('peer_address')
filters = {'peer_address': [peer_address]}
connections = self.vpn_plugin.get_ipsec_site_connections(
context.elevated(), filters=filters)
for conn in connections:
# skip this connection and connections in non active state
if (conn['id'] == ipsec_site_conn.get('id') or
conn['status'] != constants.ACTIVE):
continue
# this connection has the same peer addr as ours.
# check the service local address
srv_id = conn.get('vpnservice_id')
srv_local = self._get_service_local_address(
context.elevated(), srv_id)
if srv_local == local_addr:
msg = (_("Cannot create another connection with the same "
"local address %(local)s and peer address %(peer)s "
"as connection %(id)s") % {'local': local_addr,
'peer': peer_address,
'id': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
def _check_advertisment_overlap(self, context, ipsec_site_conn):
"""Validate there is no overlapping advertisement of networks
The plugin advertise all no-snat routers networks + vpn local
networks.
The NSX does not allow different Tier1 router to advertise the
same subnets
"""
admin_con = context.elevated()
srv_id = ipsec_site_conn.get('vpnservice_id')
srv = self.vpn_plugin._get_vpnservice(admin_con, srv_id)
this_router = srv['router_id']
this_cidr = srv['subnet']['cidr']
# get all subnets of no-snat routers
all_routers = self._core_plugin.get_routers(admin_con)
nosnat_routers = [rtr for rtr in all_routers
if (rtr['id'] != this_router and
rtr.get('external_gateway_info') and
not rtr['external_gateway_info'].get(
'enable_snat',
cfg.CONF.enable_snat_by_default))]
for rtr in nosnat_routers:
if rtr['id'] == this_router:
continue
# go over the subnets of this router
subnets = self._core_plugin._find_router_subnets_cidrs(
admin_con, rtr['id'])
if subnets and netaddr.IPSet(subnets) & netaddr.IPSet([this_cidr]):
msg = (_("Cannot create connection with overlapping local "
"cidrs %(local)s which was already advertised by "
"no-snat router %(rtr)s") % {'local': subnets,
'rtr': rtr['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
# add all vpn local subnets
connections = self.vpn_plugin.get_ipsec_site_connections(admin_con)
for conn in connections:
# skip this connection and connections in non active state
if (conn['id'] == ipsec_site_conn.get('id') or
conn['status'] != constants.ACTIVE):
continue
# check the service local address
conn_srv_id = conn.get('vpnservice_id')
conn_srv = self.vpn_plugin._get_vpnservice(admin_con, conn_srv_id)
if conn_srv['router_id'] == this_router:
continue
conn_cidr = conn_srv['subnet']['cidr']
if netaddr.IPSet([conn_cidr]) & netaddr.IPSet([this_cidr]):
msg = (_("Cannot create connection with overlapping local "
"cidr %(local)s which was already advertised by "
"router %(rtr)s and connection %(conn)s") % {
'local': conn_cidr,
'rtr': conn_srv['router_id'],
'conn': conn['id']})
raise nsx_exc.NsxVpnValidationError(details=msg)
def validate_ipsec_site_connection(self, context, ipsec_site_conn):
"""Called upon create/update of a connection"""
self._validate_backend_version()
self._validate_dpd(ipsec_site_conn)
self._validate_psk(ipsec_site_conn)
ike_policy_id = ipsec_site_conn.get('ikepolicy_id')
if ike_policy_id:
ikepolicy = self.vpn_plugin.get_ikepolicy(context,
ike_policy_id)
self.validate_ike_policy(context, ikepolicy)
ipsec_policy_id = ipsec_site_conn.get('ipsecpolicy_id')
if ipsec_policy_id:
ipsecpolicy = self.vpn_plugin.get_ipsecpolicy(context,
ipsec_policy_id)
self.validate_ipsec_policy(context, ipsecpolicy)
if ipsec_site_conn.get('vpnservice_id'):
self._check_advertisment_overlap(context, ipsec_site_conn)
self._check_unique_addresses(context, ipsec_site_conn)
self._check_policy_rules_overlap(context, ipsec_site_conn)
#TODO(asarfaty): IPv6 is not yet supported. add validation
def _get_service_local_address(self, context, vpnservice_id):
"""The local address of the service is assigned upon creation
From the attached external network pool
"""
vpnservice = self.vpn_plugin._get_vpnservice(context,
vpnservice_id)
return vpnservice['external_v4_ip']
def _validate_router(self, context, router_id):
# Verify that the router gw network is connected to an active-standby
# Tier0 router
router_db = self._core_plugin._get_router(context, router_id)
tier0_uuid = self._core_plugin._get_tier0_uuid_by_router(context,
router_db)
def _validate_t0_ha_mode(self, tier0_uuid):
# TODO(asarfaty): cache this result
tier0_router = self.nsxlib.logical_router.get(tier0_uuid)
if (not tier0_router or
@ -323,69 +66,11 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
"with ACTIVE_STANDBY HA mode")
raise nsx_exc.NsxVpnValidationError(details=msg)
def _validate_router(self, context, router_id):
super(IPsecV3Validator, self)._validate_router(context, router_id)
# Verify that this is a no-snat router
router_db = self._core_plugin._get_router(context, router_id)
if router_db.enable_snat:
msg = _("VPN is supported only for routers with disabled SNAT")
raise nsx_exc.NsxVpnValidationError(details=msg)
def validate_vpnservice(self, context, vpnservice):
"""Called upon create/update of a service"""
self._validate_backend_version()
# Call general validations
super(IPsecV3Validator, self).validate_vpnservice(
context, vpnservice)
# Call specific NSX validations
self._validate_router(context, vpnservice['router_id'])
if not vpnservice['subnet_id']:
# we currently do not support multiple subnets so a subnet must
# be defined
msg = _("Subnet must be defined in a service")
raise nsx_exc.NsxVpnValidationError(details=msg)
#TODO(asarfaty): IPv6 is not yet supported. add validation
def validate_ipsec_policy(self, context, ipsec_policy):
# Call general validations
super(IPsecV3Validator, self).validate_ipsec_policy(
context, ipsec_policy)
# Call specific NSX validations
self._validate_policy_lifetime(ipsec_policy, "IPSec")
self._validate_policy_auth_algorithm(ipsec_policy, "IPSec")
self._validate_policy_encryption_algorithm(ipsec_policy, "IPSec")
self._validate_policy_pfs(ipsec_policy, "IPSec")
# Ensure IPSec policy encap mode is tunnel
mode = ipsec_policy.get('encapsulation_mode')
if mode and mode not in ipsec_utils.ENCAPSULATION_MODE_MAP.keys():
msg = _("Unsupported encapsulation mode: %s. Only 'tunnel' mode "
"is supported.") % mode
raise nsx_exc.NsxVpnValidationError(details=msg)
# Ensure IPSec policy transform protocol is esp
prot = ipsec_policy.get('transform_protocol')
if prot and prot not in ipsec_utils.TRANSFORM_PROTOCOL_MAP.keys():
msg = _("Unsupported transform protocol: %s. Only 'esp' protocol "
"is supported.") % prot
raise nsx_exc.NsxVpnValidationError(details=msg)
def validate_ike_policy(self, context, ike_policy):
# Call general validations
super(IPsecV3Validator, self).validate_ike_policy(
context, ike_policy)
# Call specific NSX validations
self._validate_policy_lifetime(ike_policy, "IKE")
self._validate_policy_auth_algorithm(ike_policy, "IKE")
self._validate_policy_encryption_algorithm(ike_policy, "IKE")
self._validate_policy_pfs(ike_policy, "IKE")
# 'aggressive' phase1-negotiation-mode is not supported
if ike_policy.get('phase1-negotiation-mode', 'main') != 'main':
msg = _("Unsupported phase1-negotiation-mode: %s! Only 'main' is "
"supported.") % ike_policy['phase1-negotiation-mode']
raise nsx_exc.NsxVpnValidationError(details=msg)

View File

@ -0,0 +1,757 @@
# Copyright 2019 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 collections import namedtuple
import contextlib
import mock
from oslo_utils import uuidutils
from neutron.db import l3_db
from neutron.db.models import l3 as l3_models
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib import context as n_ctx
from neutron_lib.plugins import directory
from neutron_vpnaas.db.vpn import vpn_models # noqa
from neutron_vpnaas.tests import base
from vmware_nsx.common import exceptions as nsx_exc
from vmware_nsx.services.vpnaas.nsxp import ipsec_driver
from vmware_nsx.services.vpnaas.nsxp import ipsec_validator
from vmware_nsx.tests.unit.nsx_p import test_plugin
_uuid = uuidutils.generate_uuid
FAKE_TENANT = _uuid()
FAKE_ROUTER_ID = "aaaaaa-bbbbb-ccc"
FAKE_ROUTER = {'id': FAKE_ROUTER_ID,
'name': 'fake router',
'project_id': FAKE_TENANT,
'admin_state_up': True,
'status': 'ACTIVE',
'gw_port_id': _uuid(),
'enable_snat': False,
l3_db.EXTERNAL_GW_INFO: {'network_id': _uuid()}}
FAKE_SUBNET_ID = _uuid()
FAKE_SUBNET = {'cidr': '1.1.1.0/24', 'id': FAKE_SUBNET_ID}
FAKE_VPNSERVICE_ID = _uuid()
FAKE_VPNSERVICE = {'id': FAKE_VPNSERVICE_ID,
'name': 'vpn_service',
'description': 'dummy',
'router': FAKE_ROUTER,
'router_id': FAKE_ROUTER_ID,
'subnet': FAKE_SUBNET,
'subnet_id': FAKE_SUBNET_ID,
'project_id': FAKE_TENANT,
'external_v4_ip': '1.1.1.1',
'admin_state_up': True}
FAKE_IKE_POLICY_ID = _uuid()
FAKE_IKE_POLICY = {'id': FAKE_IKE_POLICY_ID,
'name': 'ike_dummy',
'description': 'ike_dummy',
'auth_algorithm': 'sha1',
'encryption_algorithm': 'aes-128',
'phase1_negotiation_mode': 'main',
'lifetime': {
'units': 'seconds',
'value': 3600},
'ike_version': 'v1',
'pfs': 'group14',
'project_id': FAKE_TENANT}
FAKE_IPSEC_POLICY_ID = _uuid()
FAKE_IPSEC_POLICY = {'id': FAKE_IPSEC_POLICY_ID,
'name': 'ipsec_dummy',
'description': 'myipsecpolicy1',
'auth_algorithm': 'sha1',
'encryption_algorithm': 'aes-128',
'encapsulation_mode': 'tunnel',
'lifetime': {
'units': 'seconds',
'value': 3600},
'transform_protocol': 'esp',
'pfs': 'group14',
'project_id': FAKE_TENANT}
FAKE_IPSEC_CONNECTION_ID = _uuid()
FAKE_IPSEC_CONNECTION = {'vpnservice_id': FAKE_VPNSERVICE_ID,
'ikepolicy_id': FAKE_IKE_POLICY_ID,
'ipsecpolicy_id': FAKE_IPSEC_POLICY_ID,
'name': 'VPN connection',
'description': 'VPN connection',
'id': FAKE_IPSEC_CONNECTION_ID,
'peer_address': '192.168.1.10',
'peer_id': '192.168.1.10',
'peer_cidrs': '192.168.1.0/24',
'mtu': 1500,
'psk': 'abcd',
'initiator': 'bi-directional',
'dpd': {
'action': 'hold',
'interval': 30,
'timeout': 120},
'admin_state_up': True,
'project_id': FAKE_TENANT}
FAKE_NEW_CONNECTION = {'vpnservice_id': FAKE_VPNSERVICE_ID,
'ikepolicy_id': FAKE_IKE_POLICY_ID,
'ipsecpolicy_id': FAKE_IPSEC_POLICY_ID,
'name': 'VPN connection',
'description': 'VPN connection',
'id': FAKE_IPSEC_CONNECTION_ID,
'peer_address': '192.168.1.10',
'peer_id': '192.168.1.10',
'peer_cidrs': '192.168.2.0/24',
'mtu': 1500,
'psk': 'abcd',
'initiator': 'bi-directional',
'dpd': {
'action': 'hold',
'interval': 30,
'timeout': 120},
'admin_state_up': True,
'project_id': FAKE_TENANT}
FAKE_VPNSERVICE_NO_SUB = {'id': FAKE_VPNSERVICE_ID,
'name': 'vpn_service',
'description': 'dummy',
'router': FAKE_ROUTER,
'router_id': FAKE_ROUTER_ID,
'project_id': FAKE_TENANT,
'external_v4_ip': '1.1.1.1',
'admin_state_up': True}
FAKE_ENDPOINTS_CONNECTION = {'vpnservice_id': FAKE_VPNSERVICE_ID,
'ikepolicy_id': FAKE_IKE_POLICY_ID,
'ipsecpolicy_id': FAKE_IPSEC_POLICY_ID,
'name': 'VPN connection',
'description': 'VPN connection',
'id': FAKE_IPSEC_CONNECTION_ID,
'peer_address': '192.168.1.10',
'peer_id': '192.168.1.10',
'peer_ep_group_id': 'cidr_ep',
'local_ep_group_id': 'subnet_ep',
'mtu': 1500,
'psk': 'abcd',
'initiator': 'bi-directional',
'dpd': {
'action': 'hold',
'interval': 30,
'timeout': 120},
'admin_state_up': True,
'project_id': FAKE_TENANT}
class TestDriverValidation(base.BaseTestCase):
def setUp(self):
super(TestDriverValidation, self).setUp()
self.context = n_ctx.Context('some_user', 'some_tenant')
self.service_plugin = mock.Mock()
driver = mock.Mock()
driver.service_plugin = self.service_plugin
with mock.patch("neutron_lib.plugins.directory.get_plugin"):
self.validator = ipsec_validator.IPsecNsxPValidator(driver)
self.validator._l3_plugin = mock.Mock()
self.validator._core_plugin = mock.Mock()
self.vpn_service = {'router_id': 'dummy_router',
'subnet_id': 'dummy_subnet'}
self.peer_address = '10.10.10.10'
self.peer_cidr = '10.10.11.0/20'
def _test_lifetime_not_in_seconds(self, validation_func):
policy_info = {'lifetime': {'units': 'kilobytes', 'value': 1000}}
self.assertRaises(nsx_exc.NsxVpnValidationError,
validation_func,
self.context, policy_info)
def test_ike_lifetime_not_in_seconds(self):
self._test_lifetime_not_in_seconds(
self.validator.validate_ike_policy)
def test_ipsec_lifetime_not_in_seconds(self):
self._test_lifetime_not_in_seconds(
self.validator.validate_ipsec_policy)
def _test_lifetime_seconds_values_at_limits(self, validation_func):
policy_info = {'lifetime': {'units': 'seconds', 'value': 21600}}
validation_func(self.context, policy_info)
policy_info = {'lifetime': {'units': 'seconds', 'value': 86400}}
validation_func(self.context, policy_info)
policy_info = {'lifetime': {'units': 'seconds', 'value': 10}}
self.assertRaises(nsx_exc.NsxVpnValidationError,
validation_func,
self.context, policy_info)
def test_ike_lifetime_seconds_values_at_limits(self):
self._test_lifetime_seconds_values_at_limits(
self.validator.validate_ike_policy)
def test_ipsec_lifetime_seconds_values_at_limits(self):
self._test_lifetime_seconds_values_at_limits(
self.validator.validate_ipsec_policy)
def _test_auth_algorithm(self, validation_func):
auth_algorithm = {'auth_algorithm': 'sha384'}
validation_func(self.context, auth_algorithm)
auth_algorithm = {'auth_algorithm': 'sha512'}
validation_func(self.context, auth_algorithm)
auth_algorithm = {'auth_algorithm': 'sha1'}
validation_func(self.context, auth_algorithm)
auth_algorithm = {'auth_algorithm': 'sha256'}
validation_func(self.context, auth_algorithm)
def test_ipsec_auth_algorithm(self):
self._test_auth_algorithm(self.validator.validate_ipsec_policy)
def test_ike_auth_algorithm(self):
self._test_auth_algorithm(self.validator.validate_ike_policy)
def _test_encryption_algorithm(self, validation_func):
auth_algorithm = {'encryption_algorithm': 'aes-192'}
self.assertRaises(nsx_exc.NsxVpnValidationError,
validation_func,
self.context, auth_algorithm)
auth_algorithm = {'encryption_algorithm': 'aes-128'}
validation_func(self.context, auth_algorithm)
auth_algorithm = {'encryption_algorithm': 'aes-256'}
validation_func(self.context, auth_algorithm)
def test_ipsec_encryption_algorithm(self):
self._test_encryption_algorithm(self.validator.validate_ipsec_policy)
def test_ike_encryption_algorithm(self):
self._test_encryption_algorithm(self.validator.validate_ike_policy)
def test_ike_negotiation_mode(self):
policy_info = {'phase1-negotiation-mode': 'aggressive'}
self.assertRaises(nsx_exc.NsxVpnValidationError,
self.validator.validate_ike_policy,
self.context, policy_info)
policy_info = {'phase1-negotiation-mode': 'main'}
self.validator.validate_ike_policy(self.context, policy_info)
def _test_pfs(self, validation_func):
policy_info = {'pfs': 'group15'}
self.assertRaises(nsx_exc.NsxVpnValidationError,
validation_func,
self.context, policy_info)
policy_info = {'pfs': 'group14'}
validation_func(self.context, policy_info)
def test_ipsec_pfs(self):
self._test_pfs(self.validator.validate_ipsec_policy)
def test_ike_pfs(self):
self._test_pfs(self.validator.validate_ike_policy)
def test_ipsec_encap_mode(self):
policy_info = {'encapsulation_mode': 'transport'}
self.assertRaises(nsx_exc.NsxVpnValidationError,
self.validator.validate_ipsec_policy,
self.context, policy_info)
policy_info = {'encapsulation_mode': 'tunnel'}
self.validator.validate_ipsec_policy(self.context, policy_info)
def test_ipsec_transform_protocol(self):
policy_info = {'transform_protocol': 'ah'}
self.assertRaises(nsx_exc.NsxVpnValidationError,
self.validator.validate_ipsec_policy,
self.context, policy_info)
policy_info = {'transform_protocol': 'esp'}
self.validator.validate_ipsec_policy(self.context, policy_info)
def test_vpn_service_validation(self):
db_router = l3_models.Router()
nsx_router = {'ha_mode': 'ACITVE_ACTIVE'}
db_router.enable_snat = False
with mock.patch.object(self.validator.nsxpolicy.tier0, 'get',
return_value=nsx_router):
self.assertRaises(nsx_exc.NsxVpnValidationError,
self.validator.validate_vpnservice,
self.context, self.vpn_service)
nsx_router = {'ha_mode': 'ACTIVE_STANDBY'}
db_router.enable_snat = True
with mock.patch.object(self.validator.nsxpolicy.tier0, 'get',
return_value=nsx_router),\
mock.patch.object(self.validator._core_plugin, '_get_router',
return_value=db_router):
self.validator.validate_vpnservice(self.context, self.vpn_service)
nsx_router = {'ha_mode': 'ACTIVE_STANDBY'}
db_router.enable_snat = False
with mock.patch.object(self.validator.nsxpolicy.tier0, 'get',
return_value=nsx_router),\
mock.patch.object(self.validator._core_plugin, '_get_router',
return_value=db_router):
self.validator.validate_vpnservice(self.context, self.vpn_service)
nsx_router = {'ha_mode': 'ACTIVE_STANDBY'}
db_router.enable_snat = False
vpn_service_no_subnet = {'router_id': 'dummy_router',
'subnet_id': None}
with mock.patch.object(self.validator.nsxpolicy.tier0, 'get',
return_value=nsx_router),\
mock.patch.object(self.validator._core_plugin, '_get_router',
return_value=db_router):
self.validator.validate_vpnservice(
self.context, vpn_service_no_subnet)
def _test_conn_validation(self, conn_params=None, success=True,
connections=None, service_subnets=None,
router_subnets=None):
if connections is None:
connections = []
if router_subnets is None:
router_subnets = []
def mock_get_routers(context, filters=None, fields=None):
return [{'id': 'no-snat',
'external_gateway_info': {'enable_snat': False}}]
def mock_get_service(context, service_id):
if service_subnets:
# option to give the test a different subnet per service
subnet_cidr = service_subnets[int(service_id) - 1]
else:
subnet_cidr = '5.5.5.0/2%s' % service_id
return {'id': service_id,
'router_id': service_id,
'subnet_id': 'dummy_subnet',
'external_v4_ip': '1.1.1.%s' % service_id,
'subnet': {'id': 'dummy_subnet',
'cidr': subnet_cidr}}
def mock_get_connections(context, filters=None, fields=None):
if filters and 'peer_address' in filters:
return [conn for conn in connections
if conn['peer_address'] == filters['peer_address'][0]]
else:
return connections
with mock.patch.object(self.validator.vpn_plugin, '_get_vpnservice',
side_effect=mock_get_service),\
mock.patch.object(self.validator._core_plugin, 'get_routers',
side_effect=mock_get_routers),\
mock.patch.object(self.validator._core_plugin,
'_find_router_subnets_cidrs',
return_value=router_subnets),\
mock.patch.object(self.validator.vpn_plugin,
'get_ipsec_site_connections',
side_effect=mock_get_connections):
ipsec_sitecon = {'id': '1',
'vpnservice_id': '1',
'mtu': 1500,
'peer_address': self.peer_address,
'peer_cidrs': [self.peer_cidr]}
if conn_params:
ipsec_sitecon.update(conn_params)
if success:
self.validator.validate_ipsec_site_connection(
self.context, ipsec_sitecon)
else:
self.assertRaises(
nsx_exc.NsxVpnValidationError,
self.validator.validate_ipsec_site_connection,
self.context, ipsec_sitecon)
def test_dpd_validation(self):
params = {'dpd': {'action': 'hold',
'timeout': 120}}
self._test_conn_validation(conn_params=params, success=True)
params = {'dpd': {'action': 'clear',
'timeout': 120}}
self._test_conn_validation(conn_params=params, success=False)
params = {'dpd': {'action': 'hold',
'timeout': 2}}
self._test_conn_validation(conn_params=params, success=False)
def test_check_unique_addresses(self):
# this test runs with non-overlapping local subnets on
# different routers
subnets = ['5.5.5.0/20', '6.6.6.0/20']
# same service/router gw & peer address - should fail
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '1',
'peer_address': self.peer_address,
'peer_cidrs': [self.peer_cidr]}]
self._test_conn_validation(success=False,
connections=connections,
service_subnets=subnets)
# different service/router gw - ok
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '2',
'peer_address': self.peer_address,
'peer_cidrs': ['6.6.6.6']}]
self._test_conn_validation(success=True,
connections=connections,
service_subnets=subnets)
# different peer address - ok
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '1',
'peer_address': '7.7.7.1',
'peer_cidrs': ['7.7.7.7']}]
self._test_conn_validation(success=True,
connections=connections,
service_subnets=subnets)
# ignoring non-active connections
connections = [{'id': '2',
'status': 'ERROR',
'vpnservice_id': '1',
'peer_address': self.peer_address,
'peer_cidrs': [self.peer_cidr]}]
self._test_conn_validation(success=True,
connections=connections,
service_subnets=subnets)
def test_overlapping_rules(self):
# peer-cidr overlapping with new one, same subnet - should fail
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '1',
'peer_address': '9.9.9.9',
'peer_cidrs': ['10.10.11.1/19']}]
self._test_conn_validation(success=False,
connections=connections)
# same peer-cidr, overlapping subnets - should fail
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '2',
'peer_address': '9.9.9.9',
'peer_cidrs': [self.peer_cidr]}]
self._test_conn_validation(success=False,
connections=connections)
# non overlapping peer-cidr, same subnet - ok
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '1',
'peer_address': '7.7.7.1',
'peer_cidrs': ['7.7.7.7']}]
self._test_conn_validation(success=True,
connections=connections)
# ignoring non-active connections
connections = [{'id': '2',
'status': 'ERROR',
'vpnservice_id': '1',
'peer_address': '9.9.9.9',
'peer_cidrs': ['10.10.11.1/19']}]
self._test_conn_validation(success=True,
connections=connections)
def test_advertisment(self):
# different routers, same subnet - should fail
subnets = ['5.5.5.0/20', '5.5.5.0/20']
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '2',
'peer_address': self.peer_address,
'peer_cidrs': ['6.6.6.6']}]
self._test_conn_validation(success=False,
connections=connections,
service_subnets=subnets)
# different routers, overlapping subnet - should fail
subnets = ['5.5.5.0/20', '5.5.5.0/21']
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '2',
'peer_address': self.peer_address,
'peer_cidrs': ['6.6.6.6']}]
self._test_conn_validation(success=False,
connections=connections,
service_subnets=subnets)
# different routers, non overlapping subnet - ok
subnets = ['5.5.5.0/20', '50.5.5.0/21']
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '2',
'peer_address': self.peer_address,
'peer_cidrs': ['6.6.6.6']}]
self._test_conn_validation(success=True,
connections=connections,
service_subnets=subnets)
# no-snat router with overlapping subnet to the service subnet - fail
subnets = ['5.5.5.0/21', '1.1.1.0/20']
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '2',
'peer_address': self.peer_address,
'peer_cidrs': ['6.6.6.6']}]
self._test_conn_validation(success=False,
connections=connections,
router_subnets=subnets)
# no-snat router with non overlapping subnet to the service subnet - ok
service_subnets = ['5.5.5.0/20', '6.6.6.0/20']
router_subnets = ['50.5.5.0/21', '1.1.1.0/20']
connections = [{'id': '2',
'status': 'ACTIVE',
'vpnservice_id': '2',
'peer_address': self.peer_address,
'peer_cidrs': ['6.6.6.6']}]
self._test_conn_validation(success=True,
connections=connections,
service_subnets=service_subnets,
router_subnets=router_subnets)
class TestVpnaasDriver(test_plugin.NsxPPluginTestCaseMixin):
def setUp(self):
super(TestVpnaasDriver, self).setUp()
self.context = n_ctx.get_admin_context()
self.service_plugin = mock.Mock()
self.validator = mock.Mock()
self.driver = ipsec_driver.NSXpIPsecVpnDriver(self.service_plugin)
self.plugin = directory.get_plugin()
self.policy_vpn = self.plugin.nsxpolicy.ipsec_vpn
self.l3plugin = self.plugin
@contextlib.contextmanager
def router(self, name='vpn-test-router', tenant_id=_uuid(),
admin_state_up=True, **kwargs):
request = {'router': {'tenant_id': tenant_id,
'name': name,
'admin_state_up': admin_state_up}}
for arg in kwargs:
request['router'][arg] = kwargs[arg]
router = self.l3plugin.create_router(self.context, request)
yield router
def test_create_ipsec_site_connection(self):
with mock.patch.object(self.service_plugin, 'get_ikepolicy',
return_value=FAKE_IKE_POLICY),\
mock.patch.object(self.service_plugin, 'get_ipsecpolicy',
return_value=FAKE_IPSEC_POLICY),\
mock.patch.object(self.service_plugin, '_get_vpnservice',
return_value=FAKE_VPNSERVICE),\
mock.patch.object(self.service_plugin, 'get_vpnservices',
return_value=[FAKE_VPNSERVICE]),\
mock.patch.object(self.plugin, 'get_router',
return_value=FAKE_ROUTER),\
mock.patch.object(self.plugin, 'get_subnet',
return_value=FAKE_SUBNET),\
mock.patch("vmware_nsx.db.db.add_nsx_vpn_connection_mapping"),\
mock.patch.object(self.policy_vpn.ike_profile,
'create_or_overwrite') as create_ike,\
mock.patch.object(self.policy_vpn.tunnel_profile,
'create_or_overwrite') as create_ipsec,\
mock.patch.object(self.policy_vpn.dpd_profile,
'create_or_overwrite') as create_dpd,\
mock.patch.object(self.policy_vpn.session,
'create_or_overwrite') as create_sesson:
self.driver.create_ipsec_site_connection(self.context,
FAKE_IPSEC_CONNECTION)
create_ike.assert_called_once()
create_ipsec.assert_called_once()
create_dpd.assert_called_once()
create_sesson.assert_called_once()
# TODO(asarfaty): make sure router adv also updated
def test_update_ipsec_site_connection(self):
with mock.patch.object(self.service_plugin, '_get_vpnservice',
return_value=FAKE_VPNSERVICE),\
mock.patch.object(self.plugin, 'get_router',
return_value=FAKE_ROUTER),\
mock.patch.object(self.plugin,
'update_router_firewall') as update_fw,\
mock.patch.object(self.policy_vpn.session,
'update') as update_sesson,\
mock.patch("vmware_nsx.db.db.get_nsx_vpn_connection_mapping"):
self.driver.update_ipsec_site_connection(self.context,
FAKE_IPSEC_CONNECTION,
FAKE_NEW_CONNECTION)
update_sesson.assert_called_once()
update_fw.assert_called_once()
def test_delete_ipsec_site_connection(self):
with mock.patch.object(self.service_plugin, 'get_ikepolicy',
return_value=FAKE_IKE_POLICY),\
mock.patch.object(self.service_plugin, 'get_ipsecpolicy',
return_value=FAKE_IPSEC_POLICY),\
mock.patch.object(self.service_plugin, '_get_vpnservice',
return_value=FAKE_VPNSERVICE),\
mock.patch.object(self.service_plugin, 'get_vpnservices',
return_value=[FAKE_VPNSERVICE]),\
mock.patch.object(self.plugin, 'get_router',
return_value=FAKE_ROUTER),\
mock.patch.object(self.plugin, 'get_subnet',
return_value=FAKE_SUBNET),\
mock.patch("vmware_nsx.db.db.get_nsx_vpn_connection_mapping"),\
mock.patch.object(self.policy_vpn.ike_profile,
'delete') as delete_ike,\
mock.patch.object(self.policy_vpn.tunnel_profile,
'delete') as delete_ipsec,\
mock.patch.object(self.policy_vpn.dpd_profile,
'delete') as delete_dpd,\
mock.patch.object(self.policy_vpn.session,
'delete') as delete_sesson:
self.driver.delete_ipsec_site_connection(self.context,
FAKE_IPSEC_CONNECTION)
delete_ike.assert_called_once()
delete_ipsec.assert_called_once()
delete_dpd.assert_called_once()
delete_sesson.assert_called_once()
# TODO(asarfaty): make sure router adv rules also updated
def test_create_vpn_service_legal(self):
"""Create a legal vpn service"""
# create an external network with a subnet, and a router
providernet_args = {extnet_apidef.EXTERNAL: True}
router_db = namedtuple("Router", FAKE_ROUTER.keys())(
*FAKE_ROUTER.values())
tier0_uuid = 'tier-0'
with self.network(name='ext-net',
providernet_args=providernet_args,
arg_list=(extnet_apidef.EXTERNAL, )) as ext_net,\
self.subnet(ext_net, enable_dhcp=False),\
mock.patch.object(self.plugin, '_get_tier0_uuid_by_router',
return_value=tier0_uuid),\
self.router(external_gateway_info={'network_id':
ext_net['network']['id']}) as router,\
self.subnet(cidr='1.1.0.0/24') as sub:
# add an interface to the router
self.l3plugin.add_router_interface(
self.context,
router['id'],
{'subnet_id': sub['subnet']['id']})
# create the service
dummy_port = {'id': 'dummy_port',
'fixed_ips': [{'ip_address': '1.1.1.1'}]}
tier0_rtr = {'ha_mode': 'ACTIVE_STANDBY'}
with mock.patch.object(self.service_plugin, '_get_vpnservice',
return_value=FAKE_VPNSERVICE),\
mock.patch.object(self.policy_vpn.service,
'create_or_overwrite') as create_service,\
mock.patch.object(self.l3plugin, '_get_router',
return_value=router_db),\
mock.patch.object(self.plugin, 'get_router',
return_value=FAKE_ROUTER),\
mock.patch.object(self.plugin, 'get_ports',
return_value=[dummy_port]),\
mock.patch.object(self.plugin, 'delete_port') as delete_port,\
mock.patch.object(self.plugin, 'service_router_has_services',
return_value=True),\
mock.patch.object(self.plugin.nsxpolicy.tier0, 'get',
return_value=tier0_rtr):
self.driver.create_vpnservice(self.context, FAKE_VPNSERVICE)
create_service.assert_called_once()
# Delete the service
nsx_services = [{'logical_router_id': tier0_uuid,
'id': 'xxx'}]
with mock.patch.object(
self.policy_vpn.service, 'list',
return_value={'results': nsx_services}),\
mock.patch.object(self.service_plugin, 'get_vpnservices',
return_value=[]),\
mock.patch.object(self.policy_vpn.service,
'delete') as delete_service:
self.driver.delete_vpnservice(
self.context, FAKE_VPNSERVICE)
delete_service.assert_called_once()
delete_port.assert_called_once()
def test_create_another_vpn_service(self):
# make sure another backend service is not created
providernet_args = {extnet_apidef.EXTERNAL: True}
router_db = namedtuple("Router", FAKE_ROUTER.keys())(
*FAKE_ROUTER.values())
tier0_rtr_id = _uuid()
with self.network(name='ext-net',
providernet_args=providernet_args,
arg_list=(extnet_apidef.EXTERNAL, )) as ext_net,\
self.subnet(ext_net, enable_dhcp=False),\
mock.patch.object(self.plugin, '_get_tier0_uuid_by_router',
return_value=tier0_rtr_id),\
self.router(external_gateway_info={'network_id':
ext_net['network']['id']}) as router,\
self.subnet(cidr='1.1.0.0/24') as sub:
# add an interface to the router
self.l3plugin.add_router_interface(
self.context,
router['id'],
{'subnet_id': sub['subnet']['id']})
# create the service
dummy_port = {'id': 'dummy_port',
'fixed_ips': [{'ip_address': '1.1.1.1'}]}
tier0_rtr = {'id': tier0_rtr_id,
'ha_mode': 'ACTIVE_STANDBY'}
nsx_srv = {'logical_router_id': tier0_rtr_id,
'id': _uuid(),
'enabled': True}
with mock.patch.object(self.service_plugin, '_get_vpnservice',
return_value=FAKE_VPNSERVICE),\
mock.patch.object(self.policy_vpn.service,
'create_or_overwrite') as create_service,\
mock.patch.object(
self.policy_vpn.service, 'list',
return_value={'results': [nsx_srv]}),\
mock.patch.object(self.l3plugin, '_get_router',
return_value=router_db),\
mock.patch.object(self.plugin, 'get_router',
return_value=FAKE_ROUTER),\
mock.patch.object(self.plugin, 'get_ports',
return_value=[dummy_port]),\
mock.patch.object(self.plugin, 'delete_port'),\
mock.patch.object(self.plugin, 'service_router_has_services',
return_value=True),\
mock.patch.object(self.plugin.nsxpolicy.tier0, 'get',
return_value=tier0_rtr):
self.driver.create_vpnservice(self.context, FAKE_VPNSERVICE)
create_service.assert_called_once()
# now delete both
nsx_services = [{'logical_router_id': tier0_rtr_id,
'id': 'xxx'}]
with mock.patch.object(
self.policy_vpn.service, 'list',
return_value={'results': nsx_services}),\
mock.patch.object(self.policy_vpn.service,
'delete') as delete_service:
self.driver.delete_vpnservice(
self.context, FAKE_VPNSERVICE)
delete_service.assert_not_called()
with mock.patch.object(
self.policy_vpn.service, 'list',
return_value={'results': nsx_services}),\
mock.patch.object(self.service_plugin, 'get_vpnservices',
return_value=[]),\
mock.patch.object(self.policy_vpn.service,
'delete') as delete_service:
self.driver.delete_vpnservice(
self.context, FAKE_VPNSERVICE)
delete_service.assert_called_once()