NSXv: Support ipsec VPNaaS on nsxv driver
Change-Id: Id3fd4da7e4dd4cac4eb2e32024c0d8242b85a0bb Co-Authored-By: Roey Chen <roeyc@vmware.com>
This commit is contained in:
parent
d0af9b5a9d
commit
53d1bd0e2f
@ -121,6 +121,15 @@ Add neutron-dynamic-routing repo as an external repository and configure followi
|
||||
[DEFAULT]
|
||||
api_extensions_path = $DEST/neutron-dynamic-routing/neutron_dynamic_routing/extensions
|
||||
|
||||
Neutron VPNaaS
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Add neutron-vpnaas repo as an external repository and configure following flags in ``local.conf``::
|
||||
|
||||
[[local|localrc]]
|
||||
enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas
|
||||
NEUTRON_VPNAAS_SERVICE_PROVIDER=VPN:vmware:vmware_nsx.services.vpnaas.nsxv.ipsec_driver.NSXvIPsecVpnDriver:default
|
||||
|
||||
|
||||
NSXv3
|
||||
-----
|
||||
|
@ -10,6 +10,7 @@ ${DIR}/tox_install_project.sh neutron-lbaas neutron_lbaas $*
|
||||
${DIR}/tox_install_project.sh vmware-nsxlib vmware_nsxlib $*
|
||||
${DIR}/tox_install_project.sh neutron-fwaas neutron_fwaas $*
|
||||
${DIR}/tox_install_project.sh neutron-dynamic-routing neutron-dynamic-routing $*
|
||||
${DIR}/tox_install_project.sh neutron-vpnaas neutron-vpnaas $*
|
||||
|
||||
CONSTRAINTS_FILE=$1
|
||||
shift
|
||||
|
@ -198,3 +198,11 @@ class NsxRouterInterfaceDoesNotMatchAddressScope(n_exc.BadRequest):
|
||||
message = _("Unable to update no-NAT router %(router_id)s, "
|
||||
"only subnets allocated from address-scope "
|
||||
"%(address_scope_id)s can be connected.")
|
||||
|
||||
|
||||
class NsxVpnValidationError(NsxPluginException):
|
||||
message = _("Invalid VPN configuration: %(details)s")
|
||||
|
||||
|
||||
class NsxIPsecVpnMappingNotFound(n_exc.NotFound):
|
||||
message = _("Unable to find mapping for ipsec site connection: %(conn)s")
|
||||
|
@ -60,3 +60,19 @@ CSR_REQUEST = ("<csr><subject>"
|
||||
RESERVED_IPS = ["169.254.128.0/17",
|
||||
"169.254.1.0/24",
|
||||
"169.254.64.192/26"]
|
||||
|
||||
# VPNaaS constants
|
||||
ENCRYPTION_ALGORITHM_MAP = {
|
||||
'3des': '3des',
|
||||
'aes-128': 'aes',
|
||||
'aes-256': 'aes256'
|
||||
}
|
||||
|
||||
PFS_MAP = {
|
||||
'group2': 'dh2',
|
||||
'group5': 'dh5'
|
||||
}
|
||||
|
||||
TRANSFORM_PROTOCOL_ALLOWED = ('esp',)
|
||||
|
||||
ENCAPSULATION_MODE_ALLOWED = ('tunnel',)
|
||||
|
@ -3549,6 +3549,7 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
fw_rules = []
|
||||
router_with_firewall = True if fwaas_rules is not None else False
|
||||
neutron_id = router_db['id']
|
||||
edge_id = self._get_edge_id_by_rtr_id(context, router_id)
|
||||
|
||||
# Add FW rule to open subnets firewall flows and static routes
|
||||
# relative flows
|
||||
@ -3595,9 +3596,15 @@ class NsxVPluginV2(addr_pair_db.AllowedAddressPairsMixin,
|
||||
context, router_db)
|
||||
fw_rules.extend(nosnat_fw_rules)
|
||||
|
||||
vpn_plugin = directory.get_plugin(plugin_const.VPN)
|
||||
if vpn_plugin:
|
||||
vpn_driver = vpn_plugin.ipsec_driver
|
||||
vpn_rules = (
|
||||
vpn_driver._generate_ipsecvpn_firewall_rules(edge_id))
|
||||
fw_rules.extend(vpn_rules)
|
||||
|
||||
# Get the load balancer rules in case they are refreshed
|
||||
# (relevant only for older LB that are still on the router edge)
|
||||
edge_id = self._get_edge_id_by_rtr_id(context, router_id)
|
||||
lb_rules = nsxv_db.get_nsxv_lbaas_loadbalancer_binding_by_edge(
|
||||
context.session, edge_id)
|
||||
for rule in lb_rules:
|
||||
|
0
vmware_nsx/services/vpnaas/__init__.py
Normal file
0
vmware_nsx/services/vpnaas/__init__.py
Normal file
0
vmware_nsx/services/vpnaas/nsxv/__init__.py
Normal file
0
vmware_nsx/services/vpnaas/nsxv/__init__.py
Normal file
355
vmware_nsx/services/vpnaas/nsxv/ipsec_driver.py
Normal file
355
vmware_nsx/services/vpnaas/nsxv/ipsec_driver.py
Normal file
@ -0,0 +1,355 @@
|
||||
# Copyright 2016 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 neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_vpnaas.services.vpn import service_drivers
|
||||
from oslo_log import log as logging
|
||||
|
||||
from vmware_nsx._i18n import _
|
||||
from vmware_nsx.common import exceptions as nsxv_exc
|
||||
from vmware_nsx.common import locking
|
||||
from vmware_nsx.common import nsxv_constants
|
||||
from vmware_nsx.db import nsxv_db
|
||||
from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as vcns_exc
|
||||
from vmware_nsx.services.vpnaas.nsxv import ipsec_validator
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
IPSEC = 'ipsec'
|
||||
|
||||
|
||||
class NSXvIPsecVpnDriver(service_drivers.VpnDriver):
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
self._core_plugin = directory.get_plugin()
|
||||
self._vcns = self._core_plugin.nsx_v.vcns
|
||||
validator = ipsec_validator.IPsecValidator(service_plugin)
|
||||
super(NSXvIPsecVpnDriver, self).__init__(service_plugin, validator)
|
||||
|
||||
@property
|
||||
def l3_plugin(self):
|
||||
return self._core_plugin
|
||||
|
||||
@property
|
||||
def service_type(self):
|
||||
return IPSEC
|
||||
|
||||
def _is_shared_router(self, router):
|
||||
return router.get('router_type') == nsxv_constants.SHARED
|
||||
|
||||
def _get_router_edge_id(self, context, vpnservice_id):
|
||||
vpnservice = self.service_plugin._get_vpnservice(context,
|
||||
vpnservice_id)
|
||||
router_id = vpnservice['router_id']
|
||||
edge_binding = nsxv_db.get_nsxv_router_binding(context.session,
|
||||
router_id)
|
||||
if not edge_binding:
|
||||
msg = _("Couldn't find edge binding for router %s") % router_id
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
if edge_binding['edge_type'] == nsxv_constants.VDR_EDGE:
|
||||
edge_manager = self._core_plugin.edge_manager
|
||||
router_id = edge_manager.get_plr_by_tlr_id(context, router_id)
|
||||
binding = nsxv_db.get_nsxv_router_binding(context.session,
|
||||
router_id)
|
||||
edge_id = binding['edge_id']
|
||||
else:
|
||||
# Get exclusive edge id
|
||||
edge_id = edge_binding['edge_id']
|
||||
return router_id, edge_id
|
||||
|
||||
def _convert_ipsec_conn(self, context, ipsec_site_connection):
|
||||
ipsec_id = ipsec_site_connection['ipsecpolicy_id']
|
||||
vpnservice_id = ipsec_site_connection['vpnservice_id']
|
||||
ipsecpolicy = self.service_plugin.get_ipsecpolicy(context, ipsec_id)
|
||||
vpnservice = self.service_plugin._get_vpnservice(context,
|
||||
vpnservice_id)
|
||||
local_cidr = vpnservice['subnet']['cidr']
|
||||
router_id = vpnservice['router_id']
|
||||
router = self._core_plugin.get_router(context, router_id)
|
||||
local_addr = (router['external_gateway_info']['external_fixed_ips']
|
||||
[0]["ip_address"])
|
||||
encrypt = nsxv_constants.ENCRYPTION_ALGORITHM_MAP.get(
|
||||
ipsecpolicy.get('encryption_algorithm'))
|
||||
site = {
|
||||
'enabled': True,
|
||||
'enablePfs': True,
|
||||
'dhGroup': nsxv_constants.PFS_MAP.get(ipsecpolicy.get('pfs')),
|
||||
'name': ipsec_site_connection.get('name'),
|
||||
'description': ipsec_site_connection.get('description'),
|
||||
'localId': local_addr,
|
||||
'localIp': local_addr,
|
||||
'peerId': ipsec_site_connection['peer_id'],
|
||||
'peerIp': ipsec_site_connection.get('peer_address'),
|
||||
'localSubnets': {
|
||||
'subnets': [local_cidr]},
|
||||
'peerSubnets': {
|
||||
'subnets': ipsec_site_connection.get('peer_cidrs')},
|
||||
'authenticationMode': ipsec_site_connection.get('auth_mode'),
|
||||
'psk': ipsec_site_connection.get('psk'),
|
||||
'encryptionAlgorithm': encrypt
|
||||
}
|
||||
return site
|
||||
|
||||
def _generate_new_sites(self, edge_id, ipsec_site_conn):
|
||||
# Fetch the previous ipsec vpn configuration
|
||||
ipsecvpn_configs = self._get_ipsec_config(edge_id)
|
||||
vse_sites = []
|
||||
if ipsecvpn_configs[1]['enabled']:
|
||||
vse_sites = ([site for site
|
||||
in ipsecvpn_configs[1]['sites']['sites']])
|
||||
vse_sites.append(ipsec_site_conn)
|
||||
return vse_sites
|
||||
|
||||
def _generate_ipsecvpn_firewall_rules(self, edge_id):
|
||||
ipsecvpn_configs = self._get_ipsec_config(edge_id)
|
||||
ipsec_vpn_fw_rules = []
|
||||
if ipsecvpn_configs[1]['enabled']:
|
||||
for site in ipsecvpn_configs[1]['sites']['sites']:
|
||||
peer_subnets = site['peerSubnets']['subnets']
|
||||
local_subnets = site['localSubnets']['subnets']
|
||||
ipsec_vpn_fw_rules.append({
|
||||
'name': 'VPN ' + site['name'],
|
||||
'action': 'allow',
|
||||
'enabled': True,
|
||||
'source_ip_address': peer_subnets,
|
||||
'destination_ip_address': local_subnets})
|
||||
return ipsec_vpn_fw_rules
|
||||
|
||||
def _update_firewall_rules(self, context, vpnservice_id):
|
||||
vpnservice = self.service_plugin._get_vpnservice(context,
|
||||
vpnservice_id)
|
||||
router_db = (
|
||||
self._core_plugin._get_router(context, vpnservice['router_id']))
|
||||
self._core_plugin._update_subnets_and_dnat_firewall(context,
|
||||
router_db)
|
||||
|
||||
def _update_status(self, context, vpn_service_id, ipsec_site_conn_id,
|
||||
status, updated_pending_status=True):
|
||||
status_list = []
|
||||
vpn_status = {}
|
||||
ipsec_site_conn = {}
|
||||
vpn_status['id'] = vpn_service_id
|
||||
vpn_status['updated_pending_status'] = updated_pending_status
|
||||
vpn_status['status'] = status
|
||||
ipsec_site_conn['status'] = status
|
||||
ipsec_site_conn['updated_pending_status'] = updated_pending_status
|
||||
vpn_status['ipsec_site_connections'] = {ipsec_site_conn_id:
|
||||
ipsec_site_conn}
|
||||
status_list.append(vpn_status)
|
||||
self.service_plugin.update_status_by_agent(context, status_list)
|
||||
|
||||
def create_ipsec_site_connection(self, context, ipsec_site_connection):
|
||||
LOG.debug('Creating ipsec site connection %(conn_info)s.',
|
||||
{"conn_info": ipsec_site_connection})
|
||||
|
||||
self.validator.validate_ipsec_conn(context, ipsec_site_connection)
|
||||
new_ipsec = self._convert_ipsec_conn(context, ipsec_site_connection)
|
||||
vpnservice_id = ipsec_site_connection['vpnservice_id']
|
||||
edge_id = self._get_router_edge_id(context, vpnservice_id)[1]
|
||||
with locking.LockManager.get_lock(edge_id):
|
||||
vse_sites = self._generate_new_sites(edge_id, new_ipsec)
|
||||
ipsec_id = ipsec_site_connection["id"]
|
||||
try:
|
||||
LOG.debug('Updating ipsec vpn configuration %(vse_sites)s.',
|
||||
{'vse_sites': vse_sites})
|
||||
self._update_ipsec_config(edge_id, vse_sites, enabled=True)
|
||||
except vcns_exc.VcnsApiException:
|
||||
self._update_status(context, vpnservice_id, ipsec_id,
|
||||
"ERROR")
|
||||
msg = (_("Failed to create ipsec site connection "
|
||||
"configuration with %(edge_id)s.") %
|
||||
{'edge_id': edge_id})
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
LOG.debug('Updating ipsec vpn firewall')
|
||||
try:
|
||||
self._update_firewall_rules(context, vpnservice_id)
|
||||
except vcns_exc.VcnsApiException:
|
||||
self._update_status(context, vpnservice_id, ipsec_id, "ERROR")
|
||||
msg = (_("Failed to update firewall rule for ipsec vpn "
|
||||
"with %(edge_id)s.") % {'edge_id': edge_id})
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
||||
self._update_status(context, vpnservice_id, ipsec_id, "ACTIVE")
|
||||
|
||||
def _get_ipsec_config(self, edge_id):
|
||||
return self._vcns.get_ipsec_config(edge_id)
|
||||
|
||||
def delete_ipsec_site_connection(self, context, ipsec_site_conn):
|
||||
LOG.debug('Deleting ipsec site connection %(site)s.',
|
||||
{"site": ipsec_site_conn})
|
||||
ipsec_id = ipsec_site_conn['id']
|
||||
edge_id = self._get_router_edge_id(context,
|
||||
ipsec_site_conn['vpnservice_id'])[1]
|
||||
with locking.LockManager.get_lock(edge_id):
|
||||
del_site, vse_sites = self._find_vse_site(context, edge_id,
|
||||
ipsec_site_conn)
|
||||
if not del_site:
|
||||
LOG.error("Failed to find ipsec_site_connection "
|
||||
"%(ipsec_site_conn)s with %(edge_id)s.",
|
||||
{'ipsec_site_conn': ipsec_site_conn,
|
||||
'edge_id': edge_id})
|
||||
raise nsxv_exc.NsxIPsecVpnMappingNotFound(conn=ipsec_id)
|
||||
|
||||
vse_sites.remove(del_site)
|
||||
enabled = True if vse_sites else False
|
||||
try:
|
||||
self._update_ipsec_config(edge_id, vse_sites, enabled)
|
||||
except vcns_exc.VcnsApiException:
|
||||
msg = (_("Failed to delete ipsec site connection "
|
||||
"configuration with edge_id: %(edge_id)s.") %
|
||||
{'egde_id': edge_id})
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
||||
try:
|
||||
self._update_firewall_rules(context,
|
||||
ipsec_site_conn['vpnservice_id'])
|
||||
except vcns_exc.VcnsApiException:
|
||||
msg = _("Failed to update firewall rule for ipsec vpn with "
|
||||
"%(edge_id)s.") % {'edge_id': edge_id}
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
def _find_vse_site(self, context, edge_id, site):
|
||||
# Fetch the previous ipsec vpn configuration
|
||||
ipsecvpn_configs = self._get_ipsec_config(edge_id)[1]
|
||||
vpnservice = self.service_plugin._get_vpnservice(context,
|
||||
site['vpnservice_id'])
|
||||
local_cidr = vpnservice['subnet']['cidr']
|
||||
old_site = None
|
||||
vse_sites = None
|
||||
if ipsecvpn_configs['enabled']:
|
||||
vse_sites = ipsecvpn_configs['sites'].get('sites')
|
||||
for s in vse_sites:
|
||||
if ((s['peerSubnets'].get('subnets') == site['peer_cidrs'])
|
||||
and
|
||||
(s['localSubnets'].get('subnets')[0] == local_cidr)):
|
||||
old_site = s
|
||||
break
|
||||
return old_site, vse_sites
|
||||
|
||||
def _update_site_dict(self, context, edge_id, site,
|
||||
ipsec_site_connection):
|
||||
# Fetch the previous ipsec vpn configuration
|
||||
old_site, vse_sites = self._find_vse_site(context, edge_id, site)
|
||||
if old_site:
|
||||
vse_sites.remove(old_site)
|
||||
if 'peer_addresses' in ipsec_site_connection:
|
||||
old_site['peerIp'] = ipsec_site_connection['peer_address']
|
||||
if 'peer_cidrs' in ipsec_site_connection:
|
||||
old_site['peerSubnets']['subnets'] = (ipsec_site_connection
|
||||
['peer_cidrs'])
|
||||
vse_sites.append(old_site)
|
||||
return vse_sites
|
||||
|
||||
def update_ipsec_site_connection(self, context, old_ipsec_conn,
|
||||
ipsec_site_connection):
|
||||
LOG.debug('Updating ipsec site connection %(site)s.',
|
||||
{"site": ipsec_site_connection})
|
||||
vpnservice_id = old_ipsec_conn['vpnservice_id']
|
||||
ipsec_id = old_ipsec_conn['id']
|
||||
edge_id = self._get_router_edge_id(context, vpnservice_id)[1]
|
||||
with locking.LockManager.get_lock(edge_id):
|
||||
vse_sites = self._update_site_dict(context, edge_id,
|
||||
old_ipsec_conn,
|
||||
ipsec_site_connection)
|
||||
if not vse_sites:
|
||||
self._update_status(context, vpnservice_id, ipsec_id,
|
||||
"ERROR")
|
||||
LOG.error("Failed to find ipsec_site_connection "
|
||||
"%(ipsec_site_conn)s with %(edge_id)s.",
|
||||
{'ipsec_site_conn': ipsec_site_connection,
|
||||
'edge_id': edge_id})
|
||||
raise nsxv_exc.NsxIPsecVpnMappingNotFound(conn=ipsec_id)
|
||||
try:
|
||||
LOG.debug('Updating ipsec vpn configuration %(vse_sites)s.',
|
||||
{'vse_sites': vse_sites})
|
||||
self._update_ipsec_config(edge_id, vse_sites)
|
||||
except vcns_exc.VcnsApiException:
|
||||
self._update_status(context, vpnservice_id, ipsec_id, "ERROR")
|
||||
msg = (_("Failed to create ipsec site connection "
|
||||
"configuration with %(edge_id)s.") %
|
||||
{'edge_id': edge_id})
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
if 'peer_cidrs' in ipsec_site_connection:
|
||||
# Update firewall
|
||||
old_ipsec_conn['peer_cidrs'] = (
|
||||
ipsec_site_connection['peer_cidrs'])
|
||||
try:
|
||||
self._update_firewall_rules(context, vpnservice_id)
|
||||
except vcns_exc.VcnsApiException:
|
||||
self._update_status(context, vpnservice_id, ipsec_id,
|
||||
"ERROR")
|
||||
msg = (_("Failed to update firewall rule for ipsec "
|
||||
"vpn with %(edge_id)s.") % {'edge_id': edge_id})
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
def _get_gateway_ips(self, router):
|
||||
"""Obtain the IPv4 and/or IPv6 GW IP for the router.
|
||||
|
||||
If there are multiples, (arbitrarily) use the first one.
|
||||
"""
|
||||
v4_ip = v6_ip = None
|
||||
for fixed_ip in router.gw_port['fixed_ips']:
|
||||
addr = fixed_ip['ip_address']
|
||||
vers = netaddr.IPAddress(addr).version
|
||||
if vers == 4:
|
||||
if v4_ip is None:
|
||||
v4_ip = addr
|
||||
elif v6_ip is None:
|
||||
v6_ip = addr
|
||||
return v4_ip, v6_ip
|
||||
|
||||
def create_vpnservice(self, context, vpnservice):
|
||||
LOG.debug('Creating VPN service %(vpn)s', {'vpn': vpnservice})
|
||||
# Only support distributed and exclusive router type
|
||||
router_id = vpnservice['router_id']
|
||||
vpnservice_id = vpnservice['id']
|
||||
router = self._core_plugin.get_router(context, router_id)
|
||||
if self._is_shared_router(router):
|
||||
# Rolling back change on the neutron
|
||||
self.service_plugin.delete_vpnservice(context, vpnservice_id)
|
||||
msg = _("Router type is not supported for VPN service, only "
|
||||
"support distributed and exclusive router")
|
||||
raise n_exc.InvalidInput(error_message=msg)
|
||||
|
||||
vpnservice = self.service_plugin._get_vpnservice(context,
|
||||
vpnservice_id)
|
||||
v4_ip, v6_ip = self._get_gateway_ips(vpnservice.router)
|
||||
if v4_ip:
|
||||
vpnservice['external_v4_ip'] = v4_ip
|
||||
if v6_ip:
|
||||
vpnservice['external_v6_ip'] = v6_ip
|
||||
self.service_plugin.set_external_tunnel_ips(context,
|
||||
vpnservice_id,
|
||||
v4_ip=v4_ip, v6_ip=v6_ip)
|
||||
|
||||
def update_vpnservice(self, context, old_vpnservice, vpnservice):
|
||||
pass
|
||||
|
||||
def delete_vpnservice(self, context, vpnservice):
|
||||
pass
|
||||
|
||||
def _update_ipsec_config(self, edge_id, sites, enabled=True):
|
||||
ipsec_config = {'featureType': "ipsec_4.0",
|
||||
'enabled': enabled}
|
||||
|
||||
ipsec_config['sites'] = {'sites': sites}
|
||||
try:
|
||||
self._vcns.update_ipsec_config(edge_id, ipsec_config)
|
||||
except vcns_exc.VcnsApiException:
|
||||
msg = _("Failed to update ipsec vpn configuration with "
|
||||
"edge_id: %s") % edge_id
|
||||
raise nsxv_exc.NsxPluginException(err_msg=msg)
|
93
vmware_nsx/services/vpnaas/nsxv/ipsec_validator.py
Normal file
93
vmware_nsx/services/vpnaas/nsxv/ipsec_validator.py
Normal file
@ -0,0 +1,93 @@
|
||||
# Copyright 2016 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 neutron_vpnaas.db.vpn import vpn_validator
|
||||
from oslo_log import log as logging
|
||||
|
||||
from vmware_nsx._i18n import _
|
||||
from vmware_nsx.common import exceptions as nsxv_exc
|
||||
from vmware_nsx.common import nsxv_constants
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IPsecValidator(vpn_validator.VpnReferenceValidator):
|
||||
|
||||
"""Validator methods for Vmware VPN support"""
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
super(IPsecValidator, self).__init__()
|
||||
self.vpn_plugin = service_plugin
|
||||
|
||||
def validate_ikepolicy_version(self, policy_info):
|
||||
"""NSX Edge provides IKEv1"""
|
||||
version = policy_info.get('ike_version')
|
||||
if version != 'v1':
|
||||
msg = _("Unsupported ike policy %s! only v1 "
|
||||
"is supported right now.") % version
|
||||
raise nsxv_exc.NsxIPsecVpnError(details=msg)
|
||||
|
||||
def validate_ikepolicy_pfs(self, policy_info):
|
||||
# Check whether pfs is allowed.
|
||||
if not nsxv_constants.PFS_MAP.get(policy_info['pfs']):
|
||||
msg = _("Unsupported pfs: %(pfs)s! currently only "
|
||||
"the following pfs are supported on VSE: %s") % {
|
||||
'pfs': policy_info['pfs'],
|
||||
'supported': nsxv_constants.PFS_MAP}
|
||||
raise nsxv_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def validate_encryption_algorithm(self, policy_info):
|
||||
encryption = policy_info['encryption_algorithm']
|
||||
if encryption not in nsxv_constants.ENCRYPTION_ALGORITHM_MAP:
|
||||
msg = _("Unsupported encryption_algorithm: %(algo)s! please "
|
||||
"select one of the followoing supported algorithms: "
|
||||
"%(supported_algos)s") % {
|
||||
'algo': encryption,
|
||||
'supported_algos':
|
||||
nsxv_constants.ENCRYPTION_ALGORITHM_MAP}
|
||||
raise nsxv_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def validate_ipsec_policy(self, context, policy_info):
|
||||
"""Ensure IPSec policy encap mode is tunnel for current REST API."""
|
||||
mode = policy_info['encapsulation_mode']
|
||||
if mode not in nsxv_constants.ENCAPSULATION_MODE_ALLOWED:
|
||||
msg = _("Unsupported encapsulation mode: %s! currently only"
|
||||
"'tunnel' mode is supported.") % mode
|
||||
raise nsxv_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def validate_policies_matching_algorithms(self, ikepolicy, ipsecpolicy):
|
||||
# In VSE, Phase 1 and Phase 2 share the same encryption_algorithm
|
||||
# and authentication algorithms setting. At present, just record the
|
||||
# discrepancy error in log and take ipsecpolicy to do configuration.
|
||||
keys = ('auth_algorithm', 'encryption_algorithm', 'pfs')
|
||||
for key in keys:
|
||||
if ikepolicy[key] != ipsecpolicy[key]:
|
||||
LOG.warning("IKEPolicy and IPsecPolicy should have consistent "
|
||||
"auth_algorithm, encryption_algorithm and pfs for "
|
||||
"VSE!")
|
||||
break
|
||||
|
||||
def validate_ipsec_conn(self, context, ipsec_site_conn):
|
||||
ike_policy_id = ipsec_site_conn['ikepolicy_id']
|
||||
ipsec_policy_id = ipsec_site_conn['ipsecpolicy_id']
|
||||
ipsecpolicy = self.vpn_plugin.get_ipsecpolicy(context,
|
||||
ipsec_policy_id)
|
||||
ikepolicy = self.vpn_plugin.get_ikepolicy(context,
|
||||
ike_policy_id)
|
||||
self.validate_ikepolicy_version(ikepolicy)
|
||||
self.validate_ikepolicy_pfs(ikepolicy)
|
||||
self.validate_encryption_algorithm(ikepolicy)
|
||||
self.validate_ipsec_policy(context, ipsecpolicy)
|
||||
self.validate_policies_matching_algorithms(ikepolicy, ipsecpolicy)
|
0
vmware_nsx/tests/unit/services/vpnaas/__init__.py
Normal file
0
vmware_nsx/tests/unit/services/vpnaas/__init__.py
Normal file
255
vmware_nsx/tests/unit/services/vpnaas/test_nsxv_vpnaas.py
Normal file
255
vmware_nsx/tests/unit/services/vpnaas/test_nsxv_vpnaas.py
Normal file
@ -0,0 +1,255 @@
|
||||
# Copyright 2016 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 contextlib
|
||||
|
||||
import mock
|
||||
from neutron_lib import context
|
||||
from neutron_lib import exceptions as n_exc
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_vpnaas.db.vpn import vpn_models # noqa
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from vmware_nsx.common import exceptions as nsxv_exc
|
||||
from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as vcns_exc
|
||||
from vmware_nsx.services.vpnaas.nsxv import ipsec_driver
|
||||
from vmware_nsx.tests.unit.nsx_v import test_plugin
|
||||
|
||||
|
||||
_uuid = uuidutils.generate_uuid
|
||||
|
||||
DRIVER_PATH = "vmware_nsx.services.vpnaas.nsxv.ipsec_driver.NSXvIPsecVpnDriver"
|
||||
VALI_PATH = "vmware_nsx.services.vpnaas.nsxv.ipsec_validator.IPsecValidator"
|
||||
FAKE_ROUTER_ID = "aaaaaa-bbbbb-ccc"
|
||||
FAKE_VPNSERVICE_ID = _uuid()
|
||||
FAKE_IPSEC_CONNECTION = {"vpnservice_id": FAKE_VPNSERVICE_ID,
|
||||
"id": _uuid()}
|
||||
FAKE_EDGE_ID = _uuid()
|
||||
FAKE_IPSEC_VPN_SITE = {"peerIp": "192.168.1.1"}
|
||||
FAKE_VCNSAPIEXC = {"status": "fail",
|
||||
"head": "fake_head",
|
||||
"response": "error"}
|
||||
FAKE_NEW_CONNECTION = {"peer_cidrs": "192.168.1.0/24"}
|
||||
|
||||
|
||||
class TestVpnaasDriver(test_plugin.NsxVPluginV2TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVpnaasDriver, self).setUp()
|
||||
self.context = context.get_admin_context()
|
||||
self.service_plugin = mock.Mock()
|
||||
self.validator = mock.Mock()
|
||||
self.driver = ipsec_driver.NSXvIPsecVpnDriver(self.service_plugin)
|
||||
self.plugin = directory.get_plugin()
|
||||
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
|
||||
|
||||
@mock.patch('%s.validate_ipsec_conn' % VALI_PATH)
|
||||
@mock.patch('%s._convert_ipsec_conn' % DRIVER_PATH)
|
||||
@mock.patch('%s._get_router_edge_id' % DRIVER_PATH)
|
||||
@mock.patch('%s._generate_new_sites' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_ipsec_config' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_status' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_firewall_rules' % DRIVER_PATH)
|
||||
def test_create_ipsec_site_connection(self, mock_update_fw,
|
||||
mock_update_status,
|
||||
mock_update_ipsec, mock_gen_new,
|
||||
mock_get_id,
|
||||
mock_conv_ipsec,
|
||||
mock_val_conn):
|
||||
mock_get_id.return_value = (FAKE_ROUTER_ID, FAKE_EDGE_ID)
|
||||
mock_conv_ipsec.return_value = FAKE_IPSEC_VPN_SITE
|
||||
mock_gen_new.return_value = FAKE_IPSEC_VPN_SITE
|
||||
self.driver.create_ipsec_site_connection(self.context,
|
||||
FAKE_IPSEC_CONNECTION)
|
||||
mock_val_conn.assert_called_with(self.context,
|
||||
FAKE_IPSEC_CONNECTION)
|
||||
mock_conv_ipsec.assert_called_with(self.context,
|
||||
FAKE_IPSEC_CONNECTION)
|
||||
mock_get_id.assert_called_with(self.context, FAKE_VPNSERVICE_ID)
|
||||
mock_gen_new.assert_called_with(FAKE_EDGE_ID, FAKE_IPSEC_VPN_SITE)
|
||||
mock_update_ipsec.assert_called_with(FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_VPN_SITE,
|
||||
enabled=True)
|
||||
mock_update_fw.assert_called_with(self.context, FAKE_VPNSERVICE_ID)
|
||||
mock_update_status.assert_called_with(
|
||||
self.context,
|
||||
FAKE_IPSEC_CONNECTION["vpnservice_id"],
|
||||
FAKE_IPSEC_CONNECTION["id"],
|
||||
"ACTIVE")
|
||||
|
||||
@mock.patch('%s.validate_ipsec_conn' % VALI_PATH)
|
||||
@mock.patch('%s._convert_ipsec_conn' % DRIVER_PATH)
|
||||
@mock.patch('%s._get_router_edge_id' % DRIVER_PATH)
|
||||
@mock.patch('%s._generate_new_sites' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_ipsec_config' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_status' % DRIVER_PATH)
|
||||
def test_create_ipsec_site_connection_fail(self,
|
||||
mock_update_status,
|
||||
mock_update_ipsec,
|
||||
mock_gen_new, mock_get_id,
|
||||
mock_conv_ipsec,
|
||||
mock_val_conn):
|
||||
mock_get_id.return_value = (FAKE_ROUTER_ID, FAKE_EDGE_ID)
|
||||
mock_conv_ipsec.return_value = FAKE_IPSEC_VPN_SITE
|
||||
mock_gen_new.return_value = FAKE_IPSEC_VPN_SITE
|
||||
mock_update_ipsec.side_effect = (
|
||||
vcns_exc.VcnsApiException(**FAKE_VCNSAPIEXC))
|
||||
self.assertRaises(nsxv_exc.NsxPluginException,
|
||||
self.driver.create_ipsec_site_connection,
|
||||
self.context, FAKE_IPSEC_CONNECTION)
|
||||
mock_val_conn.assert_called_with(self.context, FAKE_IPSEC_CONNECTION)
|
||||
mock_conv_ipsec.assert_called_with(self.context, FAKE_IPSEC_CONNECTION)
|
||||
mock_get_id.assert_called_with(self.context, FAKE_VPNSERVICE_ID)
|
||||
mock_gen_new.assert_called_with(FAKE_EDGE_ID, FAKE_IPSEC_VPN_SITE)
|
||||
mock_update_ipsec.assert_called_with(FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_VPN_SITE,
|
||||
enabled=True)
|
||||
mock_update_status.assert_called_with(
|
||||
self.context,
|
||||
FAKE_IPSEC_CONNECTION["vpnservice_id"],
|
||||
FAKE_IPSEC_CONNECTION["id"],
|
||||
"ERROR")
|
||||
|
||||
@mock.patch('%s.validate_ipsec_conn' % VALI_PATH)
|
||||
@mock.patch('%s._convert_ipsec_conn' % DRIVER_PATH)
|
||||
@mock.patch('%s._get_router_edge_id' % DRIVER_PATH)
|
||||
@mock.patch('%s._generate_new_sites' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_ipsec_config' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_status' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_firewall_rules' % DRIVER_PATH)
|
||||
def test_update_fw_fail(self, mock_update_fw, mock_update_status,
|
||||
mock_update_ipsec, mock_gen_new,
|
||||
mock_get_id, mock_conv_ipsec, mock_val_conn):
|
||||
mock_get_id.return_value = (FAKE_ROUTER_ID, FAKE_EDGE_ID)
|
||||
mock_conv_ipsec.return_value = FAKE_IPSEC_VPN_SITE
|
||||
mock_gen_new.return_value = FAKE_IPSEC_VPN_SITE
|
||||
mock_update_fw.side_effect = (
|
||||
vcns_exc.VcnsApiException(**FAKE_VCNSAPIEXC))
|
||||
self.assertRaises(nsxv_exc.NsxPluginException,
|
||||
self.driver.create_ipsec_site_connection,
|
||||
self.context, FAKE_IPSEC_CONNECTION)
|
||||
mock_val_conn.assert_called_with(self.context, FAKE_IPSEC_CONNECTION)
|
||||
mock_conv_ipsec.assert_called_with(self.context, FAKE_IPSEC_CONNECTION)
|
||||
mock_get_id.assert_called_with(self.context, FAKE_VPNSERVICE_ID)
|
||||
mock_gen_new.assert_called_with(FAKE_EDGE_ID, FAKE_IPSEC_VPN_SITE)
|
||||
mock_update_ipsec.assert_called_with(FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_VPN_SITE,
|
||||
enabled=True)
|
||||
mock_update_fw.assert_called_with(self.context, FAKE_VPNSERVICE_ID)
|
||||
mock_update_status.assert_called_with(
|
||||
self.context,
|
||||
FAKE_IPSEC_CONNECTION["vpnservice_id"],
|
||||
FAKE_IPSEC_CONNECTION["id"],
|
||||
"ERROR")
|
||||
|
||||
@mock.patch('%s._get_router_edge_id' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_site_dict' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_ipsec_config' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_firewall_rules' % DRIVER_PATH)
|
||||
def test_update_ipsec(self, mock_update_fw, mock_update_ipsec,
|
||||
mock_update_sites, mock_get_id):
|
||||
mock_get_id.return_value = (FAKE_ROUTER_ID, FAKE_EDGE_ID)
|
||||
mock_update_sites.return_value = FAKE_IPSEC_VPN_SITE
|
||||
self.driver.update_ipsec_site_connection(self.context,
|
||||
FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
mock_update_sites.assert_called_with(self.context, FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
mock_update_ipsec.assert_called_with(FAKE_EDGE_ID, FAKE_IPSEC_VPN_SITE)
|
||||
mock_update_fw.assert_called_with(self.context, FAKE_VPNSERVICE_ID)
|
||||
|
||||
@mock.patch('%s._get_router_edge_id' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_site_dict' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_ipsec_config' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_firewall_rules' % DRIVER_PATH)
|
||||
def test_update_ipsec_fail_with_notfound(self, mock_update_fw,
|
||||
mock_update_ipsec,
|
||||
mock_update_sites, mock_get_id):
|
||||
mock_get_id.return_value = (FAKE_ROUTER_ID, FAKE_EDGE_ID)
|
||||
mock_update_sites.return_value = {}
|
||||
self.assertRaises(nsxv_exc.NsxIPsecVpnMappingNotFound,
|
||||
self.driver.update_ipsec_site_connection,
|
||||
self.context, FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
mock_update_sites.assert_called_with(self.context,
|
||||
FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
|
||||
@mock.patch('%s._get_router_edge_id' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_site_dict' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_ipsec_config' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_firewall_rules' % DRIVER_PATH)
|
||||
def test_update_ipsec_fail_with_fw_fail(self, mock_update_fw,
|
||||
mock_update_ipsec,
|
||||
mock_update_sites, mock_get_id):
|
||||
mock_get_id.return_value = (FAKE_ROUTER_ID, FAKE_EDGE_ID)
|
||||
mock_update_fw.side_effect = (
|
||||
vcns_exc.VcnsApiException(**FAKE_VCNSAPIEXC))
|
||||
self.assertRaises(nsxv_exc.NsxPluginException,
|
||||
self.driver.update_ipsec_site_connection,
|
||||
self.context, FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
mock_update_sites.assert_called_with(self.context, FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
mock_update_fw.assert_called_with(self.context, FAKE_VPNSERVICE_ID)
|
||||
|
||||
@mock.patch('%s._get_router_edge_id' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_site_dict' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_ipsec_config' % DRIVER_PATH)
|
||||
@mock.patch('%s._update_status' % DRIVER_PATH)
|
||||
def test_update_ipsec_fail_with_site_fail(self, mock_update_status,
|
||||
mock_update_ipsec,
|
||||
mock_update_sites, mock_get_id):
|
||||
mock_get_id.return_value = (FAKE_ROUTER_ID, FAKE_EDGE_ID)
|
||||
mock_update_sites.return_value = FAKE_IPSEC_VPN_SITE
|
||||
mock_update_ipsec.side_effect = (
|
||||
vcns_exc.VcnsApiException(**FAKE_VCNSAPIEXC))
|
||||
self.assertRaises(nsxv_exc.NsxPluginException,
|
||||
self.driver.update_ipsec_site_connection,
|
||||
self.context,
|
||||
FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
mock_update_sites.assert_called_with(self.context, FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_CONNECTION,
|
||||
FAKE_NEW_CONNECTION)
|
||||
mock_update_ipsec.assert_called_with(FAKE_EDGE_ID,
|
||||
FAKE_IPSEC_VPN_SITE)
|
||||
mock_update_status.assert_called_with(
|
||||
self.context,
|
||||
FAKE_IPSEC_CONNECTION["vpnservice_id"],
|
||||
FAKE_IPSEC_CONNECTION["id"],
|
||||
"ERROR")
|
||||
|
||||
def test_create_vpn_service_on_shared_router(self):
|
||||
with self.router(router_type='shared') as router, self.subnet():
|
||||
vpnservice = {'router_id': router['id'],
|
||||
'id': _uuid()}
|
||||
self.assertRaises(n_exc.InvalidInput,
|
||||
self.driver.create_vpnservice,
|
||||
self.context, vpnservice)
|
@ -21,6 +21,7 @@ from neutron.db import servicetype_db # noqa
|
||||
from neutron.quota import resource_registry
|
||||
from neutron.tests import base
|
||||
from neutron_lib.callbacks import registry
|
||||
from neutron_lib import constants
|
||||
from oslo_config import cfg
|
||||
from oslo_log import _options
|
||||
from oslo_log import log as logging
|
||||
@ -137,10 +138,13 @@ class TestNsxvAdminUtils(AbstractTestAdminUtils,
|
||||
return_value=0).start()
|
||||
|
||||
self._plugin = nsxv_utils.NsxVPluginWrapper()
|
||||
mock_nm_get_plugin = mock.patch(
|
||||
"neutron_lib.plugins.directory.get_plugin")
|
||||
self.mock_nm_get_plugin = mock_nm_get_plugin.start()
|
||||
self.mock_nm_get_plugin.return_value = self._plugin
|
||||
|
||||
def get_plugin_mock(alias=constants.CORE):
|
||||
if alias in (constants.CORE, constants.L3):
|
||||
return self._plugin
|
||||
|
||||
mock.patch("neutron_lib.plugins.directory.get_plugin",
|
||||
side_effect=get_plugin_mock).start()
|
||||
|
||||
# Create a router to make sure we have deployed an edge
|
||||
self.router = self.create_router()
|
||||
|
Loading…
Reference in New Issue
Block a user