53d1bd0e2f
Change-Id: Id3fd4da7e4dd4cac4eb2e32024c0d8242b85a0bb Co-Authored-By: Roey Chen <roeyc@vmware.com>
356 lines
16 KiB
Python
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)
|