vmware-nsx/vmware_nsx/services/vpnaas/nsxv/ipsec_driver.py
Zhenmei 53d1bd0e2f NSXv: Support ipsec VPNaaS on nsxv driver
Change-Id: Id3fd4da7e4dd4cac4eb2e32024c0d8242b85a0bb
Co-Authored-By: Roey Chen <roeyc@vmware.com>
2017-07-11 06:03:19 -07:00

356 lines
16 KiB
Python

# Copyright 2016 VMware, Inc.
# All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import netaddr
from neutron_lib import exceptions as n_exc
from neutron_lib.plugins import directory
from neutron_vpnaas.services.vpn import service_drivers
from oslo_log import log as logging
from vmware_nsx._i18n import _
from vmware_nsx.common import exceptions as nsxv_exc
from vmware_nsx.common import locking
from vmware_nsx.common import nsxv_constants
from vmware_nsx.db import nsxv_db
from vmware_nsx.plugins.nsx_v.vshield.common import exceptions as vcns_exc
from vmware_nsx.services.vpnaas.nsxv import ipsec_validator
LOG = logging.getLogger(__name__)
IPSEC = 'ipsec'
class NSXvIPsecVpnDriver(service_drivers.VpnDriver):
def __init__(self, service_plugin):
self._core_plugin = directory.get_plugin()
self._vcns = self._core_plugin.nsx_v.vcns
validator = ipsec_validator.IPsecValidator(service_plugin)
super(NSXvIPsecVpnDriver, self).__init__(service_plugin, validator)
@property
def l3_plugin(self):
return self._core_plugin
@property
def service_type(self):
return IPSEC
def _is_shared_router(self, router):
return router.get('router_type') == nsxv_constants.SHARED
def _get_router_edge_id(self, context, vpnservice_id):
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_id)
router_id = vpnservice['router_id']
edge_binding = nsxv_db.get_nsxv_router_binding(context.session,
router_id)
if not edge_binding:
msg = _("Couldn't find edge binding for router %s") % router_id
raise nsxv_exc.NsxPluginException(err_msg=msg)
if edge_binding['edge_type'] == nsxv_constants.VDR_EDGE:
edge_manager = self._core_plugin.edge_manager
router_id = edge_manager.get_plr_by_tlr_id(context, router_id)
binding = nsxv_db.get_nsxv_router_binding(context.session,
router_id)
edge_id = binding['edge_id']
else:
# Get exclusive edge id
edge_id = edge_binding['edge_id']
return router_id, edge_id
def _convert_ipsec_conn(self, context, ipsec_site_connection):
ipsec_id = ipsec_site_connection['ipsecpolicy_id']
vpnservice_id = ipsec_site_connection['vpnservice_id']
ipsecpolicy = self.service_plugin.get_ipsecpolicy(context, ipsec_id)
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_id)
local_cidr = vpnservice['subnet']['cidr']
router_id = vpnservice['router_id']
router = self._core_plugin.get_router(context, router_id)
local_addr = (router['external_gateway_info']['external_fixed_ips']
[0]["ip_address"])
encrypt = nsxv_constants.ENCRYPTION_ALGORITHM_MAP.get(
ipsecpolicy.get('encryption_algorithm'))
site = {
'enabled': True,
'enablePfs': True,
'dhGroup': nsxv_constants.PFS_MAP.get(ipsecpolicy.get('pfs')),
'name': ipsec_site_connection.get('name'),
'description': ipsec_site_connection.get('description'),
'localId': local_addr,
'localIp': local_addr,
'peerId': ipsec_site_connection['peer_id'],
'peerIp': ipsec_site_connection.get('peer_address'),
'localSubnets': {
'subnets': [local_cidr]},
'peerSubnets': {
'subnets': ipsec_site_connection.get('peer_cidrs')},
'authenticationMode': ipsec_site_connection.get('auth_mode'),
'psk': ipsec_site_connection.get('psk'),
'encryptionAlgorithm': encrypt
}
return site
def _generate_new_sites(self, edge_id, ipsec_site_conn):
# Fetch the previous ipsec vpn configuration
ipsecvpn_configs = self._get_ipsec_config(edge_id)
vse_sites = []
if ipsecvpn_configs[1]['enabled']:
vse_sites = ([site for site
in ipsecvpn_configs[1]['sites']['sites']])
vse_sites.append(ipsec_site_conn)
return vse_sites
def _generate_ipsecvpn_firewall_rules(self, edge_id):
ipsecvpn_configs = self._get_ipsec_config(edge_id)
ipsec_vpn_fw_rules = []
if ipsecvpn_configs[1]['enabled']:
for site in ipsecvpn_configs[1]['sites']['sites']:
peer_subnets = site['peerSubnets']['subnets']
local_subnets = site['localSubnets']['subnets']
ipsec_vpn_fw_rules.append({
'name': 'VPN ' + site['name'],
'action': 'allow',
'enabled': True,
'source_ip_address': peer_subnets,
'destination_ip_address': local_subnets})
return ipsec_vpn_fw_rules
def _update_firewall_rules(self, context, vpnservice_id):
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_id)
router_db = (
self._core_plugin._get_router(context, vpnservice['router_id']))
self._core_plugin._update_subnets_and_dnat_firewall(context,
router_db)
def _update_status(self, context, vpn_service_id, ipsec_site_conn_id,
status, updated_pending_status=True):
status_list = []
vpn_status = {}
ipsec_site_conn = {}
vpn_status['id'] = vpn_service_id
vpn_status['updated_pending_status'] = updated_pending_status
vpn_status['status'] = status
ipsec_site_conn['status'] = status
ipsec_site_conn['updated_pending_status'] = updated_pending_status
vpn_status['ipsec_site_connections'] = {ipsec_site_conn_id:
ipsec_site_conn}
status_list.append(vpn_status)
self.service_plugin.update_status_by_agent(context, status_list)
def create_ipsec_site_connection(self, context, ipsec_site_connection):
LOG.debug('Creating ipsec site connection %(conn_info)s.',
{"conn_info": ipsec_site_connection})
self.validator.validate_ipsec_conn(context, ipsec_site_connection)
new_ipsec = self._convert_ipsec_conn(context, ipsec_site_connection)
vpnservice_id = ipsec_site_connection['vpnservice_id']
edge_id = self._get_router_edge_id(context, vpnservice_id)[1]
with locking.LockManager.get_lock(edge_id):
vse_sites = self._generate_new_sites(edge_id, new_ipsec)
ipsec_id = ipsec_site_connection["id"]
try:
LOG.debug('Updating ipsec vpn configuration %(vse_sites)s.',
{'vse_sites': vse_sites})
self._update_ipsec_config(edge_id, vse_sites, enabled=True)
except vcns_exc.VcnsApiException:
self._update_status(context, vpnservice_id, ipsec_id,
"ERROR")
msg = (_("Failed to create ipsec site connection "
"configuration with %(edge_id)s.") %
{'edge_id': edge_id})
raise nsxv_exc.NsxPluginException(err_msg=msg)
LOG.debug('Updating ipsec vpn firewall')
try:
self._update_firewall_rules(context, vpnservice_id)
except vcns_exc.VcnsApiException:
self._update_status(context, vpnservice_id, ipsec_id, "ERROR")
msg = (_("Failed to update firewall rule for ipsec vpn "
"with %(edge_id)s.") % {'edge_id': edge_id})
raise nsxv_exc.NsxPluginException(err_msg=msg)
self._update_status(context, vpnservice_id, ipsec_id, "ACTIVE")
def _get_ipsec_config(self, edge_id):
return self._vcns.get_ipsec_config(edge_id)
def delete_ipsec_site_connection(self, context, ipsec_site_conn):
LOG.debug('Deleting ipsec site connection %(site)s.',
{"site": ipsec_site_conn})
ipsec_id = ipsec_site_conn['id']
edge_id = self._get_router_edge_id(context,
ipsec_site_conn['vpnservice_id'])[1]
with locking.LockManager.get_lock(edge_id):
del_site, vse_sites = self._find_vse_site(context, edge_id,
ipsec_site_conn)
if not del_site:
LOG.error("Failed to find ipsec_site_connection "
"%(ipsec_site_conn)s with %(edge_id)s.",
{'ipsec_site_conn': ipsec_site_conn,
'edge_id': edge_id})
raise nsxv_exc.NsxIPsecVpnMappingNotFound(conn=ipsec_id)
vse_sites.remove(del_site)
enabled = True if vse_sites else False
try:
self._update_ipsec_config(edge_id, vse_sites, enabled)
except vcns_exc.VcnsApiException:
msg = (_("Failed to delete ipsec site connection "
"configuration with edge_id: %(edge_id)s.") %
{'egde_id': edge_id})
raise nsxv_exc.NsxPluginException(err_msg=msg)
try:
self._update_firewall_rules(context,
ipsec_site_conn['vpnservice_id'])
except vcns_exc.VcnsApiException:
msg = _("Failed to update firewall rule for ipsec vpn with "
"%(edge_id)s.") % {'edge_id': edge_id}
raise nsxv_exc.NsxPluginException(err_msg=msg)
def _find_vse_site(self, context, edge_id, site):
# Fetch the previous ipsec vpn configuration
ipsecvpn_configs = self._get_ipsec_config(edge_id)[1]
vpnservice = self.service_plugin._get_vpnservice(context,
site['vpnservice_id'])
local_cidr = vpnservice['subnet']['cidr']
old_site = None
vse_sites = None
if ipsecvpn_configs['enabled']:
vse_sites = ipsecvpn_configs['sites'].get('sites')
for s in vse_sites:
if ((s['peerSubnets'].get('subnets') == site['peer_cidrs'])
and
(s['localSubnets'].get('subnets')[0] == local_cidr)):
old_site = s
break
return old_site, vse_sites
def _update_site_dict(self, context, edge_id, site,
ipsec_site_connection):
# Fetch the previous ipsec vpn configuration
old_site, vse_sites = self._find_vse_site(context, edge_id, site)
if old_site:
vse_sites.remove(old_site)
if 'peer_addresses' in ipsec_site_connection:
old_site['peerIp'] = ipsec_site_connection['peer_address']
if 'peer_cidrs' in ipsec_site_connection:
old_site['peerSubnets']['subnets'] = (ipsec_site_connection
['peer_cidrs'])
vse_sites.append(old_site)
return vse_sites
def update_ipsec_site_connection(self, context, old_ipsec_conn,
ipsec_site_connection):
LOG.debug('Updating ipsec site connection %(site)s.',
{"site": ipsec_site_connection})
vpnservice_id = old_ipsec_conn['vpnservice_id']
ipsec_id = old_ipsec_conn['id']
edge_id = self._get_router_edge_id(context, vpnservice_id)[1]
with locking.LockManager.get_lock(edge_id):
vse_sites = self._update_site_dict(context, edge_id,
old_ipsec_conn,
ipsec_site_connection)
if not vse_sites:
self._update_status(context, vpnservice_id, ipsec_id,
"ERROR")
LOG.error("Failed to find ipsec_site_connection "
"%(ipsec_site_conn)s with %(edge_id)s.",
{'ipsec_site_conn': ipsec_site_connection,
'edge_id': edge_id})
raise nsxv_exc.NsxIPsecVpnMappingNotFound(conn=ipsec_id)
try:
LOG.debug('Updating ipsec vpn configuration %(vse_sites)s.',
{'vse_sites': vse_sites})
self._update_ipsec_config(edge_id, vse_sites)
except vcns_exc.VcnsApiException:
self._update_status(context, vpnservice_id, ipsec_id, "ERROR")
msg = (_("Failed to create ipsec site connection "
"configuration with %(edge_id)s.") %
{'edge_id': edge_id})
raise nsxv_exc.NsxPluginException(err_msg=msg)
if 'peer_cidrs' in ipsec_site_connection:
# Update firewall
old_ipsec_conn['peer_cidrs'] = (
ipsec_site_connection['peer_cidrs'])
try:
self._update_firewall_rules(context, vpnservice_id)
except vcns_exc.VcnsApiException:
self._update_status(context, vpnservice_id, ipsec_id,
"ERROR")
msg = (_("Failed to update firewall rule for ipsec "
"vpn with %(edge_id)s.") % {'edge_id': edge_id})
raise nsxv_exc.NsxPluginException(err_msg=msg)
def _get_gateway_ips(self, router):
"""Obtain the IPv4 and/or IPv6 GW IP for the router.
If there are multiples, (arbitrarily) use the first one.
"""
v4_ip = v6_ip = None
for fixed_ip in router.gw_port['fixed_ips']:
addr = fixed_ip['ip_address']
vers = netaddr.IPAddress(addr).version
if vers == 4:
if v4_ip is None:
v4_ip = addr
elif v6_ip is None:
v6_ip = addr
return v4_ip, v6_ip
def create_vpnservice(self, context, vpnservice):
LOG.debug('Creating VPN service %(vpn)s', {'vpn': vpnservice})
# Only support distributed and exclusive router type
router_id = vpnservice['router_id']
vpnservice_id = vpnservice['id']
router = self._core_plugin.get_router(context, router_id)
if self._is_shared_router(router):
# Rolling back change on the neutron
self.service_plugin.delete_vpnservice(context, vpnservice_id)
msg = _("Router type is not supported for VPN service, only "
"support distributed and exclusive router")
raise n_exc.InvalidInput(error_message=msg)
vpnservice = self.service_plugin._get_vpnservice(context,
vpnservice_id)
v4_ip, v6_ip = self._get_gateway_ips(vpnservice.router)
if v4_ip:
vpnservice['external_v4_ip'] = v4_ip
if v6_ip:
vpnservice['external_v6_ip'] = v6_ip
self.service_plugin.set_external_tunnel_ips(context,
vpnservice_id,
v4_ip=v4_ip, v6_ip=v6_ip)
def update_vpnservice(self, context, old_vpnservice, vpnservice):
pass
def delete_vpnservice(self, context, vpnservice):
pass
def _update_ipsec_config(self, edge_id, sites, enabled=True):
ipsec_config = {'featureType': "ipsec_4.0",
'enabled': enabled}
ipsec_config['sites'] = {'sites': sites}
try:
self._vcns.update_ipsec_config(edge_id, ipsec_config)
except vcns_exc.VcnsApiException:
msg = _("Failed to update ipsec vpn configuration with "
"edge_id: %s") % edge_id
raise nsxv_exc.NsxPluginException(err_msg=msg)