ec2-api/ec2api/api/vpn_connection.py
Ngo Quoc Cuong 90ed02d682 Delete log translation functions and add hacking rule
The i18n team has decided not to translate the logs because it seems
like it not very useful; operators prefer to have them in English so
that they can search for those strings on the internet.

Since we have removed log translations completely, we should add hacking
rule to prevent future mistakes.

Change-Id: Ia7524308ef2675f8d41ac80b37dfc7e3787efd90
2017-07-03 04:14:44 -04:00

502 lines
21 KiB
Python

# Copyright 2014
# The Cloudscaling Group, 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.
import random
import string
from lxml import etree
import netaddr
from neutronclient.common import exceptions as neutron_exception
from oslo_log import log as logging
import six
from ec2api.api import common
from ec2api.api import ec2utils
from ec2api import clients
from ec2api.db import api as db_api
from ec2api import exception
from ec2api.i18n import _
LOG = logging.getLogger(__name__)
"""VPN connections related API implementation
"""
Validator = common.Validator
SHARED_KEY_CHARS = string.ascii_letters + '_.' + string.digits
AWS_MSS = 1387
MTU_MSS_DELTA = 40 # 20 byte IP and 20 byte TCP headers
def create_vpn_connection(context, customer_gateway_id, vpn_gateway_id,
type, options=None):
if not options or options.get('static_routes_only') is not True:
raise exception.Unsupported('BGP dynamic routing is unsupported')
customer_gateway = ec2utils.get_db_item(context, customer_gateway_id)
vpn_gateway = ec2utils.get_db_item(context, vpn_gateway_id)
vpn_connection = next(
(vpn for vpn in db_api.get_items(context, 'vpn')
if vpn['customer_gateway_id'] == customer_gateway_id),
None)
if vpn_connection:
if vpn_connection['vpn_gateway_id'] == vpn_gateway_id:
ec2_vpn_connections = describe_vpn_connections(
context, vpn_connection_id=[vpn_connection['id']])
return {
'vpnConnection': ec2_vpn_connections['vpnConnectionSet'][0]}
else:
raise exception.InvalidCustomerGatewayDuplicateIpAddress()
neutron = clients.neutron(context)
with common.OnCrashCleaner() as cleaner:
os_ikepolicy = {'ike_version': 'v1',
'auth_algorithm': 'sha1',
'encryption_algorithm': 'aes-128',
'pfs': 'group2',
'phase1_negotiation_mode': 'main',
'lifetime': {'units': 'seconds',
'value': 28800}}
os_ikepolicy = neutron.create_ikepolicy(
{'ikepolicy': os_ikepolicy})['ikepolicy']
cleaner.addCleanup(neutron.delete_ikepolicy, os_ikepolicy['id'])
os_ipsecpolicy = {'transform_protocol': 'esp',
'auth_algorithm': 'sha1',
'encryption_algorithm': 'aes-128',
'pfs': 'group2',
'encapsulation_mode': 'tunnel',
'lifetime': {'units': 'seconds',
'value': 3600}}
os_ipsecpolicy = neutron.create_ipsecpolicy(
{'ipsecpolicy': os_ipsecpolicy})['ipsecpolicy']
cleaner.addCleanup(neutron.delete_ipsecpolicy, os_ipsecpolicy['id'])
psk = ''.join(random.choice(SHARED_KEY_CHARS) for _x in range(32))
vpn_connection = db_api.add_item(
context, 'vpn',
{'customer_gateway_id': customer_gateway['id'],
'vpn_gateway_id': vpn_gateway['id'],
'pre_shared_key': psk,
'os_ikepolicy_id': os_ikepolicy['id'],
'os_ipsecpolicy_id': os_ipsecpolicy['id'],
'cidrs': [],
'os_ipsec_site_connections': {}})
cleaner.addCleanup(db_api.delete_item, context, vpn_connection['id'])
neutron.update_ikepolicy(
os_ikepolicy['id'], {'ikepolicy': {'name': vpn_connection['id']}})
neutron.update_ipsecpolicy(
os_ipsecpolicy['id'],
{'ipsecpolicy': {'name': vpn_connection['id']}})
_reset_vpn_connections(context, neutron, cleaner,
vpn_gateway, vpn_connections=[vpn_connection])
ec2_vpn_connections = describe_vpn_connections(
context, vpn_connection_id=[vpn_connection['id']])
return {
'vpnConnection': ec2_vpn_connections['vpnConnectionSet'][0]}
def create_vpn_connection_route(context, vpn_connection_id,
destination_cidr_block):
vpn_connection = ec2utils.get_db_item(context, vpn_connection_id)
if destination_cidr_block in vpn_connection['cidrs']:
return True
neutron = clients.neutron(context)
vpn_gateway = db_api.get_item_by_id(context,
vpn_connection['vpn_gateway_id'])
with common.OnCrashCleaner() as cleaner:
_add_cidr_to_vpn_connection_item(context, vpn_connection,
destination_cidr_block)
cleaner.addCleanup(_remove_cidr_from_vpn_connection_item,
context, vpn_connection, destination_cidr_block)
_reset_vpn_connections(context, neutron, cleaner,
vpn_gateway, vpn_connections=[vpn_connection])
return True
def delete_vpn_connection_route(context, vpn_connection_id,
destination_cidr_block):
vpn_connection = ec2utils.get_db_item(context, vpn_connection_id)
if destination_cidr_block not in vpn_connection['cidrs']:
raise exception.InvalidRouteNotFound(
_('The specified route %(destination_cidr_block)s does not exist')
% {'destination_cidr_block': destination_cidr_block})
neutron = clients.neutron(context)
vpn_gateway = db_api.get_item_by_id(context,
vpn_connection['vpn_gateway_id'])
with common.OnCrashCleaner() as cleaner:
_remove_cidr_from_vpn_connection_item(context, vpn_connection,
destination_cidr_block)
cleaner.addCleanup(_add_cidr_to_vpn_connection_item,
context, vpn_connection, destination_cidr_block)
_reset_vpn_connections(context, neutron, cleaner,
vpn_gateway, vpn_connections=[vpn_connection])
return True
def delete_vpn_connection(context, vpn_connection_id):
vpn_connection = ec2utils.get_db_item(context, vpn_connection_id)
with common.OnCrashCleaner() as cleaner:
db_api.delete_item(context, vpn_connection['id'])
cleaner.addCleanup(db_api.restore_item, context, 'vpn', vpn_connection)
neutron = clients.neutron(context)
_stop_vpn_connection(neutron, vpn_connection)
try:
neutron.delete_ipsecpolicy(vpn_connection['os_ipsecpolicy_id'])
except neutron_exception.Conflict as ex:
LOG.warning('Failed to delete ipsecoplicy %(os_id)s during '
'deleting VPN connection %(id)s. Reason: %(reason)s',
{'id': vpn_connection['id'],
'os_id': vpn_connection['os_ipsecpolicy_id'],
'reason': ex.message})
except neutron_exception.NotFound:
pass
try:
neutron.delete_ikepolicy(vpn_connection['os_ikepolicy_id'])
except neutron_exception.Conflict as ex:
LOG.warning(
'Failed to delete ikepolicy %(os_id)s during deleting '
'VPN connection %(id)s. Reason: %(reason)s',
{'id': vpn_connection['id'],
'os_id': vpn_connection['os_ikepolicy_id'],
'reason': ex.message})
except neutron_exception.NotFound:
pass
return True
def describe_vpn_connections(context, vpn_connection_id=None, filter=None):
formatted_vpn_connections = VpnConnectionDescriber().describe(
context, ids=vpn_connection_id, filter=filter)
return {'vpnConnectionSet': formatted_vpn_connections}
class VpnConnectionDescriber(common.TaggableItemsDescriber,
common.NonOpenstackItemsDescriber):
KIND = 'vpn'
FILTER_MAP = {'customer-gateway-configuration': (
'customerGatewayConfiguration'),
'customer-gateway-id': 'customerGatewayId',
'state': 'state',
'option.static-routes-only': ('options', 'staticRoutesOnly'),
'route.destination-cidr-block': ['routes',
'destinationCidrBlock'],
'type': 'type',
'vpn-connection-id': 'vpnConnectionId',
'vpn-gateway-id': 'vpnGatewayId'}
def get_db_items(self):
self.customer_gateways = {
cgw['id']: cgw
for cgw in db_api.get_items(self.context, 'cgw')}
neutron = clients.neutron(self.context)
self.os_ikepolicies = {
ike['id']: ike
for ike in neutron.list_ikepolicies(
tenant_id=self.context.project_id)['ikepolicies']}
self.os_ipsecpolicies = {
ipsec['id']: ipsec
for ipsec in neutron.list_ipsecpolicies(
tenant_id=self.context.project_id)['ipsecpolicies']}
self.os_ipsec_site_connections = {
conn['id']: conn
for conn in neutron.list_ipsec_site_connections(
tenant_id=self.context.project_id)['ipsec_site_connections']}
self.external_ips = _get_vpn_gateways_external_ips(
self.context, neutron)
return super(VpnConnectionDescriber, self).get_db_items()
def format(self, vpn_connection):
return _format_vpn_connection(
vpn_connection, self.customer_gateways, self.os_ikepolicies,
self.os_ipsecpolicies, self.os_ipsec_site_connections,
self.external_ips)
def _format_vpn_connection(vpn_connection, customer_gateways, os_ikepolicies,
os_ipsecpolicies, os_ipsec_site_connections,
external_ips):
config_dict = _format_customer_config(
vpn_connection, customer_gateways, os_ikepolicies, os_ipsecpolicies,
os_ipsec_site_connections, external_ips)
config = ec2utils.dict_to_xml(config_dict, 'vpn_connection')
config.attrib['id'] = vpn_connection['id']
config_str = etree.tostring(config, xml_declaration=True, encoding='UTF-8',
pretty_print=True)
return {'vpnConnectionId': vpn_connection['id'],
'vpnGatewayId': vpn_connection['vpn_gateway_id'],
'customerGatewayId': vpn_connection['customer_gateway_id'],
'state': 'available',
'type': 'ipsec.1',
'routes': [{'destinationCidrBlock': cidr,
'state': 'available'}
for cidr in vpn_connection['cidrs']],
'vgwTelemetry': [],
'options': {'staticRoutesOnly': True},
'customerGatewayConfiguration': config_str}
def _format_customer_config(vpn_connection, customer_gateways, os_ikepolicies,
os_ipsecpolicies, os_ipsec_site_connections,
external_ips):
customer_gateway = customer_gateways[vpn_connection['customer_gateway_id']]
os_connections_ids = vpn_connection['os_ipsec_site_connections'].values()
if os_connections_ids:
os_ipsec_site_connection = next(
(os_ipsec_site_connections[conn_id]
for conn_id in os_connections_ids
if os_ipsec_site_connections.get(conn_id)),
None)
else:
os_ipsec_site_connection = None
# TODO(ft): figure out and add to the output tunnel internal addresses
config_dict = {
'customer_gateway_id': vpn_connection['customer_gateway_id'],
'vpn_gateway_id': vpn_connection['vpn_gateway_id'],
'vpn_connection_type': 'ipsec.1',
'vpn_connection_attributes': 'NoBGPVPNConnection',
'ipsec_tunnel': {
'customer_gateway': {
'tunnel_outside_address': {
'ip_address': (
os_ipsec_site_connection['peer_address']
if os_ipsec_site_connection else
customer_gateway['ip_address'])}},
'vpn_gateway': {
'tunnel_outside_address': {
'ip_address': external_ips.get(
vpn_connection['vpn_gateway_id'])}}},
}
os_ikepolicy = os_ikepolicies.get(vpn_connection['os_ikepolicy_id'])
if os_ikepolicy:
config_dict['ipsec_tunnel']['ike'] = {
'authentication_protocol': os_ikepolicy['auth_algorithm'],
'encryption_protocol': os_ikepolicy['encryption_algorithm'],
'lifetime': os_ikepolicy['lifetime']['value'],
'perfect_forward_secrecy': os_ikepolicy['pfs'],
'mode': os_ikepolicy['phase1_negotiation_mode'],
'pre_shared_key': (
os_ipsec_site_connection['psk']
if os_ipsec_site_connection else
vpn_connection['pre_shared_key']),
}
os_ipsecpolicy = os_ipsecpolicies.get(vpn_connection['os_ipsecpolicy_id'])
if os_ipsecpolicy:
config_dict['ipsec_tunnel']['ipsec'] = {
'protocol': os_ipsecpolicy['transform_protocol'],
'authentication_protocol': os_ipsecpolicy['auth_algorithm'],
'encryption_protocol': os_ipsecpolicy['encryption_algorithm'],
'lifetime': os_ipsecpolicy['lifetime']['value'],
'perfect_forward_secrecy': os_ipsecpolicy['pfs'],
'mode': os_ipsecpolicy['encapsulation_mode'],
'tcp_mss_adjustment': (
os_ipsec_site_connection['mtu'] - MTU_MSS_DELTA
if os_ipsec_site_connection else
AWS_MSS),
}
return config_dict
def _stop_vpn_connection(neutron, vpn_connection):
connection_ids = vpn_connection['os_ipsec_site_connections']
for os_connection_id in six.itervalues(connection_ids):
try:
neutron.delete_ipsec_site_connection(os_connection_id)
except neutron_exception.NotFound:
pass
def _stop_gateway_vpn_connections(context, neutron, cleaner, vpn_gateway):
def undo_vpn_connection(context, vpn_connection, connections_ids):
vpn_connection['os_ipsec_site_connections'] = connections_ids
db_api.update_item(context, vpn_connection)
for vpn_connection in db_api.get_items(context, 'vpn'):
if vpn_connection['vpn_gateway_id'] == vpn_gateway['id']:
_stop_vpn_connection(neutron, vpn_connection)
connection_ids = vpn_connection['os_ipsec_site_connections']
vpn_connection['os_ipsec_site_connections'] = {}
db_api.update_item(context, vpn_connection)
cleaner.addCleanup(undo_vpn_connection, context, vpn_connection,
connection_ids)
def _update_vpn_routes(context, neutron, cleaner, route_table, subnets):
vpn_gateway = ec2utils.get_attached_gateway(
context, route_table['vpc_id'], 'vgw')
if not vpn_gateway:
return
_reset_vpn_connections(context, neutron, cleaner, vpn_gateway,
route_tables=[route_table], subnets=subnets)
def _reset_vpn_connections(context, neutron, cleaner, vpn_gateway,
subnets=None, route_tables=None,
vpn_connections=None):
if not vpn_gateway['vpc_id']:
return
# TODO(ft): implement search filters in DB api
vpn_connections = (vpn_connections or
[vpn for vpn in db_api.get_items(context, 'vpn')
if vpn['vpn_gateway_id'] == vpn_gateway['id']])
if not vpn_connections:
return
subnets = (subnets or
[subnet for subnet in db_api.get_items(context, 'subnet')
if subnet['vpc_id'] == vpn_gateway['vpc_id']])
if not subnets:
return
vpc = db_api.get_item_by_id(context, vpn_gateway['vpc_id'])
customer_gateways = {cgw['id']: cgw
for cgw in db_api.get_items(context, 'cgw')}
route_tables = route_tables or db_api.get_items(context, 'rtb')
route_tables = {rtb['id']: rtb
for rtb in route_tables
if rtb['vpc_id'] == vpc['id']}
route_tables_cidrs = {}
for subnet in subnets:
route_table_id = subnet.get('route_table_id', vpc['route_table_id'])
if route_table_id not in route_tables_cidrs:
route_tables_cidrs[route_table_id] = (
_get_route_table_vpn_cidrs(route_tables[route_table_id],
vpn_gateway, vpn_connections))
cidrs = route_tables_cidrs[route_table_id]
for vpn_conn in vpn_connections:
if vpn_conn['id'] in cidrs:
_set_subnet_vpn(
context, neutron, cleaner, subnet, vpn_conn,
customer_gateways[vpn_conn['customer_gateway_id']],
cidrs[vpn_conn['id']])
else:
_delete_subnet_vpn(context, neutron, cleaner, subnet, vpn_conn)
def _set_subnet_vpn(context, neutron, cleaner, subnet, vpn_connection,
customer_gateway, cidrs):
subnets_connections = vpn_connection['os_ipsec_site_connections']
os_connection_id = subnets_connections.get(subnet['id'])
if os_connection_id:
# TODO(ft): restore original peer_cidrs on crash
neutron.update_ipsec_site_connection(
os_connection_id,
{'ipsec_site_connection': {'peer_cidrs': cidrs}})
else:
os_connection = {
'vpnservice_id': subnet['os_vpnservice_id'],
'ikepolicy_id': vpn_connection['os_ikepolicy_id'],
'ipsecpolicy_id': vpn_connection['os_ipsecpolicy_id'],
'peer_address': customer_gateway['ip_address'],
'peer_cidrs': cidrs,
'psk': vpn_connection['pre_shared_key'],
'name': '%s/%s' % (vpn_connection['id'], subnet['id']),
'peer_id': customer_gateway['ip_address'],
'mtu': AWS_MSS + MTU_MSS_DELTA,
'initiator': 'response-only',
}
os_connection = (neutron.create_ipsec_site_connection(
{'ipsec_site_connection': os_connection})
['ipsec_site_connection'])
cleaner.addCleanup(neutron.delete_ipsec_site_connection,
os_connection['id'])
_add_subnet_connection_to_vpn_connection_item(
context, vpn_connection, subnet['id'], os_connection['id'])
cleaner.addCleanup(_remove_subnet_connection_from_vpn_connection_item,
context, vpn_connection, subnet['id'])
def _delete_subnet_vpn(context, neutron, cleaner, subnet, vpn_connection):
subnets_connections = vpn_connection['os_ipsec_site_connections']
os_connection_id = subnets_connections.get(subnet['id'])
if not os_connection_id:
return
_remove_subnet_connection_from_vpn_connection_item(
context, vpn_connection, subnet['id'])
cleaner.addCleanup(_add_subnet_connection_to_vpn_connection_item,
context, vpn_connection, subnet['id'], os_connection_id)
try:
neutron.delete_ipsec_site_connection(os_connection_id)
except neutron_exception.NotFound:
pass
def _get_route_table_vpn_cidrs(route_table, vpn_gateway, vpn_connections):
static_cidrs = [route['destination_cidr_block']
for route in route_table['routes']
if route.get('gateway_id') == vpn_gateway['id']]
is_propagation_enabled = (
vpn_gateway['id'] in route_table.get('propagating_gateways', []))
vpn_cidrs = {}
for vpn in vpn_connections:
if is_propagation_enabled:
cidrs = list(set(static_cidrs + vpn['cidrs']))
else:
cidrs = static_cidrs
if cidrs:
vpn_cidrs[vpn['id']] = cidrs
return vpn_cidrs
def _get_vpn_gateways_external_ips(context, neutron):
vpcs = {vpc['id']: vpc
for vpc in db_api.get_items(context, 'vpc')}
external_ips = {}
routers = neutron.list_routers(
tenant_id=context.project_id)['routers']
for router in routers:
info = router['external_gateway_info']
if info:
for ip in info['external_fixed_ips']:
if netaddr.valid_ipv4(ip['ip_address']):
external_ips[router['id']] = ip['ip_address']
return {vgw['id']: external_ips.get(vpcs[vgw['vpc_id']]['os_id'])
for vgw in db_api.get_items(context, 'vgw')
if vgw['vpc_id']}
def _add_cidr_to_vpn_connection_item(context, vpn_connection, cidr):
vpn_connection['cidrs'].append(cidr)
db_api.update_item(context, vpn_connection)
def _remove_cidr_from_vpn_connection_item(context, vpn_connection, cidr):
vpn_connection['cidrs'].remove(cidr)
db_api.update_item(context, vpn_connection)
def _add_subnet_connection_to_vpn_connection_item(context, vpn_connection,
subnet_id, os_connection_id):
vpn_connection['os_ipsec_site_connections'][subnet_id] = os_connection_id
db_api.update_item(context, vpn_connection)
def _remove_subnet_connection_from_vpn_connection_item(context, vpn_connection,
subnet_id):
del vpn_connection['os_ipsec_site_connections'][subnet_id]
db_api.update_item(context, vpn_connection)