# 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.plugins import directory from neutron_vpnaas.services.vpn import service_drivers from oslo_log import log as logging from oslo_utils import excutils 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.extensions import projectpluginmap 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() if self._core_plugin.is_tvd_plugin(): self._core_plugin = self._core_plugin.get_plugin_by_type( projectpluginmap.NsxPlugins.NSX_V) 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 _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, plugin_type, context, edge_id=None): 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.get('name', 'rule'), '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}) 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 = bool(vse_sites) 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}) 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) 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)