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
|
||||
|
||||
|
||||
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
|
||||
-------
|
||||
|
||||
|
@ -86,7 +86,7 @@ from vmware_nsx.extensions import providersecuritygroup as provider_sg
|
||||
from vmware_nsx.extensions import secgroup_rule_local_ip_prefix as sg_prefix
|
||||
from vmware_nsx.plugins.common import plugin
|
||||
from vmware_nsx.services.qos.common import utils as qos_com_utils
|
||||
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils
|
||||
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
|
||||
|
||||
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
|
||||
from vmware_nsxlib.v3 import nsx_constants as nsxlib_consts
|
||||
|
@ -1207,6 +1207,8 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
||||
port['device_owner'] in [const.DEVICE_OWNER_DHCP]):
|
||||
msg = (_('Can not delete DHCP port %s') % port_id)
|
||||
raise n_exc.BadRequest(resource='port', msg=msg)
|
||||
if not force_delete_vpn:
|
||||
self._assert_on_vpn_port_change(port)
|
||||
|
||||
if self._is_backend_port(context, port_data):
|
||||
self._delete_port_on_backend(context, net_id, port_id)
|
||||
@ -1450,11 +1452,12 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
||||
router = self._get_router(context, router_id)
|
||||
snat_exist = router.enable_snat
|
||||
fw_exist = self._router_has_edge_fw_rules(context, router)
|
||||
vpn_exist = self.service_router_has_vpnaas(context, router_id)
|
||||
lb_exist = False
|
||||
if not (fw_exist or snat_exist):
|
||||
if not (fw_exist or snat_exist or vpn_exist):
|
||||
lb_exist = self.service_router_has_loadbalancers(
|
||||
context, router_id)
|
||||
return snat_exist or lb_exist or fw_exist
|
||||
return snat_exist or lb_exist or fw_exist or vpn_exist
|
||||
|
||||
def service_router_has_loadbalancers(self, context, router_id):
|
||||
tags_to_search = [{'scope': lb_const.LR_ROUTER_TYPE, 'tag': router_id}]
|
||||
@ -1464,6 +1467,15 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
||||
)['results']
|
||||
return True if router_lb_services else False
|
||||
|
||||
def service_router_has_vpnaas(self, context, router_id):
|
||||
"""Return True if there is a vpn service attached to this router"""
|
||||
vpn_plugin = directory.get_plugin(plugin_const.VPN)
|
||||
if vpn_plugin:
|
||||
filters = {'router_id': [router_id]}
|
||||
if vpn_plugin.get_vpnservices(context.elevated(), filters=filters):
|
||||
return True
|
||||
return False
|
||||
|
||||
def verify_sr_at_backend(self, router_id):
|
||||
"""Check if the backend Tier1 has a service router or not"""
|
||||
if self.nsxpolicy.tier1.get_edge_cluster_path(router_id):
|
||||
@ -1734,9 +1746,17 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
||||
router_data = router['router']
|
||||
self._assert_on_router_admin_state(router_data)
|
||||
|
||||
vpn_driver = None
|
||||
if validators.is_attr_set(gw_info):
|
||||
self._validate_update_router_gw(context, router_id, gw_info)
|
||||
|
||||
# VPNaaS need to be notified on router GW changes (there is
|
||||
# currently no matching upstream registration for this)
|
||||
vpn_plugin = directory.get_plugin(plugin_const.VPN)
|
||||
if vpn_plugin:
|
||||
vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider]
|
||||
vpn_driver.validate_router_gw_info(context, router_id, gw_info)
|
||||
|
||||
routes_added = []
|
||||
routes_removed = []
|
||||
if 'routes' in router_data:
|
||||
@ -1782,7 +1802,9 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
||||
except Exception as e:
|
||||
LOG.error("Rollback router %s changes failed to add "
|
||||
"static routes: %s", router_id, e)
|
||||
|
||||
if vpn_driver:
|
||||
# Update vpn advertisement if GW was updated
|
||||
vpn_driver.update_router_advertisement(context, router_id)
|
||||
return updated_router
|
||||
|
||||
def _get_gateway_addr_from_subnet(self, subnet):
|
||||
@ -2814,3 +2836,29 @@ class NsxPolicyPlugin(nsx_plugin_common.NsxPluginV3Base):
|
||||
if len(port_tags) != orig_len:
|
||||
self.nsxpolicy.segment_port.update(
|
||||
segment_id, port_id, tags=port_tags)
|
||||
|
||||
def get_extra_fw_rules(self, context, router_id, port_id):
|
||||
"""Return firewall rules that should be added to the router firewall
|
||||
|
||||
This method should return a list of allow firewall rules that are
|
||||
required in order to enable different plugin features with north/south
|
||||
traffic.
|
||||
The returned rules will be added after the FWaaS rules, and before the
|
||||
default drop rule.
|
||||
Only rules relevant for port_id router interface port should be
|
||||
returned, and the rules should be ingress/egress
|
||||
(but not both) and include the source/dest nsx logical port.
|
||||
"""
|
||||
extra_rules = []
|
||||
|
||||
# VPN rules:
|
||||
vpn_plugin = directory.get_plugin(plugin_const.VPN)
|
||||
if vpn_plugin:
|
||||
vpn_driver = vpn_plugin.drivers[vpn_plugin.default_provider]
|
||||
vpn_rules = (
|
||||
vpn_driver._generate_ipsecvpn_firewall_rules(
|
||||
self.plugin_type(), context, router_id=router_id))
|
||||
if vpn_rules:
|
||||
extra_rules.extend(vpn_rules)
|
||||
|
||||
return extra_rules
|
||||
|
@ -2567,6 +2567,8 @@ class NsxV3Plugin(nsx_plugin_common.NsxPluginV3Base,
|
||||
port should be returned, and the rules should be ingress/egress
|
||||
(but not both) and include the source/dest nsx logical port.
|
||||
"""
|
||||
# TODO(asarfaty) support only cases with port_id, as FWaaS v1 is no
|
||||
# longer supported
|
||||
extra_rules = []
|
||||
|
||||
# DHCP relay rules:
|
||||
|
@ -255,7 +255,7 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
|
||||
return translated_rules
|
||||
|
||||
def _get_port_translated_rules(self, project_id, router_id, neutron_net_id,
|
||||
firewall_group):
|
||||
firewall_group, plugin_rules):
|
||||
"""Return the list of translated FWaaS rules per port
|
||||
Add the egress/ingress rules of this port +
|
||||
default drop rules in each direction for this port.
|
||||
@ -272,6 +272,10 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
|
||||
project_id, router_id, net_group_id,
|
||||
firewall_group['egress_rule_list'], is_ingress=False))
|
||||
|
||||
# Add the per-port plugin rules
|
||||
if plugin_rules and isinstance(plugin_rules, list):
|
||||
port_rules.extend(plugin_rules)
|
||||
|
||||
# Add ingress/egress block rules for this port
|
||||
port_rules.extend([
|
||||
self.nsxpolicy.gateway_policy.build_entry(
|
||||
@ -323,10 +327,16 @@ class NsxpFwaasCallbacksV2(com_callbacks.NsxCommonv3FwaasCallbacksV2):
|
||||
fwg = self.get_port_fwg(context, port['id'])
|
||||
if fwg:
|
||||
router_with_fw = True
|
||||
|
||||
# Add plugin additional allow rules
|
||||
plugin_rules = self.core_plugin.get_extra_fw_rules(
|
||||
context, router_id, port['id'])
|
||||
|
||||
# Add the FWaaS rules for this port:ingress/egress firewall
|
||||
# rules + default ingress/egress drop rule for this port
|
||||
fw_rules.extend(self._get_port_translated_rules(
|
||||
project_id, router_id, port['network_id'], fwg))
|
||||
project_id, router_id, port['network_id'], fwg,
|
||||
plugin_rules))
|
||||
|
||||
# Add a default allow-all rule to all other traffic & ports
|
||||
fw_rules.append(self._get_default_backend_rule(router_id))
|
||||
|
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,
|
||||
}
|
||||
|
||||
AUTH_ALGORITHM_MAP_P = {
|
||||
'sha1': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA1,
|
||||
'sha256': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA256,
|
||||
'sha384': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA2_384,
|
||||
'sha512': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA2_512,
|
||||
}
|
||||
|
||||
PFS_MAP = {
|
||||
'group14': vpn_ipsec.DHGroupTypes.DH_GROUP_14
|
||||
}
|
||||
|
||||
PFS_MAP_P = {
|
||||
'group2': vpn_ipsec.DHGroupTypes.DH_GROUP_2,
|
||||
'group5': vpn_ipsec.DHGroupTypes.DH_GROUP_5,
|
||||
'group14': vpn_ipsec.DHGroupTypes.DH_GROUP_14,
|
||||
}
|
||||
|
||||
IKE_VERSION_MAP = {
|
||||
'v1': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V1,
|
||||
'v2': vpn_ipsec.IkeVersionTypes.IKE_VERSION_V2,
|
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 import constants
|
||||
from neutron_lib import context as n_context
|
||||
from neutron_lib import exceptions as nexception
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_vpnaas.services.vpn import service_drivers
|
||||
|
||||
from vmware_nsx.common import exceptions as nsx_exc
|
||||
from vmware_nsx.db import db
|
||||
from vmware_nsx.extensions import projectpluginmap
|
||||
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils
|
||||
from vmware_nsx.services.vpnaas.common_v3 import ipsec_driver as common_driver
|
||||
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
|
||||
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_validator
|
||||
from vmware_nsxlib.v3 import exceptions as nsx_lib_exc
|
||||
from vmware_nsxlib.v3 import nsx_constants as consts
|
||||
@ -40,50 +37,18 @@ LOG = logging.getLogger(__name__)
|
||||
IPSEC = 'ipsec'
|
||||
|
||||
|
||||
class RouterWithSNAT(nexception.BadRequest):
|
||||
message = _("Router %(router_id)s has a VPN service and cannot enable "
|
||||
"SNAT")
|
||||
|
||||
|
||||
class RouterWithOverlapNoSnat(nexception.BadRequest):
|
||||
message = _("Router %(router_id)s has a subnet overlapping with a VPN "
|
||||
"local subnet, and cannot disable SNAT")
|
||||
|
||||
|
||||
class RouterOverlapping(nexception.BadRequest):
|
||||
message = _("Router %(router_id)s interface is overlapping with a VPN "
|
||||
"local subnet and cannot be added")
|
||||
|
||||
|
||||
class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
class NSXv3IPsecVpnDriver(common_driver.NSXcommonIPsecVpnDriver):
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
self.vpn_plugin = service_plugin
|
||||
self._core_plugin = directory.get_plugin()
|
||||
if self._core_plugin.is_tvd_plugin():
|
||||
self._core_plugin = self._core_plugin.get_plugin_by_type(
|
||||
projectpluginmap.NsxPlugins.NSX_T)
|
||||
self._nsxlib = self._core_plugin.nsxlib
|
||||
self._nsx_vpn = self._nsxlib.vpn_ipsec
|
||||
validator = ipsec_validator.IPsecV3Validator(service_plugin)
|
||||
super(NSXv3IPsecVpnDriver, self).__init__(service_plugin, validator)
|
||||
self._nsxlib = self._core_plugin.nsxlib
|
||||
self._nsx_vpn = self._nsxlib.vpn_ipsec
|
||||
|
||||
registry.subscribe(
|
||||
self._delete_local_endpoint, resources.ROUTER_GATEWAY,
|
||||
events.AFTER_DELETE)
|
||||
|
||||
registry.subscribe(
|
||||
self._verify_overlap_subnet, resources.ROUTER_INTERFACE,
|
||||
events.BEFORE_CREATE)
|
||||
|
||||
@property
|
||||
def l3_plugin(self):
|
||||
return self._core_plugin
|
||||
|
||||
@property
|
||||
def service_type(self):
|
||||
return IPSEC
|
||||
|
||||
def _translate_cidr(self, cidr):
|
||||
return self._nsxlib.firewall_section.get_ip_cidr_reference(
|
||||
cidr,
|
||||
@ -170,18 +135,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
self._nsxlib.logical_router.update_advertisement_rules(
|
||||
logical_router_id, rules, name_prefix=rule_name_pref)
|
||||
|
||||
def _update_status(self, context, vpn_service_id, ipsec_site_conn_id,
|
||||
status, updated_pending_status=True):
|
||||
ipsec_site_conn = {'status': status,
|
||||
'updated_pending_status': updated_pending_status}
|
||||
vpn_status = {'id': vpn_service_id,
|
||||
'updated_pending_status': updated_pending_status,
|
||||
'status': status,
|
||||
'ipsec_site_connections': {ipsec_site_conn_id:
|
||||
ipsec_site_conn}}
|
||||
status_list = [vpn_status]
|
||||
self.service_plugin.update_status_by_agent(context, status_list)
|
||||
|
||||
def _nsx_tags(self, context, connection):
|
||||
return self._nsxlib.build_v3_tags_payload(
|
||||
connection, resource_type='os-vpn-connection-id',
|
||||
@ -251,9 +204,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
def _delete_ipsec_profile(self, ipsecprofile_id):
|
||||
self._nsx_vpn.tunnel_profile.delete(ipsecprofile_id)
|
||||
|
||||
def _get_dpd_profile_name(self, connection):
|
||||
return (connection['name'] or connection['id'])[:240] + '-dpd-profile'
|
||||
|
||||
def _create_dpd_profile(self, context, connection):
|
||||
dpd_info = connection['dpd']
|
||||
try:
|
||||
@ -378,14 +328,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
vpnservice['project_id'])
|
||||
return local_ep_id
|
||||
|
||||
def _find_vpn_service_port(self, context, router_id):
|
||||
"""Look for the neutron port created for the vpnservice of a router"""
|
||||
filters = {'device_id': ['router-' + router_id],
|
||||
'device_owner': [ipsec_utils.VPN_PORT_OWNER]}
|
||||
ports = self.l3_plugin.get_ports(context, filters=filters)
|
||||
if ports:
|
||||
return ports[0]
|
||||
|
||||
def _delete_local_endpoint_by_router(self, context, router_id):
|
||||
# delete the local endpoint from the NSX
|
||||
local_ep_id = self._search_local_endpint(router_id)
|
||||
@ -403,43 +345,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
ctx = n_context.get_admin_context()
|
||||
self._delete_local_endpoint_by_router(ctx, router_id)
|
||||
|
||||
def _check_subnets_overlap_with_all_conns(self, context, subnets):
|
||||
# find all vpn services with connections
|
||||
filters = {'status': [constants.ACTIVE]}
|
||||
connections = self.vpn_plugin.get_ipsec_site_connections(
|
||||
context, filters=filters)
|
||||
for conn in connections:
|
||||
srv_id = conn.get('vpnservice_id')
|
||||
srv = self.vpn_plugin._get_vpnservice(context, srv_id)
|
||||
srv_subnet = self.l3_plugin.get_subnet(
|
||||
context, srv['subnet_id'])
|
||||
if netaddr.IPSet(subnets) & netaddr.IPSet([srv_subnet['cidr']]):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _verify_overlap_subnet(self, resource, event, trigger, **kwargs):
|
||||
"""Upon router interface creation validation overlapping with vpn"""
|
||||
router_db = kwargs.get('router_db')
|
||||
port = kwargs.get('port')
|
||||
if not port or not router_db:
|
||||
LOG.warning("NSX V3 VPNaaS ROUTER_INTERFACE BEFORE_CRAETE "
|
||||
"callback didn't get all the relevant information")
|
||||
return
|
||||
|
||||
if router_db.enable_snat:
|
||||
# checking only no-snat routers
|
||||
return
|
||||
|
||||
admin_con = n_context.get_admin_context()
|
||||
subnet_id = port['fixed_ips'][0].get('subnet_id')
|
||||
if subnet_id:
|
||||
subnet = self._core_plugin.get_subnet(admin_con, subnet_id)
|
||||
# find all vpn services with connections
|
||||
if not self._check_subnets_overlap_with_all_conns(
|
||||
admin_con, [subnet['cidr']]):
|
||||
raise RouterOverlapping(router_id=kwargs.get('router_id'))
|
||||
|
||||
def validate_router_gw_info(self, context, router_id, gw_info):
|
||||
"""Upon router gw update - verify no-snat"""
|
||||
# check if this router has a vpn service
|
||||
@ -454,7 +359,7 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
# do not allow enable-snat
|
||||
if (gw_info and
|
||||
gw_info.get('enable_snat', cfg.CONF.enable_snat_by_default)):
|
||||
raise RouterWithSNAT(router_id=router_id)
|
||||
raise common_driver.RouterWithSNAT(router_id=router_id)
|
||||
else:
|
||||
# if this is a non-vpn router. if snat was disabled, should check
|
||||
# there is no overlapping with vpn connections
|
||||
@ -467,7 +372,8 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
# find all vpn services with connections
|
||||
if not self._check_subnets_overlap_with_all_conns(
|
||||
admin_con, subnets):
|
||||
raise RouterWithOverlapNoSnat(router_id=router_id)
|
||||
raise common_driver.RouterWithOverlapNoSnat(
|
||||
router_id=router_id)
|
||||
|
||||
def _get_session_rules(self, context, connection, vpnservice):
|
||||
# TODO(asarfaty): support vpn-endpoint-groups too
|
||||
@ -701,16 +607,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
|
||||
return service['id']
|
||||
|
||||
def _get_tier0_uuid(self, context, vpnservice):
|
||||
router_id = vpnservice['router_id']
|
||||
router_db = self._core_plugin._get_router(context, router_id)
|
||||
return self._core_plugin._get_tier0_uuid_by_router(context, router_db)
|
||||
|
||||
def _get_router_ext_gw(self, context, router_id):
|
||||
router_db = self._core_plugin.get_router(context, router_id)
|
||||
gw = router_db['external_gateway_info']
|
||||
return gw['external_fixed_ips'][0]["ip_address"]
|
||||
|
||||
def _find_vpn_service(self, tier0_uuid, validate=True):
|
||||
# find the service for the tier0 router in the NSX.
|
||||
# Note(asarfaty) we expect only a small number of services
|
||||
@ -774,34 +670,6 @@ class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
tier0_uuid = self._get_tier0_uuid(context, vpnservice)
|
||||
return self._find_vpn_service(tier0_uuid, validate=False)
|
||||
|
||||
def _get_service_local_address(self, context, vpnservice):
|
||||
"""Find/Allocate a port on the external network
|
||||
|
||||
to save the ip to be used as the local ip of this service
|
||||
"""
|
||||
router_id = vpnservice['router_id']
|
||||
# check if this router already have an IP
|
||||
port = self._find_vpn_service_port(context, router_id)
|
||||
if not port:
|
||||
# create a new port, on the external network of the router
|
||||
# Note(asarfaty): using a unique device owner and device id to
|
||||
# make sure tis port will be ignored in certain queries
|
||||
ext_net = vpnservice['router']['gw_port']['network_id']
|
||||
port_data = {
|
||||
'port': {
|
||||
'network_id': ext_net,
|
||||
'name': 'VPN local address port',
|
||||
'admin_state_up': True,
|
||||
'device_id': 'router-' + router_id,
|
||||
'device_owner': ipsec_utils.VPN_PORT_OWNER,
|
||||
'fixed_ips': constants.ATTR_NOT_SPECIFIED,
|
||||
'mac_address': constants.ATTR_NOT_SPECIFIED,
|
||||
'port_security_enabled': False,
|
||||
'tenant_id': vpnservice['tenant_id']}}
|
||||
port = self.l3_plugin.base_create_port(context, port_data)
|
||||
# return the port ip as the local address
|
||||
return port['fixed_ips'][0]['ip_address']
|
||||
|
||||
def create_vpnservice(self, context, vpnservice):
|
||||
#TODO(asarfaty) support vpn-endpoint-group-create for local & peer
|
||||
# cidrs too
|
||||
|
@ -13,37 +13,25 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import netaddr
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from neutron_lib import constants
|
||||
from neutron_vpnaas.db.vpn import vpn_validator
|
||||
|
||||
from vmware_nsx._i18n import _
|
||||
from vmware_nsx.common import exceptions as nsx_exc
|
||||
from vmware_nsx.extensions import projectpluginmap
|
||||
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_utils
|
||||
from vmware_nsx.services.vpnaas.common_v3 import ipsec_utils
|
||||
from vmware_nsx.services.vpnaas.common_v3 import ipsec_validator
|
||||
from vmware_nsxlib.v3 import nsx_constants as consts
|
||||
from vmware_nsxlib.v3 import vpn_ipsec
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
|
||||
|
||||
class IPsecV3Validator(ipsec_validator.IPsecCommonValidator):
|
||||
"""Validator methods for Vmware NSX-V3 VPN support"""
|
||||
def __init__(self, service_plugin):
|
||||
super(IPsecV3Validator, self).__init__()
|
||||
self.vpn_plugin = service_plugin
|
||||
super(IPsecV3Validator, self).__init__(service_plugin)
|
||||
|
||||
self._core_plugin = self.core_plugin
|
||||
if self._core_plugin.is_tvd_plugin():
|
||||
self._core_plugin = self._core_plugin.get_plugin_by_type(
|
||||
projectpluginmap.NsxPlugins.NSX_T)
|
||||
self.nsxlib = self._core_plugin.nsxlib
|
||||
|
||||
self.check_backend_version()
|
||||
@property
|
||||
def nsxlib(self):
|
||||
return self._core_plugin.nsxlib
|
||||
|
||||
def check_backend_version(self):
|
||||
if not self.nsxlib.feature_supported(consts.FEATURE_IPSEC_VPN):
|
||||
@ -61,260 +49,15 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
|
||||
"(version %s)") % self.nsxlib.get_version())
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _validate_policy_lifetime(self, policy_info, policy_type):
|
||||
"""NSX supports only units=seconds"""
|
||||
lifetime = policy_info.get('lifetime')
|
||||
if not lifetime:
|
||||
return
|
||||
if lifetime.get('units') != 'seconds':
|
||||
msg = _("Unsupported policy lifetime %(val)s in %(pol)s policy. "
|
||||
"Only seconds lifetime is supported.") % {
|
||||
'val': lifetime, 'pol': policy_type}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
value = lifetime.get('value')
|
||||
if policy_type == 'IKE':
|
||||
limits = vpn_ipsec.IkeSALifetimeLimits
|
||||
else:
|
||||
limits = vpn_ipsec.IPsecSALifetimeLimits
|
||||
if (value and (value < limits.SA_LIFETIME_MIN or
|
||||
value > limits.SA_LIFETIME_MAX)):
|
||||
msg = _("Unsupported policy lifetime %(value)s in %(pol)s policy. "
|
||||
"Value range is [%(min)s-%(max)s].") % {
|
||||
'value': value,
|
||||
'pol': policy_type,
|
||||
'min': limits.SA_LIFETIME_MIN,
|
||||
'max': limits.SA_LIFETIME_MAX}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
@property
|
||||
def auth_algorithm_map(self):
|
||||
return ipsec_utils.AUTH_ALGORITHM_MAP
|
||||
|
||||
def _validate_policy_auth_algorithm(self, policy_info, policy_type):
|
||||
"""NSX supports only SHA1 and SHA256"""
|
||||
auth = policy_info.get('auth_algorithm')
|
||||
if auth and auth not in ipsec_utils.AUTH_ALGORITHM_MAP:
|
||||
msg = _("Unsupported auth_algorithm: %(algo)s in %(pol)s policy. "
|
||||
"Please select one of the following supported algorithms: "
|
||||
"%(supported_algos)s") % {
|
||||
'pol': policy_type,
|
||||
'algo': auth,
|
||||
'supported_algos':
|
||||
ipsec_utils.AUTH_ALGORITHM_MAP.keys()}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
@property
|
||||
def pfs_map(self):
|
||||
return ipsec_utils.PFS_MAP
|
||||
|
||||
def _validate_policy_encryption_algorithm(self, policy_info, policy_type):
|
||||
encryption = policy_info.get('encryption_algorithm')
|
||||
if (encryption and
|
||||
encryption not in ipsec_utils.ENCRYPTION_ALGORITHM_MAP):
|
||||
msg = _("Unsupported encryption_algorithm: %(algo)s in %(pol)s "
|
||||
"policy. Please select one of the following supported "
|
||||
"algorithms: %(supported_algos)s") % {
|
||||
'algo': encryption,
|
||||
'pol': policy_type,
|
||||
'supported_algos':
|
||||
ipsec_utils.ENCRYPTION_ALGORITHM_MAP.keys()}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _validate_policy_pfs(self, policy_info, policy_type):
|
||||
pfs = policy_info.get('pfs')
|
||||
if pfs and pfs not in ipsec_utils.PFS_MAP:
|
||||
msg = _("Unsupported pfs: %(pfs)s in %(pol)s policy. Please "
|
||||
"select one of the following pfs: "
|
||||
"%(supported_pfs)s") % {
|
||||
'pfs': pfs,
|
||||
'pol': policy_type,
|
||||
'supported_pfs':
|
||||
ipsec_utils.PFS_MAP.keys()}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _validate_dpd(self, connection):
|
||||
dpd_info = connection.get('dpd')
|
||||
if not dpd_info:
|
||||
return
|
||||
action = dpd_info.get('action')
|
||||
if action not in ipsec_utils.DPD_ACTION_MAP.keys():
|
||||
msg = _("Unsupported DPD action: %(action)s! Currently only "
|
||||
"%(supported)s is supported.") % {
|
||||
'action': action,
|
||||
'supported': ipsec_utils.DPD_ACTION_MAP.keys()}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
timeout = dpd_info.get('timeout')
|
||||
if (timeout < vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MIN or
|
||||
timeout > vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MAX):
|
||||
msg = _("Unsupported DPD timeout: %(timeout)s. Value range is "
|
||||
"[%(min)s-%(max)s].") % {
|
||||
'timeout': timeout,
|
||||
'min': vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MIN,
|
||||
'max': vpn_ipsec.DpdProfileTimeoutLimits.DPD_TIMEOUT_MAX}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _validate_psk(self, connection):
|
||||
if 'psk' in connection and not connection['psk']:
|
||||
msg = _("'psk' cannot be empty or null when authentication "
|
||||
"mode is psk")
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _check_policy_rules_overlap(self, context, ipsec_site_conn):
|
||||
"""validate no overlapping policy rules
|
||||
|
||||
The nsx does not support overlapping policy rules cross
|
||||
all tenants, and tier0 routers
|
||||
"""
|
||||
connections = self.vpn_plugin.get_ipsec_site_connections(
|
||||
context.elevated())
|
||||
if not connections:
|
||||
return
|
||||
vpnservice_id = ipsec_site_conn.get('vpnservice_id')
|
||||
vpnservice = self.vpn_plugin._get_vpnservice(context, vpnservice_id)
|
||||
local_cidrs = [vpnservice['subnet']['cidr']]
|
||||
peer_cidrs = ipsec_site_conn['peer_cidrs']
|
||||
for conn in connections:
|
||||
# skip this connection and connections in non active state
|
||||
if (conn['id'] == ipsec_site_conn.get('id') or
|
||||
conn['status'] != constants.ACTIVE):
|
||||
continue
|
||||
# TODO(asarfaty): support peer groups too
|
||||
# check if it overlaps with the peer cidrs
|
||||
conn_peer_cidrs = conn['peer_cidrs']
|
||||
if netaddr.IPSet(conn_peer_cidrs) & netaddr.IPSet(peer_cidrs):
|
||||
# check if the local cidr also overlaps
|
||||
con_service_id = conn.get('vpnservice_id')
|
||||
con_service = self.vpn_plugin._get_vpnservice(
|
||||
context.elevated(), con_service_id)
|
||||
conn_local_cidr = [con_service['subnet']['cidr']]
|
||||
if netaddr.IPSet(conn_local_cidr) & netaddr.IPSet(local_cidrs):
|
||||
msg = (_("Cannot create a connection with overlapping "
|
||||
"local and peer cidrs (%(local)s and %(peer)s) "
|
||||
"as connection %(id)s") % {'local': local_cidrs,
|
||||
'peer': peer_cidrs,
|
||||
'id': conn['id']})
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _check_unique_addresses(self, context, ipsec_site_conn):
|
||||
"""Validate no repeating local & peer addresses (of all tenants)
|
||||
|
||||
The nsx does not support it cross all tenants, and tier0 routers
|
||||
"""
|
||||
vpnservice_id = ipsec_site_conn.get('vpnservice_id')
|
||||
local_addr = self._get_service_local_address(context, vpnservice_id)
|
||||
peer_address = ipsec_site_conn.get('peer_address')
|
||||
filters = {'peer_address': [peer_address]}
|
||||
connections = self.vpn_plugin.get_ipsec_site_connections(
|
||||
context.elevated(), filters=filters)
|
||||
for conn in connections:
|
||||
# skip this connection and connections in non active state
|
||||
if (conn['id'] == ipsec_site_conn.get('id') or
|
||||
conn['status'] != constants.ACTIVE):
|
||||
continue
|
||||
# this connection has the same peer addr as ours.
|
||||
# check the service local address
|
||||
srv_id = conn.get('vpnservice_id')
|
||||
srv_local = self._get_service_local_address(
|
||||
context.elevated(), srv_id)
|
||||
if srv_local == local_addr:
|
||||
msg = (_("Cannot create another connection with the same "
|
||||
"local address %(local)s and peer address %(peer)s "
|
||||
"as connection %(id)s") % {'local': local_addr,
|
||||
'peer': peer_address,
|
||||
'id': conn['id']})
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _check_advertisment_overlap(self, context, ipsec_site_conn):
|
||||
"""Validate there is no overlapping advertisement of networks
|
||||
|
||||
The plugin advertise all no-snat routers networks + vpn local
|
||||
networks.
|
||||
The NSX does not allow different Tier1 router to advertise the
|
||||
same subnets
|
||||
"""
|
||||
admin_con = context.elevated()
|
||||
srv_id = ipsec_site_conn.get('vpnservice_id')
|
||||
srv = self.vpn_plugin._get_vpnservice(admin_con, srv_id)
|
||||
this_router = srv['router_id']
|
||||
this_cidr = srv['subnet']['cidr']
|
||||
|
||||
# get all subnets of no-snat routers
|
||||
all_routers = self._core_plugin.get_routers(admin_con)
|
||||
nosnat_routers = [rtr for rtr in all_routers
|
||||
if (rtr['id'] != this_router and
|
||||
rtr.get('external_gateway_info') and
|
||||
not rtr['external_gateway_info'].get(
|
||||
'enable_snat',
|
||||
cfg.CONF.enable_snat_by_default))]
|
||||
for rtr in nosnat_routers:
|
||||
if rtr['id'] == this_router:
|
||||
continue
|
||||
# go over the subnets of this router
|
||||
subnets = self._core_plugin._find_router_subnets_cidrs(
|
||||
admin_con, rtr['id'])
|
||||
if subnets and netaddr.IPSet(subnets) & netaddr.IPSet([this_cidr]):
|
||||
msg = (_("Cannot create connection with overlapping local "
|
||||
"cidrs %(local)s which was already advertised by "
|
||||
"no-snat router %(rtr)s") % {'local': subnets,
|
||||
'rtr': rtr['id']})
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
# add all vpn local subnets
|
||||
connections = self.vpn_plugin.get_ipsec_site_connections(admin_con)
|
||||
for conn in connections:
|
||||
# skip this connection and connections in non active state
|
||||
if (conn['id'] == ipsec_site_conn.get('id') or
|
||||
conn['status'] != constants.ACTIVE):
|
||||
continue
|
||||
# check the service local address
|
||||
conn_srv_id = conn.get('vpnservice_id')
|
||||
conn_srv = self.vpn_plugin._get_vpnservice(admin_con, conn_srv_id)
|
||||
if conn_srv['router_id'] == this_router:
|
||||
continue
|
||||
conn_cidr = conn_srv['subnet']['cidr']
|
||||
if netaddr.IPSet([conn_cidr]) & netaddr.IPSet([this_cidr]):
|
||||
msg = (_("Cannot create connection with overlapping local "
|
||||
"cidr %(local)s which was already advertised by "
|
||||
"router %(rtr)s and connection %(conn)s") % {
|
||||
'local': conn_cidr,
|
||||
'rtr': conn_srv['router_id'],
|
||||
'conn': conn['id']})
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def validate_ipsec_site_connection(self, context, ipsec_site_conn):
|
||||
"""Called upon create/update of a connection"""
|
||||
|
||||
self._validate_backend_version()
|
||||
|
||||
self._validate_dpd(ipsec_site_conn)
|
||||
self._validate_psk(ipsec_site_conn)
|
||||
|
||||
ike_policy_id = ipsec_site_conn.get('ikepolicy_id')
|
||||
if ike_policy_id:
|
||||
ikepolicy = self.vpn_plugin.get_ikepolicy(context,
|
||||
ike_policy_id)
|
||||
self.validate_ike_policy(context, ikepolicy)
|
||||
|
||||
ipsec_policy_id = ipsec_site_conn.get('ipsecpolicy_id')
|
||||
if ipsec_policy_id:
|
||||
ipsecpolicy = self.vpn_plugin.get_ipsecpolicy(context,
|
||||
ipsec_policy_id)
|
||||
self.validate_ipsec_policy(context, ipsecpolicy)
|
||||
|
||||
if ipsec_site_conn.get('vpnservice_id'):
|
||||
self._check_advertisment_overlap(context, ipsec_site_conn)
|
||||
self._check_unique_addresses(context, ipsec_site_conn)
|
||||
self._check_policy_rules_overlap(context, ipsec_site_conn)
|
||||
|
||||
#TODO(asarfaty): IPv6 is not yet supported. add validation
|
||||
|
||||
def _get_service_local_address(self, context, vpnservice_id):
|
||||
"""The local address of the service is assigned upon creation
|
||||
|
||||
From the attached external network pool
|
||||
"""
|
||||
vpnservice = self.vpn_plugin._get_vpnservice(context,
|
||||
vpnservice_id)
|
||||
return vpnservice['external_v4_ip']
|
||||
|
||||
def _validate_router(self, context, router_id):
|
||||
# Verify that the router gw network is connected to an active-standby
|
||||
# Tier0 router
|
||||
router_db = self._core_plugin._get_router(context, router_id)
|
||||
tier0_uuid = self._core_plugin._get_tier0_uuid_by_router(context,
|
||||
router_db)
|
||||
def _validate_t0_ha_mode(self, tier0_uuid):
|
||||
# TODO(asarfaty): cache this result
|
||||
tier0_router = self.nsxlib.logical_router.get(tier0_uuid)
|
||||
if (not tier0_router or
|
||||
@ -323,69 +66,11 @@ class IPsecV3Validator(vpn_validator.VpnReferenceValidator):
|
||||
"with ACTIVE_STANDBY HA mode")
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def _validate_router(self, context, router_id):
|
||||
super(IPsecV3Validator, self)._validate_router(context, router_id)
|
||||
|
||||
# Verify that this is a no-snat router
|
||||
router_db = self._core_plugin._get_router(context, router_id)
|
||||
if router_db.enable_snat:
|
||||
msg = _("VPN is supported only for routers with disabled SNAT")
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def validate_vpnservice(self, context, vpnservice):
|
||||
"""Called upon create/update of a service"""
|
||||
|
||||
self._validate_backend_version()
|
||||
|
||||
# Call general validations
|
||||
super(IPsecV3Validator, self).validate_vpnservice(
|
||||
context, vpnservice)
|
||||
|
||||
# Call specific NSX validations
|
||||
self._validate_router(context, vpnservice['router_id'])
|
||||
|
||||
if not vpnservice['subnet_id']:
|
||||
# we currently do not support multiple subnets so a subnet must
|
||||
# be defined
|
||||
msg = _("Subnet must be defined in a service")
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
#TODO(asarfaty): IPv6 is not yet supported. add validation
|
||||
|
||||
def validate_ipsec_policy(self, context, ipsec_policy):
|
||||
# Call general validations
|
||||
super(IPsecV3Validator, self).validate_ipsec_policy(
|
||||
context, ipsec_policy)
|
||||
|
||||
# Call specific NSX validations
|
||||
self._validate_policy_lifetime(ipsec_policy, "IPSec")
|
||||
self._validate_policy_auth_algorithm(ipsec_policy, "IPSec")
|
||||
self._validate_policy_encryption_algorithm(ipsec_policy, "IPSec")
|
||||
self._validate_policy_pfs(ipsec_policy, "IPSec")
|
||||
|
||||
# Ensure IPSec policy encap mode is tunnel
|
||||
mode = ipsec_policy.get('encapsulation_mode')
|
||||
if mode and mode not in ipsec_utils.ENCAPSULATION_MODE_MAP.keys():
|
||||
msg = _("Unsupported encapsulation mode: %s. Only 'tunnel' mode "
|
||||
"is supported.") % mode
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
# Ensure IPSec policy transform protocol is esp
|
||||
prot = ipsec_policy.get('transform_protocol')
|
||||
if prot and prot not in ipsec_utils.TRANSFORM_PROTOCOL_MAP.keys():
|
||||
msg = _("Unsupported transform protocol: %s. Only 'esp' protocol "
|
||||
"is supported.") % prot
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
def validate_ike_policy(self, context, ike_policy):
|
||||
# Call general validations
|
||||
super(IPsecV3Validator, self).validate_ike_policy(
|
||||
context, ike_policy)
|
||||
|
||||
# Call specific NSX validations
|
||||
self._validate_policy_lifetime(ike_policy, "IKE")
|
||||
self._validate_policy_auth_algorithm(ike_policy, "IKE")
|
||||
self._validate_policy_encryption_algorithm(ike_policy, "IKE")
|
||||
self._validate_policy_pfs(ike_policy, "IKE")
|
||||
|
||||
# 'aggressive' phase1-negotiation-mode is not supported
|
||||
if ike_policy.get('phase1-negotiation-mode', 'main') != 'main':
|
||||
msg = _("Unsupported phase1-negotiation-mode: %s! Only 'main' is "
|
||||
"supported.") % ike_policy['phase1-negotiation-mode']
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
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