NSX|P: VPNaaS driver
Change-Id: I3dae7c34527f7f65f37cf03e699007141865a090
This commit is contained in:
parent
74c9a66e00
commit
55b0cf16e8
@ -306,6 +306,20 @@ Add octavia and python-octaviaclient repos as external repositories and configur
|
|||||||
network_driver = allowed_address_pairs_driver
|
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
|
NSX-TVD
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -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.extensions import secgroup_rule_local_ip_prefix as sg_prefix
|
||||||
from vmware_nsx.plugins.common import plugin
|
from vmware_nsx.plugins.common import plugin
|
||||||
from vmware_nsx.services.qos.common import utils as qos_com_utils
|
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 exceptions as nsx_lib_exc
|
||||||
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
|
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
|
||||||
|
@ -1207,6 +1207,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
|||||||
port['device_owner'] in [const.DEVICE_OWNER_DHCP]):
|
port['device_owner'] in [const.DEVICE_OWNER_DHCP]):
|
||||||
msg = (_('Can not delete DHCP port %s') % port_id)
|
msg = (_('Can not delete DHCP port %s') % port_id)
|
||||||
raise n_exc.BadRequest(resource='port', msg=msg)
|
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):
|
if self._is_backend_port(context, port_data):
|
||||||
self._delete_port_on_backend(context, net_id, port_id)
|
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)
|
router = self._get_router(context, router_id)
|
||||||
snat_exist = router.enable_snat
|
snat_exist = router.enable_snat
|
||||||
fw_exist = self._router_has_edge_fw_rules(context, router)
|
fw_exist = self._router_has_edge_fw_rules(context, router)
|
||||||
|
vpn_exist = self.service_router_has_vpnaas(context, router_id)
|
||||||
lb_exist = False
|
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(
|
lb_exist = self.service_router_has_loadbalancers(
|
||||||
context, router_id)
|
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):
|
def service_router_has_loadbalancers(self, context, router_id):
|
||||||
tags_to_search = [{'scope': lb_const.LR_ROUTER_TYPE, 'tag': 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']
|
)['results']
|
||||||
return True if router_lb_services else False
|
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):
|
def verify_sr_at_backend(self, router_id):
|
||||||
"""Check if the backend Tier1 has a service router or not"""
|
"""Check if the backend Tier1 has a service router or not"""
|
||||||
if self.nsxpolicy.tier1.get_edge_cluster_path(router_id):
|
if self.nsxpolicy.tier1.get_edge_cluster_path(router_id):
|
||||||
@ -1734,9 +1746,17 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
|||||||
router_data = router['router']
|
router_data = router['router']
|
||||||
self._assert_on_router_admin_state(router_data)
|
self._assert_on_router_admin_state(router_data)
|
||||||
|
|
||||||
|
vpn_driver = None
|
||||||
if validators.is_attr_set(gw_info):
|
if validators.is_attr_set(gw_info):
|
||||||
self._validate_update_router_gw(context, router_id, 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_added = []
|
||||||
routes_removed = []
|
routes_removed = []
|
||||||
if 'routes' in router_data:
|
if 'routes' in router_data:
|
||||||
@ -1782,7 +1802,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
LOG.error("Rollback router %s changes failed to add "
|
LOG.error("Rollback router %s changes failed to add "
|
||||||
"static routes: %s", router_id, e)
|
"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
|
return updated_router
|
||||||
|
|
||||||
def _get_gateway_addr_from_subnet(self, subnet):
|
def _get_gateway_addr_from_subnet(self, subnet):
|
||||||
@ -2814,3 +2836,29 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
|||||||
if len(port_tags) != orig_len:
|
if len(port_tags) != orig_len:
|
||||||
self.nsxpolicy.segment_port.update(
|
self.nsxpolicy.segment_port.update(
|
||||||
segment_id, port_id, tags=port_tags)
|
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
|
||||||
|
@ -2567,6 +2567,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
|
|||||||
port should be returned, and the rules should be ingress/egress
|
port should be returned, and the rules should be ingress/egress
|
||||||
(but not both) and include the source/dest nsx logical port.
|
(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 = []
|
extra_rules = []
|
||||||
|
|
||||||
# DHCP relay rules:
|
# DHCP relay rules:
|
||||||
|
@ -255,7 +255,7 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
|
|||||||
return translated_rules
|
return translated_rules
|
||||||
|
|
||||||
def _get_port_translated_rules(self, project_id, router_id, neutron_net_id,
|
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
|
"""Return the list of translated FWaaS rules per port
|
||||||
Add the egress/ingress rules of this port +
|
Add the egress/ingress rules of this port +
|
||||||
default drop rules in each direction for 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,
|
project_id, router_id, net_group_id,
|
||||||
firewall_group['egress_rule_list'], is_ingress=False))
|
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
|
# Add ingress/egress block rules for this port
|
||||||
port_rules.extend([
|
port_rules.extend([
|
||||||
self.nsxpolicy.gateway_policy.build_entry(
|
self.nsxpolicy.gateway_policy.build_entry(
|
||||||
@ -323,10 +327,16 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
|
|||||||
fwg = self.get_port_fwg(context, port['id'])
|
fwg = self.get_port_fwg(context, port['id'])
|
||||||
if fwg:
|
if fwg:
|
||||||
router_with_fw = True
|
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
|
# Add the FWaaS rules for this port:ingress/egress firewall
|
||||||
# rules + default ingress/egress drop rule for this port
|
# rules + default ingress/egress drop rule for this port
|
||||||
fw_rules.extend(self._get_port_translated_rules(
|
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
|
# Add a default allow-all rule to all other traffic & ports
|
||||||
fw_rules.append(self._get_default_backend_rule(router_id))
|
fw_rules.append(self._get_default_backend_rule(router_id))
|
||||||
|
0
vmware_nsx/services/vpnaas/common_v3/__init__.py
Normal file
0
vmware_nsx/services/vpnaas/common_v3/__init__.py
Normal file
173
vmware_nsx/services/vpnaas/common_v3/ipsec_driver.py
Normal file
173
vmware_nsx/services/vpnaas/common_v3/ipsec_driver.py
Normal 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'))
|
@ -27,10 +27,23 @@ AUTH_ALGORITHM_MAP = {
|
|||||||
'sha256': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA256,
|
'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 = {
|
PFS_MAP = {
|
||||||
'group14': vpn_ipsec.DHGroupTypes.DH_GROUP_14
|
'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 = {
|
IKE_VERSION_MAP = {
|
||||||
'v1': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V1,
|
'v1': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V1,
|
||||||
'v2': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V2,
|
'v2': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V2,
|
400
vmware_nsx/services/vpnaas/common_v3/ipsec_validator.py
Normal file
400
vmware_nsx/services/vpnaas/common_v3/ipsec_validator.py
Normal 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)
|
0
vmware_nsx/services/vpnaas/nsxp/__init__.py
Normal file
0
vmware_nsx/services/vpnaas/nsxp/__init__.py
Normal file
715
vmware_nsx/services/vpnaas/nsxp/ipsec_driver.py
Normal file
715
vmware_nsx/services/vpnaas/nsxp/ipsec_driver.py
Normal 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)
|
49
vmware_nsx/services/vpnaas/nsxp/ipsec_validator.py
Normal file
49
vmware_nsx/services/vpnaas/nsxp/ipsec_validator.py
Normal 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
|
@ -23,14 +23,11 @@ from neutron_lib.callbacks import registry
|
|||||||
from neutron_lib.callbacks import resources
|
from neutron_lib.callbacks import resources
|
||||||
from neutron_lib import constants
|
from neutron_lib import constants
|
||||||
from neutron_lib import context as n_context
|
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.common import exceptions as nsx_exc
|
||||||
from vmware_nsx.db import db
|
from vmware_nsx.db import db
|
||||||
from vmware_nsx.extensions import projectpluginmap
|
from vmware_nsx.services.vpnaas.common_v3 import ipsec_driver as common_driver
|
||||||
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils
|
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
|
||||||
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_validator
|
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_validator
|
||||||
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
|
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
|
||||||
from vmware_nsxlib.v3 import nsx_constants as consts
|
from vmware_nsxlib.v3 import nsx_constants as consts
|
||||||
@ -40,50 +37,18 @@ LOG = logging.getLogger(__name__)
|
|||||||
IPSEC = 'ipsec'
|
IPSEC = 'ipsec'
|
||||||
|
|
||||||
|
|
||||||
class RouterWithSNAT(nexception.BadRequest):
|
class NSXv3IPsecVpnDriver(common_driver.NSXcommonIPsecVpnDriver):
|
||||||
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):
|
|
||||||
|
|
||||||
def __init__(self, service_plugin):
|
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)
|
validator = ipsec_validator.IPsecV3Validator(service_plugin)
|
||||||
super(NSXv3IPsecVpnDriver, self).__init__(service_plugin, validator)
|
super(NSXv3IPsecVpnDriver, self).__init__(service_plugin, validator)
|
||||||
|
self._nsxlib = self._core_plugin.nsxlib
|
||||||
|
self._nsx_vpn = self._nsxlib.vpn_ipsec
|
||||||
|
|
||||||
registry.subscribe(
|
registry.subscribe(
|
||||||
self._delete_local_endpoint, resources.ROUTER_GATEWAY,
|
self._delete_local_endpoint, resources.ROUTER_GATEWAY,
|
||||||
events.AFTER_DELETE)
|
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):
|
def _translate_cidr(self, cidr):
|
||||||
return self._nsxlib.firewall_section.get_ip_cidr_reference(
|
return self._nsxlib.firewall_section.get_ip_cidr_reference(
|
||||||
cidr,
|
cidr,
|
||||||
@ -170,18 +135,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
|||||||
self._nsxlib.logical_router.update_advertisement_rules(
|
self._nsxlib.logical_router.update_advertisement_rules(
|
||||||
logical_router_id, rules, name_prefix=rule_name_pref)
|
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):
|
def _nsx_tags(self, context, connection):
|
||||||
return self._nsxlib.build_v3_tags_payload(
|
return self._nsxlib.build_v3_tags_payload(
|
||||||
connection, resource_type='os-vpn-connection-id',
|
connection, resource_type='os-vpn-connection-id',
|
||||||
@ -251,9 +204,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
|||||||
def _delete_ipsec_profile(self, ipsecprofile_id):
|
def _delete_ipsec_profile(self, ipsecprofile_id):
|
||||||
self._nsx_vpn.tunnel_profile.delete(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):
|
def _create_dpd_profile(self, context, connection):
|
||||||
dpd_info = connection['dpd']
|
dpd_info = connection['dpd']
|
||||||
try:
|
try:
|
||||||
@ -378,14 +328,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
|||||||
vpnservice['project_id'])
|
vpnservice['project_id'])
|
||||||
return local_ep_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):
|
def _delete_local_endpoint_by_router(self, context, router_id):
|
||||||
# delete the local endpoint from the NSX
|
# delete the local endpoint from the NSX
|
||||||
local_ep_id = self._search_local_endpint(router_id)
|
local_ep_id = self._search_local_endpint(router_id)
|
||||||
@ -403,43 +345,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
|||||||
ctx = n_context.get_admin_context()
|
ctx = n_context.get_admin_context()
|
||||||
self._delete_local_endpoint_by_router(ctx, router_id)
|
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):
|
def validate_router_gw_info(self, context, router_id, gw_info):
|
||||||
"""Upon router gw update - verify no-snat"""
|
"""Upon router gw update - verify no-snat"""
|
||||||
# check if this router has a vpn service
|
# check if this router has a vpn service
|
||||||
@ -454,7 +359,7 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
|||||||
# do not allow enable-snat
|
# do not allow enable-snat
|
||||||
if (gw_info and
|
if (gw_info and
|
||||||
gw_info.get('enable_snat', cfg.CONF.enable_snat_by_default)):
|
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:
|
else:
|
||||||
# if this is a non-vpn router. if snat was disabled, should check
|
# if this is a non-vpn router. if snat was disabled, should check
|
||||||
# there is no overlapping with vpn connections
|
# there is no overlapping with vpn connections
|
||||||
@ -467,7 +372,8 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
|||||||
# find all vpn services with connections
|
# find all vpn services with connections
|
||||||
if not self._check_subnets_overlap_with_all_conns(
|
if not self._check_subnets_overlap_with_all_conns(
|
||||||
admin_con, subnets):
|
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):
|
def _get_session_rules(self, context, connection, vpnservice):
|
||||||
# TODO(asarfaty): support vpn-endpoint-groups too
|
# TODO(asarfaty): support vpn-endpoint-groups too
|
||||||
@ -701,16 +607,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
|||||||
|
|
||||||
return service['id']
|
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):
|
def _find_vpn_service(self, tier0_uuid, validate=True):
|
||||||
# find the service for the tier0 router in the NSX.
|
# find the service for the tier0 router in the NSX.
|
||||||
# Note(asarfaty) we expect only a small number of services
|
# 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)
|
tier0_uuid = self._get_tier0_uuid(context, vpnservice)
|
||||||
return self._find_vpn_service(tier0_uuid, validate=False)
|
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):
|
def create_vpnservice(self, context, vpnservice):
|
||||||
#TODO(asarfaty) support vpn-endpoint-group-create for local & peer
|
#TODO(asarfaty) support vpn-endpoint-group-create for local & peer
|
||||||
# cidrs too
|
# cidrs too
|
||||||
|
@ -13,37 +13,25 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import netaddr
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_log import log as logging
|
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._i18n import _
|
||||||
from vmware_nsx.common import exceptions as nsx_exc
|
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_nsx.services.vpnaas.nsxv3 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 nsx_constants as consts
|
||||||
from vmware_nsxlib.v3 import vpn_ipsec
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
|
class IPsecV3Validator(ipsec_validator.IPsecCommonValidator):
|
||||||
|
|
||||||
"""Validator methods for Vmware NSX-V3 VPN support"""
|
"""Validator methods for Vmware NSX-V3 VPN support"""
|
||||||
def __init__(self, service_plugin):
|
def __init__(self, service_plugin):
|
||||||
super(IPsecV3Validator, self).__init__()
|
super(IPsecV3Validator, self).__init__(service_plugin)
|
||||||
self.vpn_plugin = service_plugin
|
|
||||||
|
|
||||||
self._core_plugin = self.core_plugin
|
@property
|
||||||
if self._core_plugin.is_tvd_plugin():
|
def nsxlib(self):
|
||||||
self._core_plugin = self._core_plugin.get_plugin_by_type(
|
return self._core_plugin.nsxlib
|
||||||
projectpluginmap.NsxPlugins.NSX_T)
|
|
||||||
self.nsxlib = self._core_plugin.nsxlib
|
|
||||||
|
|
||||||
self.check_backend_version()
|
|
||||||
|
|
||||||
def check_backend_version(self):
|
def check_backend_version(self):
|
||||||
if not self.nsxlib.feature_supported(consts.FEATURE_IPSEC_VPN):
|
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())
|
"(version %s)") % self.nsxlib.get_version())
|
||||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||||
|
|
||||||
def _validate_policy_lifetime(self, policy_info, policy_type):
|
@property
|
||||||
"""NSX supports only units=seconds"""
|
def auth_algorithm_map(self):
|
||||||
lifetime = policy_info.get('lifetime')
|
return ipsec_utils.AUTH_ALGORITHM_MAP
|
||||||
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)
|
|
||||||
|
|
||||||
def _validate_policy_auth_algorithm(self, policy_info, policy_type):
|
@property
|
||||||
"""NSX supports only SHA1 and SHA256"""
|
def pfs_map(self):
|
||||||
auth = policy_info.get('auth_algorithm')
|
return ipsec_utils.PFS_MAP
|
||||||
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)
|
|
||||||
|
|
||||||
def _validate_policy_encryption_algorithm(self, policy_info, policy_type):
|
def _validate_t0_ha_mode(self, tier0_uuid):
|
||||||
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)
|
|
||||||
# TODO(asarfaty): cache this result
|
# TODO(asarfaty): cache this result
|
||||||
tier0_router = self.nsxlib.logical_router.get(tier0_uuid)
|
tier0_router = self.nsxlib.logical_router.get(tier0_uuid)
|
||||||
if (not tier0_router or
|
if (not tier0_router or
|
||||||
@ -323,69 +66,11 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
|
|||||||
"with ACTIVE_STANDBY HA mode")
|
"with ACTIVE_STANDBY HA mode")
|
||||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
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
|
# Verify that this is a no-snat router
|
||||||
|
router_db = self._core_plugin._get_router(context, router_id)
|
||||||
if router_db.enable_snat:
|
if router_db.enable_snat:
|
||||||
msg = _("VPN is supported only for routers with disabled SNAT")
|
msg = _("VPN is supported only for routers with disabled SNAT")
|
||||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
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)
|
|
||||||
|
757
vmware_nsx/tests/unit/services/vpnaas/test_nsxp_vpnaas.py
Normal file
757
vmware_nsx/tests/unit/services/vpnaas/test_nsxp_vpnaas.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user