Merge "NSX|V3: VPNaaS support"
This commit is contained in:
commit
bad6dc68a9
@ -211,6 +211,16 @@ Configure the service provider::
|
||||
[service_providers]
|
||||
service_provider = LOADBALANCERV2:VMWareEdge:neutron_lbaas.drivers.vmware.edge_driver_v2.EdgeLoadBalancerDriverV2:default
|
||||
|
||||
Neutron VPNaaS
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
Add neutron-vpnaas repo as an external repository and configure following flags in ``local.conf``::
|
||||
|
||||
[[local|localrc]]
|
||||
enable_plugin neutron-vpnaas https://git.openstack.org/openstack/neutron-vpnaas
|
||||
NEUTRON_VPNAAS_SERVICE_PROVIDER=VPN:vmware:vmware_nsx.services.vpnaas.nsxv3.ipsec_driver.NSXv3IPsecVpnDriver:default
|
||||
|
||||
|
||||
NSX-TVD
|
||||
-------
|
||||
|
||||
|
6
releasenotes/notes/nsxv3-vpnaas-0b02762ff4b83904.yaml
Normal file
6
releasenotes/notes/nsxv3-vpnaas-0b02762ff4b83904.yaml
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
prelude: >
|
||||
Support VPN-as-a-Service for VPN IPSEC in NSXv3 plugin.
|
||||
features:
|
||||
- |
|
||||
NSXv3 plugin now supports VPN SEC through VPNaaS plugin.
|
@ -692,3 +692,31 @@ def get_project_plugin_mapping(session, project):
|
||||
|
||||
def get_project_plugin_mappings(session):
|
||||
return session.query(nsx_models.NsxProjectPluginMapping).all()
|
||||
|
||||
|
||||
def add_nsx_vpn_connection_mapping(session, neutron_id, session_id,
|
||||
dpd_profile_id, ike_profile_id,
|
||||
ipsec_profile_id, peer_ep_id):
|
||||
with session.begin(subtransactions=True):
|
||||
mapping = nsx_models.NsxVpnConnectionMapping(
|
||||
neutron_id=neutron_id,
|
||||
session_id=session_id,
|
||||
dpd_profile_id=dpd_profile_id,
|
||||
ike_profile_id=ike_profile_id,
|
||||
ipsec_profile_id=ipsec_profile_id,
|
||||
peer_ep_id=peer_ep_id)
|
||||
session.add(mapping)
|
||||
return mapping
|
||||
|
||||
|
||||
def get_nsx_vpn_connection_mapping(session, neutron_id):
|
||||
try:
|
||||
return (session.query(nsx_models.NsxVpnConnectionMapping).
|
||||
filter_by(neutron_id=neutron_id).one())
|
||||
except exc.NoResultFound:
|
||||
return
|
||||
|
||||
|
||||
def delete_nsx_vpn_connection_mapping(session, neutron_id):
|
||||
return (session.query(nsx_models.NsxVpnConnectionMapping).
|
||||
filter_by(neutron_id=neutron_id).delete())
|
||||
|
@ -1 +1 @@
|
||||
9799427fc0e1
|
||||
0dbeda408e41
|
||||
|
@ -0,0 +1,43 @@
|
||||
# Copyright 2017 VMware, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""nsxv3_vpn_mapping
|
||||
|
||||
Revision ID: 0dbeda408e41
|
||||
Revises: 9799427fc0e1
|
||||
Create Date: 2017-11-26 12:27:40.846088
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0dbeda408e41'
|
||||
down_revision = '9799427fc0e1'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
|
||||
op.create_table(
|
||||
'neutron_nsx_vpn_connection_mappings',
|
||||
sa.Column('neutron_id', sa.String(36), nullable=False),
|
||||
sa.Column('session_id', sa.String(36), nullable=False),
|
||||
sa.Column('dpd_profile_id', sa.String(36), nullable=False),
|
||||
sa.Column('ike_profile_id', sa.String(36), nullable=False),
|
||||
sa.Column('ipsec_profile_id', sa.String(36), nullable=False),
|
||||
sa.Column('peer_ep_id', sa.String(36), nullable=False),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.PrimaryKeyConstraint('neutron_id'))
|
@ -488,3 +488,14 @@ class NsxProjectPluginMapping(model_base.BASEV2, models.TimestampMixin):
|
||||
__tablename__ = 'nsx_project_plugin_mappings'
|
||||
project = sa.Column(sa.String(36), primary_key=True)
|
||||
plugin = sa.Column(sa.Enum('dvs', 'nsx-v', 'nsx-t'), nullable=False)
|
||||
|
||||
|
||||
class NsxVpnConnectionMapping(model_base.BASEV2, models.TimestampMixin):
|
||||
"""Stores the mapping between VPNaaS connections and NSX objects"""
|
||||
__tablename__ = 'neutron_nsx_vpn_connection_mappings'
|
||||
neutron_id = sa.Column(sa.String(36), primary_key=True)
|
||||
session_id = sa.Column(sa.String(36), nullable=False)
|
||||
dpd_profile_id = sa.Column(sa.String(36), nullable=False)
|
||||
ike_profile_id = sa.Column(sa.String(36), nullable=False)
|
||||
ipsec_profile_id = sa.Column(sa.String(36), nullable=False)
|
||||
peer_ep_id = sa.Column(sa.String(36), nullable=False)
|
||||
|
@ -24,6 +24,8 @@ from neutron_lib.api.validators import availability_zone as az_validator
|
||||
from neutron_lib.exceptions import allowedaddresspairs as addr_exc
|
||||
from neutron_lib.exceptions import l3 as l3_exc
|
||||
from neutron_lib.exceptions import port_security as psec_exc
|
||||
from neutron_lib.plugins import constants as plugin_const
|
||||
from neutron_lib.plugins import directory
|
||||
from neutron_lib.services.qos import constants as qos_consts
|
||||
|
||||
from neutron.api.rpc.agentnotifiers import dhcp_rpc_agent_api
|
||||
@ -3125,7 +3127,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
|
||||
return (ipaddress, netmask, nexthop)
|
||||
|
||||
def _get_tier0_uuid_by_net(self, context, network_id):
|
||||
def _get_tier0_uuid_by_router(self, context, router):
|
||||
network_id = router.gw_port_id and router.gw_port.network_id
|
||||
if not network_id:
|
||||
return
|
||||
network = self.get_network(context, network_id)
|
||||
@ -3136,10 +3139,8 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
|
||||
def _update_router_gw_info(self, context, router_id, info):
|
||||
router = self._get_router(context, router_id)
|
||||
org_ext_net_id = router.gw_port_id and router.gw_port.network_id
|
||||
org_tier0_uuid = self._get_tier0_uuid_by_net(context, org_ext_net_id)
|
||||
org_tier0_uuid = self._get_tier0_uuid_by_router(context, router)
|
||||
org_enable_snat = router.enable_snat
|
||||
new_ext_net_id = info and info.get('network_id')
|
||||
orgaddr, orgmask, _orgnexthop = (
|
||||
self._get_external_attachment_info(
|
||||
context, router))
|
||||
@ -3159,8 +3160,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
super(NsxV3Plugin, self)._update_router_gw_info(
|
||||
context, router_id, info, router=router)
|
||||
|
||||
new_ext_net_id = router.gw_port_id and router.gw_port.network_id
|
||||
new_tier0_uuid = self._get_tier0_uuid_by_net(context, new_ext_net_id)
|
||||
new_tier0_uuid = self._get_tier0_uuid_by_router(context, router)
|
||||
new_enable_snat = router.enable_snat
|
||||
newaddr, newmask, _newnexthop = (
|
||||
self._get_external_attachment_info(
|
||||
@ -3600,6 +3600,7 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
(but not both) and include the source/dest nsx logical port.
|
||||
"""
|
||||
extra_rules = []
|
||||
|
||||
# DHCP relay rules:
|
||||
# get the list of relevant relay servers
|
||||
elv_ctx = context.elevated()
|
||||
@ -3651,6 +3652,16 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
|
||||
'services': dhcp_services,
|
||||
'direction': 'OUT'})
|
||||
|
||||
# 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(
|
||||
context, router_id))
|
||||
if vpn_rules:
|
||||
extra_rules.extend(vpn_rules)
|
||||
|
||||
return extra_rules
|
||||
|
||||
def _get_ports_and_address_groups(self, context, router_id, network_id,
|
||||
|
0
vmware_nsx/services/vpnaas/nsxv3/__init__.py
Normal file
0
vmware_nsx/services/vpnaas/nsxv3/__init__.py
Normal file
645
vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py
Normal file
645
vmware_nsx/services/vpnaas/nsxv3/ipsec_driver.py
Normal file
@ -0,0 +1,645 @@
|
||||
# Copyright 2017 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 oslo_utils import excutils
|
||||
|
||||
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.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.services.vpnaas.nsxv3 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
|
||||
from vmware_nsxlib.v3 import vpn_ipsec
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
IPSEC = 'ipsec'
|
||||
|
||||
|
||||
class NSXv3IPsecVpnDriver(service_drivers.VpnDriver):
|
||||
|
||||
def __init__(self, service_plugin):
|
||||
self.vpn_plugin = service_plugin
|
||||
self._core_plugin = directory.get_plugin()
|
||||
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)
|
||||
|
||||
registry.subscribe(
|
||||
self._delete_local_endpoint, resources.ROUTER_GATEWAY,
|
||||
events.AFTER_DELETE)
|
||||
|
||||
@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,
|
||||
consts.IPV6 if netaddr.valid_ipv6(cidr) else consts.IPV4)
|
||||
|
||||
def _translate_addresses_to_target(self, cidrs):
|
||||
return [self._translate_cidr(ip) for ip in cidrs]
|
||||
|
||||
def _generate_ipsecvpn_firewall_rules(self, context, router_id):
|
||||
"""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 = self.l3_plugin.get_subnet(
|
||||
context.elevated(), srv['subnet_id'])
|
||||
local_cidrs = [subnet['cidr']]
|
||||
# get all the active connections of this service
|
||||
filters = {'vpnservice_id': [srv['id']],
|
||||
'status': [constants.ACTIVE]}
|
||||
connections = self.vpn_plugin.get_ipsec_site_connections(
|
||||
context.elevated(), filters=filters)
|
||||
for conn in connections:
|
||||
peer_cidrs = conn['peer_cidrs']
|
||||
fw_rules.append({
|
||||
'display_name': 'VPN connection ' + conn['id'],
|
||||
'action': consts.FW_ACTION_ALLOW,
|
||||
'sources': self._translate_addresses_to_target(
|
||||
peer_cidrs),
|
||||
'destinations': self._translate_addresses_to_target(
|
||||
local_cidrs)})
|
||||
|
||||
return fw_rules
|
||||
|
||||
def _update_firewall_rules(self, context, vpnservice):
|
||||
LOG.debug("Updating vpn firewall rules for router %s",
|
||||
vpnservice['router_id'])
|
||||
self._core_plugin.update_router_firewall(
|
||||
context, vpnservice['router_id'])
|
||||
|
||||
def _update_router_advertisement(self, context, vpnservice):
|
||||
LOG.debug("Updating router advertisement rules for router %s",
|
||||
vpnservice['router_id'])
|
||||
|
||||
router_id = vpnservice['router_id']
|
||||
# skip no-snat router as it is already advertised,
|
||||
# and router with no gw
|
||||
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
|
||||
|
||||
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)
|
||||
for srv in services:
|
||||
# use only services with active connections
|
||||
filters = {'vpnservice_id': [srv['id']],
|
||||
'status': [constants.ACTIVE]}
|
||||
connections = self.vpn_plugin.get_ipsec_site_connections(
|
||||
context.elevated(), filters=filters)
|
||||
if not connections:
|
||||
continue
|
||||
subnet = self.l3_plugin.get_subnet(
|
||||
context.elevated(), srv['subnet_id'])
|
||||
rules.append({
|
||||
'display_name': 'VPN advertisement service ' + srv['id'],
|
||||
'action': consts.FW_ACTION_ALLOW,
|
||||
'networks': [subnet['cidr']]})
|
||||
|
||||
logical_router_id = db.get_nsx_router_id(context.session, router_id)
|
||||
self._nsxlib.logical_router.update_advertisement_rules(
|
||||
logical_router_id, rules)
|
||||
|
||||
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',
|
||||
project_name=context.tenant_name)
|
||||
|
||||
def _nsx_tags_for_reused(self):
|
||||
# Service & Local endpoint can be reused cross tenants,
|
||||
# so we do not add the tenant/object id.
|
||||
return self._nsxlib.build_v3_api_version_tag()
|
||||
|
||||
def _create_ike_profile(self, context, connection):
|
||||
"""Create an ike profile for a connection"""
|
||||
# Note(asarfaty) the NSX profile can be reused, so we can consider
|
||||
# creating it only once in the future, and keeping a use-count for it.
|
||||
# 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)
|
||||
try:
|
||||
profile = self._nsx_vpn.ike_profile.create(
|
||||
ikepolicy['name'],
|
||||
description=ikepolicy['description'],
|
||||
encryption_algorithm=ipsec_utils.ENCRYPTION_ALGORITHM_MAP[
|
||||
ikepolicy['encryption_algorithm']],
|
||||
digest_algorithm=ipsec_utils.AUTH_ALGORITHM_MAP[
|
||||
ikepolicy['auth_algorithm']],
|
||||
ike_version=ipsec_utils.IKE_VERSION_MAP[
|
||||
ikepolicy['ike_version']],
|
||||
dh_group=ipsec_utils.PFS_MAP[ikepolicy['pfs']],
|
||||
pfs=True,
|
||||
sa_life_time=ikepolicy['lifetime']['value'],
|
||||
tags=self._nsx_tags(context, connection))
|
||||
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):
|
||||
self._nsx_vpn.ike_profile.delete(ikeprofile_id)
|
||||
|
||||
def _create_ipsec_profile(self, context, connection):
|
||||
"""Create an ipsec profile for a connection"""
|
||||
# Note(asarfaty) the NSX profile can be reused, so we can consider
|
||||
# creating it only once in the future, and keeping a use-count for it.
|
||||
# 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)
|
||||
|
||||
try:
|
||||
profile = self._nsx_vpn.tunnel_profile.create(
|
||||
ipsecpolicy['name'],
|
||||
description=ipsecpolicy['description'],
|
||||
encryption_algorithm=ipsec_utils.ENCRYPTION_ALGORITHM_MAP[
|
||||
ipsecpolicy['encryption_algorithm']],
|
||||
digest_algorithm=ipsec_utils.AUTH_ALGORITHM_MAP[
|
||||
ipsecpolicy['auth_algorithm']],
|
||||
dh_group=ipsec_utils.PFS_MAP[ipsecpolicy['pfs']],
|
||||
pfs=True,
|
||||
sa_life_time=ipsecpolicy['lifetime']['value'],
|
||||
tags=self._nsx_tags(context, connection))
|
||||
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):
|
||||
self._nsx_vpn.tunnel_profile.delete(ipsecprofile_id)
|
||||
|
||||
def _create_dpd_profile(self, context, connection):
|
||||
dpd_info = connection['dpd']
|
||||
try:
|
||||
profile = self._nsx_vpn.dpd_profile.create(
|
||||
connection['name'][:240] + '-dpd-profile',
|
||||
description='neutron dpd profile',
|
||||
timeout=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, dpdprofile_id):
|
||||
dpd_info = connection['dpd']
|
||||
self._nsx_vpn.dpd_profile.update(dpdprofile_id,
|
||||
timeout=dpd_info.get('timeout'),
|
||||
enabled=True if dpd_info.get('action') == 'hold' else False)
|
||||
|
||||
def _create_peer_endpoint(self, context, connection, ikeprofile_id,
|
||||
ipsecprofile_id, dpdprofile_id):
|
||||
default_auth = vpn_ipsec.AuthenticationModeTypes.AUTH_MODE_PSK
|
||||
try:
|
||||
peer_endpoint = self._nsx_vpn.peer_endpoint.create(
|
||||
connection['name'],
|
||||
connection['peer_address'],
|
||||
connection['peer_id'],
|
||||
description=connection['description'],
|
||||
authentication_mode=default_auth,
|
||||
dpd_profile_id=dpdprofile_id,
|
||||
ike_profile_id=ikeprofile_id,
|
||||
ipsec_tunnel_profile_id=ipsecprofile_id,
|
||||
connection_initiation_mode=ipsec_utils.INITIATION_MODE_MAP[
|
||||
connection['initiator']],
|
||||
psk=connection['psk'],
|
||||
tags=self._nsx_tags(context, connection))
|
||||
except nsx_lib_exc.ManagerError as e:
|
||||
msg = _("Failed to create a peer endpoint: %s") % e
|
||||
raise nsx_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
return peer_endpoint['id']
|
||||
|
||||
def _update_peer_endpoint(self, peer_ep_id, connection):
|
||||
self._nsx_vpn.peer_endpoint.update(
|
||||
peer_ep_id,
|
||||
name=connection['name'],
|
||||
peer_address=connection['peer_address'],
|
||||
peer_id=connection['peer_id'],
|
||||
description=connection['description'],
|
||||
connection_initiation_mode=ipsec_utils.INITIATION_MODE_MAP[
|
||||
connection['initiator']],
|
||||
psk=connection['psk'])
|
||||
|
||||
def _delete_peer_endpoint(self, peer_ep_id):
|
||||
self._nsx_vpn.peer_endpoint.delete(peer_ep_id)
|
||||
|
||||
def _get_profiles_from_peer_endpoint(self, peer_ep_id):
|
||||
peer_ep = self._nsx_vpn.peer_endpoint.get(peer_ep_id)
|
||||
return (
|
||||
peer_ep['ike_profile_id'],
|
||||
peer_ep['ipsec_tunnel_profile_id'],
|
||||
peer_ep['dpd_profile_id'])
|
||||
|
||||
def _create_local_endpoint(self, context, local_addr, nsx_service_id,
|
||||
router_id):
|
||||
"""Creating an NSX local endpoint for a logical router
|
||||
|
||||
This endpoint can be reused by other connections, and will be deleted
|
||||
when the router is deleted or gateway is removed
|
||||
"""
|
||||
# Add the neutron router-id to the tags to help search later
|
||||
tags = self._nsxlib.build_v3_tags_payload(
|
||||
{'id': router_id}, resource_type='os-neutron-router-id',
|
||||
project_name=context.tenant_name)
|
||||
|
||||
try:
|
||||
local_endpoint = self._nsx_vpn.local_endpoint.create(
|
||||
'Local endpoint for OS VPNaaS',
|
||||
local_addr,
|
||||
nsx_service_id,
|
||||
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 _search_local_endpint(self, router_id):
|
||||
tags = [{'scope': 'os-neutron-router-id', 'tag': router_id}]
|
||||
ep_list = self._nsxlib.search_by_tags(
|
||||
tags=tags,
|
||||
resource_type=self._nsx_vpn.local_endpoint.resource_type)
|
||||
if ep_list['results']:
|
||||
return ep_list['results'][0]['id']
|
||||
|
||||
def _get_local_endpoint(self, context, connection, vpnservice):
|
||||
"""Get the id of the local endpoint for a service
|
||||
|
||||
The NSX allows only one local endpoint per local address
|
||||
This method will create it if there is not matching endpoint
|
||||
"""
|
||||
# use the router GW as the local ip
|
||||
router_id = vpnservice['router']['id']
|
||||
|
||||
# check if we already have this endpoint on the NSX
|
||||
local_ep_id = self._search_local_endpint(router_id)
|
||||
if local_ep_id:
|
||||
return local_ep_id
|
||||
|
||||
# create a new one
|
||||
local_addr = self._get_router_ext_gw(context, router_id)
|
||||
nsx_service_id = self._get_nsx_vpn_service(context, vpnservice)
|
||||
local_ep_id = self._create_local_endpoint(
|
||||
context, local_addr, nsx_service_id, router_id)
|
||||
return local_ep_id
|
||||
|
||||
def _delete_local_endpoint(self, resource, event, trigger, **kwargs):
|
||||
"""Upon router deletion / gw removal delete the matching endpoint"""
|
||||
router_id = kwargs.get('router_id')
|
||||
local_ep_id = self._search_local_endpint(router_id)
|
||||
if local_ep_id:
|
||||
self._nsx_vpn.local_endpoint.delete(local_ep_id)
|
||||
|
||||
def _get_session_rules(self, context, connection, vpnservice):
|
||||
# TODO(asarfaty): support vpn-endpoint-groups too
|
||||
peer_cidrs = connection['peer_cidrs']
|
||||
local_cidrs = [vpnservice['subnet']['cidr']]
|
||||
rule = self._nsx_vpn.session.get_rule_obj(local_cidrs, peer_cidrs)
|
||||
return [rule]
|
||||
|
||||
def _create_session(self, context, connection, local_ep_id,
|
||||
peer_ep_id, rules):
|
||||
try:
|
||||
session = self._nsx_vpn.session.create(
|
||||
connection['name'], local_ep_id, peer_ep_id, rules,
|
||||
description=connection['description'],
|
||||
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, session_id, connection, rules):
|
||||
self._nsx_vpn.session.update(
|
||||
session_id,
|
||||
name=connection['name'],
|
||||
description=connection['description'],
|
||||
policy_rules=rules)
|
||||
|
||||
def _delete_session(self, session_id):
|
||||
self._nsx_vpn.session.delete(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
|
||||
peer_ep_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 the peer endpoint and add to the DB
|
||||
peer_ep_id = self._create_peer_endpoint(
|
||||
context, ipsec_site_conn,
|
||||
ikeprofile_id, ipsecprofile_id, dpdprofile_id)
|
||||
LOG.debug("Created NSX peer endpoint %s", peer_ep_id)
|
||||
|
||||
# create or reuse a local endpoint using the vpn service
|
||||
local_ep_id = self._get_local_endpoint(
|
||||
context, ipsec_site_conn, vpnservice)
|
||||
|
||||
# Finally: create the session with policy rules
|
||||
rules = self._get_session_rules(
|
||||
context, ipsec_site_conn, vpnservice)
|
||||
session_id = self._create_session(
|
||||
context, ipsec_site_conn, local_ep_id, peer_ep_id, rules)
|
||||
|
||||
# update the DB with the session id
|
||||
db.add_nsx_vpn_connection_mapping(
|
||||
context.session, ipsec_site_conn['id'], session_id,
|
||||
dpdprofile_id, ikeprofile_id, ipsecprofile_id, peer_ep_id)
|
||||
|
||||
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(session_id)
|
||||
if peer_ep_id:
|
||||
self._delete_peer_endpoint(peer_ep_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)
|
||||
|
||||
# update router advertisement rules
|
||||
self._update_router_advertisement(context, vpnservice)
|
||||
|
||||
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)
|
||||
|
||||
# get all data from the nsx based on the connection id in the DB
|
||||
mapping = db.get_nsx_vpn_connection_mapping(
|
||||
context.session, ipsec_site_conn['id'])
|
||||
if not mapping:
|
||||
LOG.warning("Couldn't find nsx ids for VPN connection %s",
|
||||
ipsec_site_conn['id'])
|
||||
# Do not fail the deletion
|
||||
return
|
||||
|
||||
if mapping['session_id']:
|
||||
self._delete_session(mapping['session_id'])
|
||||
if mapping['peer_ep_id']:
|
||||
self._delete_peer_endpoint(mapping['peer_ep_id'])
|
||||
if mapping['dpd_profile_id']:
|
||||
self._delete_dpd_profile(mapping['dpd_profile_id'])
|
||||
if mapping['ipsec_profile_id']:
|
||||
self._delete_ipsec_profile(mapping['ipsec_profile_id'])
|
||||
if mapping['ike_profile_id']:
|
||||
self._delete_ike_profile(mapping['ike_profile_id'])
|
||||
|
||||
# Do not delete the local endpoint and service as they are reused
|
||||
db.delete_nsx_vpn_connection_mapping(context.session,
|
||||
ipsec_site_conn['id'])
|
||||
# update router firewall rules
|
||||
self._update_firewall_rules(context, vpnservice)
|
||||
|
||||
# update router advertisement rules
|
||||
self._update_router_advertisement(context, vpnservice)
|
||||
|
||||
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
|
||||
|
||||
ipsec_id = old_ipsec_conn['id']
|
||||
vpnservice_id = old_ipsec_conn['vpnservice_id']
|
||||
vpnservice = self.service_plugin._get_vpnservice(
|
||||
context, vpnservice_id)
|
||||
mapping = db.get_nsx_vpn_connection_mapping(
|
||||
context.session, ipsec_site_conn['id'])
|
||||
if not mapping:
|
||||
LOG.error("Couldn't find nsx ids for VPN connection %s",
|
||||
ipsec_site_conn['id'])
|
||||
self._update_status(context, vpnservice_id, ipsec_id, "ERROR")
|
||||
raise nsx_exc.NsxIPsecVpnMappingNotFound(conn=ipsec_id)
|
||||
|
||||
update_all = (old_ipsec_conn['name'] != ipsec_site_conn['name'] or
|
||||
old_ipsec_conn['description'] !=
|
||||
ipsec_site_conn['description'])
|
||||
# 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
|
||||
update_all):
|
||||
self._update_dpd_profile(ipsec_site_conn,
|
||||
mapping['dpd_profile_id'])
|
||||
|
||||
# update peer endpoint with all the parameters that could be modified
|
||||
# Note(asarfaty): local endpoints are reusable and will not be updated
|
||||
self._update_peer_endpoint(mapping['peer_ep_id'], ipsec_site_conn)
|
||||
rules = self._get_session_rules(
|
||||
context, ipsec_site_conn, vpnservice)
|
||||
self._update_session(mapping['session_id'], ipsec_site_conn, rules)
|
||||
|
||||
if 'peer_cidrs' in ipsec_site_conn:
|
||||
# Update firewall
|
||||
self._update_firewall_rules(context, vpnservice)
|
||||
|
||||
# No service updates. No need to update router advertisement rules
|
||||
|
||||
def _get_gateway_ips(self, router):
|
||||
"""Obtain the IPv4 and/or IPv6 GW IP for the router.
|
||||
|
||||
If there are multiples, (arbitrarily) use the first one.
|
||||
"""
|
||||
v4_ip = v6_ip = None
|
||||
for fixed_ip in router.gw_port['fixed_ips']:
|
||||
addr = fixed_ip['ip_address']
|
||||
vers = netaddr.IPAddress(addr).version
|
||||
if vers == 4:
|
||||
if v4_ip is None:
|
||||
v4_ip = addr
|
||||
elif v6_ip is None:
|
||||
v6_ip = addr
|
||||
return v4_ip, v6_ip
|
||||
|
||||
def _create_vpn_service(self, tier0_uuid):
|
||||
try:
|
||||
service = self._nsx_vpn.service.create(
|
||||
'Neutron VPN service for router ' + tier0_uuid,
|
||||
tier0_uuid,
|
||||
enabled=True,
|
||||
ike_log_level=ipsec_utils.DEFAULT_LOG_LEVEL,
|
||||
tags=self._nsx_tags_for_reused())
|
||||
except nsx_lib_exc.ManagerError as e:
|
||||
msg = _("Failed to create vpn service: %s") % e
|
||||
raise nsx_exc.NsxPluginException(err_msg=msg)
|
||||
|
||||
return service['id']
|
||||
|
||||
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_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):
|
||||
# find the service for the tier0 router in the NSX.
|
||||
# Note(asarfaty) we expect only a small number of services
|
||||
services = self._nsx_vpn.service.list()['results']
|
||||
for srv in services:
|
||||
if srv['logical_router_id']['target_id'] == tier0_uuid:
|
||||
# if it exists but disabled: issue an error
|
||||
if not srv.get('enabled', True):
|
||||
msg = _("NSX vpn service %s must be enabled") % srv['id']
|
||||
raise nsx_exc.NsxPluginException(err_msg=msg)
|
||||
return srv['id']
|
||||
|
||||
def _create_vpn_service_if_needed(self, context, vpnservice):
|
||||
# The service is created on the TIER0 router attached to the router GW
|
||||
# The NSX can keep only one service per tier0 router so we reuse it
|
||||
router_id = vpnservice['router_id']
|
||||
tier0_uuid = self._get_tier0_uuid(context, router_id)
|
||||
if self._find_vpn_service(tier0_uuid):
|
||||
return
|
||||
|
||||
# create a new one
|
||||
self._create_vpn_service(tier0_uuid)
|
||||
|
||||
def _get_nsx_vpn_service(self, context, vpnservice):
|
||||
router_id = vpnservice['router_id']
|
||||
tier0_uuid = self._get_tier0_uuid(context, router_id)
|
||||
return self._find_vpn_service(tier0_uuid)
|
||||
|
||||
def create_vpnservice(self, context, vpnservice):
|
||||
#TODO(asarfaty) support vpn-endpoint-group-create for local & peer
|
||||
# cidrs too
|
||||
LOG.debug('Creating VPN service %(vpn)s', {'vpn': vpnservice})
|
||||
vpnservice_id = vpnservice['id']
|
||||
|
||||
try:
|
||||
self.validator.validate_vpnservice(context, vpnservice)
|
||||
except Exception:
|
||||
with excutils.save_and_reraise_exception():
|
||||
# Rolling back change on the neutron
|
||||
self.service_plugin.delete_vpnservice(context, vpnservice_id)
|
||||
|
||||
vpnservice = self.service_plugin._get_vpnservice(context,
|
||||
vpnservice_id)
|
||||
v4_ip, v6_ip = self._get_gateway_ips(vpnservice.router)
|
||||
if v4_ip:
|
||||
vpnservice['external_v4_ip'] = v4_ip
|
||||
if v6_ip:
|
||||
vpnservice['external_v6_ip'] = v6_ip
|
||||
self.service_plugin.set_external_tunnel_ips(context,
|
||||
vpnservice_id,
|
||||
v4_ip=v4_ip, v6_ip=v6_ip)
|
||||
self._create_vpn_service_if_needed(context, vpnservice)
|
||||
|
||||
def update_vpnservice(self, context, old_vpnservice, vpnservice):
|
||||
# No meaningful field can change here
|
||||
pass
|
||||
|
||||
def delete_vpnservice(self, context, vpnservice):
|
||||
# Do not delete the NSX service or DB entry as those will be reused.
|
||||
pass
|
59
vmware_nsx/services/vpnaas/nsxv3/ipsec_utils.py
Normal file
59
vmware_nsx/services/vpnaas/nsxv3/ipsec_utils.py
Normal file
@ -0,0 +1,59 @@
|
||||
# Copyright 2017 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 vmware_nsxlib.v3 import vpn_ipsec
|
||||
|
||||
ENCRYPTION_ALGORITHM_MAP = {
|
||||
'aes-128': vpn_ipsec.EncryptionAlgorithmTypes.ENCRYPTION_ALGORITHM_128,
|
||||
'aes-256': vpn_ipsec.EncryptionAlgorithmTypes.ENCRYPTION_ALGORITHM_256,
|
||||
}
|
||||
|
||||
AUTH_ALGORITHM_MAP = {
|
||||
'sha1': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA1,
|
||||
'sha256': vpn_ipsec.DigestAlgorithmTypes.DIGEST_ALGORITHM_SHA256,
|
||||
}
|
||||
|
||||
PFS_MAP = {
|
||||
'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,
|
||||
}
|
||||
|
||||
ENCAPSULATION_MODE_MAP = {
|
||||
'tunnel': vpn_ipsec.EncapsulationModeTypes.ENCAPSULATION_MODE_TUNNEL
|
||||
}
|
||||
|
||||
TRANSFORM_PROTOCOL_MAP = {
|
||||
'esp': vpn_ipsec.TransformProtocolTypes.TRANSFORM_PROTOCOL_ESP
|
||||
}
|
||||
|
||||
DPD_ACTION_MAP = {
|
||||
'hold': vpn_ipsec.DpdProfileActionTypes.DPD_PROFILE_ACTION_HOLD,
|
||||
'disabled': None
|
||||
}
|
||||
|
||||
INITIATION_MODE_MAP = {
|
||||
'bi-directional': (vpn_ipsec.ConnectionInitiationModeTypes.
|
||||
INITIATION_MODE_INITIATOR),
|
||||
'response-only': (vpn_ipsec.ConnectionInitiationModeTypes.
|
||||
INITIATION_MODE_RESPOND_ONLY)
|
||||
}
|
||||
|
||||
DEFAULT_LOG_LEVEL = vpn_ipsec.IkeLogLevelTypes.LOG_LEVEL_ERROR
|
374
vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py
Normal file
374
vmware_nsx/services/vpnaas/nsxv3/ipsec_validator.py
Normal file
@ -0,0 +1,374 @@
|
||||
# Copyright 2017 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 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.services.vpnaas.nsxv3 import ipsec_utils
|
||||
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):
|
||||
|
||||
"""Validator methods for Vmware NSX-V3 VPN support"""
|
||||
def __init__(self, service_plugin):
|
||||
super(IPsecV3Validator, self).__init__()
|
||||
self.vpn_plugin = service_plugin
|
||||
self.nsxlib = self.core_plugin.nsxlib
|
||||
self.check_backend_version()
|
||||
|
||||
def check_backend_version(self):
|
||||
if not self.nsxlib.feature_supported(consts.FEATURE_IPSEC_VPN):
|
||||
# ipsec vpn is not supported
|
||||
LOG.warning("VPNaaS is not supported by the NSX backend (version "
|
||||
"%s)",
|
||||
self.nsxlib.get_version())
|
||||
self.backend_support = False
|
||||
else:
|
||||
self.backend_support = True
|
||||
|
||||
def _validate_backend_version(self):
|
||||
if not self.backend_support:
|
||||
msg = (_("VPNaaS is not supported by the NSX backend "
|
||||
"(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 (value and (value < vpn_ipsec.SALifetimeLimits.SA_LIFETIME_MIN or
|
||||
value > vpn_ipsec.SALifetimeLimits.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': vpn_ipsec.SALifetimeLimits.SA_LIFETIME_MIN,
|
||||
'max': vpn_ipsec.SALifetimeLimits.SA_LIFETIME_MAX}
|
||||
raise nsx_exc.NsxVpnValidationError(details=msg)
|
||||
|
||||
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)
|
||||
|
||||
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', True))]
|
||||
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)
|
||||
|
||||
# TODO(asarfaty): also add this validation when adding an interface
|
||||
# or no-snat to a router through the nsx-v3 plugin
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
vpnservice = self.vpn_plugin._get_vpnservice(context,
|
||||
vpnservice_id)
|
||||
router_id = vpnservice['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 _validate_router(self, context, router_id):
|
||||
# Verify that the router gw network is connected to an active-standby
|
||||
# Tier0 router
|
||||
router_db = self.core_plugin._get_router(context, router_id)
|
||||
tier0_uuid = self.core_plugin._get_tier0_uuid_by_router(context,
|
||||
router_db)
|
||||
# TODO(asarfaty): cache this result
|
||||
tier0_router = self.nsxlib.logical_router.get(tier0_uuid)
|
||||
if (not tier0_router or
|
||||
tier0_router.get('high_availability_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 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)
|
@ -197,6 +197,9 @@ class NsxV3PluginTestCaseMixin(test_plugin.NeutronDbPluginV2TestCase,
|
||||
_mock_nsx_backend_calls()
|
||||
self.setup_conf_overrides()
|
||||
self.mock_plugin_methods()
|
||||
# ignoring the given plugin and use the nsx-v3 one
|
||||
if not plugin.endswith('NsxTVDPlugin'):
|
||||
plugin = PLUGIN_NAME
|
||||
super(NsxV3PluginTestCaseMixin, self).setUp(plugin=plugin,
|
||||
ext_mgr=ext_mgr)
|
||||
|
||||
|
390
vmware_nsx/tests/unit/services/vpnaas/test_nsxv3_vpnaas.py
Normal file
390
vmware_nsx/tests/unit/services/vpnaas/test_nsxv3_vpnaas.py
Normal file
@ -0,0 +1,390 @@
|
||||
# Copyright 2013, Nachi Ueno, NTT I3, 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 mock
|
||||
|
||||
from neutron_lib import context as n_ctx
|
||||
from neutron_vpnaas.tests import base
|
||||
|
||||
from vmware_nsx.common import exceptions as nsx_exc
|
||||
from vmware_nsx.services.vpnaas.nsxv3 import ipsec_validator
|
||||
|
||||
|
||||
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.IPsecV3Validator(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': 90}}
|
||||
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'}
|
||||
self.assertRaises(nsx_exc.NsxVpnValidationError,
|
||||
validation_func,
|
||||
self.context, auth_algorithm)
|
||||
|
||||
auth_algorithm = {'auth_algorithm': 'sha512'}
|
||||
self.assertRaises(nsx_exc.NsxVpnValidationError,
|
||||
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': 'group5'}
|
||||
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_router(self):
|
||||
router = {'high_availability_mode': 'ACITVE_ACTIVE'}
|
||||
with mock.patch.object(self.validator.nsxlib.logical_router, 'get',
|
||||
return_value=router):
|
||||
self.assertRaises(nsx_exc.NsxVpnValidationError,
|
||||
self.validator.validate_vpnservice,
|
||||
self.context, self.vpn_service)
|
||||
|
||||
router = {'high_availability_mode': 'ACTIVE_STANDBY'}
|
||||
with mock.patch.object(self.validator.nsxlib.logical_router, 'get',
|
||||
return_value=router):
|
||||
self.validator.validate_vpnservice(self.context, self.vpn_service)
|
||||
|
||||
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_router(context, router_id):
|
||||
return {'id': router_id,
|
||||
'external_gateway_info': {
|
||||
'external_fixed_ips': [{
|
||||
'ip_address': '1.1.1.%s' % router_id}]}}
|
||||
|
||||
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',
|
||||
'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_router',
|
||||
side_effect=mock_get_router),\
|
||||
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': 5}}
|
||||
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)
|
||||
|
||||
|
||||
# TODO(asarfaty): add tests for the driver
|
Loading…
Reference in New Issue
Block a user