import socket import boto3 from botocore.exceptions import ClientError import logging
logger = logging.getLogger() logger.setLevel(logging.INFO)
def delete_route(client, vpn_endpoint, subnet, cidr):
client.delete_client_vpn_route( ClientVpnEndpointId=vpn_endpoint, TargetVpcSubnetId=subnet, DestinationCidrBlock=cidr, )
def create_route(client, event, cidr):
client.create_client_vpn_route( ClientVpnEndpointId=event['ClientVpnEndpointId'], DestinationCidrBlock=cidr, TargetVpcSubnetId=event['TargetSubnet'], Description=f"cfnvpn auto generated route for endpoint {event['Record']}. {event['Description']}" )
def revoke_route_auth(client, event, cidr, group = None):
args = { 'ClientVpnEndpointId': event['ClientVpnEndpointId'], 'TargetNetworkCidr': cidr } if group is None: args['RevokeAllGroups'] = True else: args['AccessGroupId'] = group client.revoke_client_vpn_ingress(**args)
def authorize_route(client, event, cidr, group = None):
args = { 'ClientVpnEndpointId': event['ClientVpnEndpointId'], 'TargetNetworkCidr': cidr, 'Description': f"cfnvpn auto generated authorization for endpoint {event['Record']}. {event['Description']}" } if group is None: args['AuthorizeAllGroups'] = True else: args['AccessGroupId'] = group client.authorize_client_vpn_ingress(**args)
def get_routes(client, event):
response = client.describe_client_vpn_routes( ClientVpnEndpointId=event['ClientVpnEndpointId'], Filters=[ { 'Name': 'origin', 'Values': ['add-route'] } ] ) routes = [route for route in response['Routes'] if event['Record'] in route['Description']] logger.info(f"found {len(routes)} exisiting routes for {event['Record']}") return routes
def get_rules(client, vpn_endpoint, cidr):
response = client.describe_client_vpn_authorization_rules( ClientVpnEndpointId=vpn_endpoint, Filters=[ { 'Name': 'destination-cidr', 'Values': [cidr] } ] ) return response['AuthorizationRules']
def handler(event,context):
# DNS lookup on the dns record and return all IPS for the endpoint try: cidrs = [ ip + "/32" for ip in socket.gethostbyname_ex(event['Record'])[-1]] logger.info(f"resolved endpoint {event['Record']} to {cidrs}") except socket.gaierror as e: logger.exception(f"failed to resolve record {event['Record']}") return 'KO' client = boto3.client('ec2') routes = get_routes(client, event) for cidr in cidrs: route = next((route for route in routes if route['DestinationCidr'] == cidr), None) # if there are no existing routes for the endpoint cidr create a new route if route is None: try: create_route(client, event, cidr) if 'Groups' in event: for group in event['Groups']: authorize_route(client, event, cidr, group) else: authorize_route(client, event, cidr) except ClientError as e: if e.response['Error']['Code'] == 'InvalidClientVpnDuplicateRoute': logger.error(f"route for CIDR {cidr} already exists with a different endpoint") continue raise e # if the route already exists else: logger.info(f"route for cidr {cidr} is already in place") # if the target subnet has changed in the payload, recreate the routes to use the new subnet if route['TargetSubnet'] != event['TargetSubnet']: logger.info(f"target subnet for route for {cidr} has changed, recreating the route") delete_route(client, event['ClientVpnEndpointId'], route['TargetSubnet'], cidr) create_route(client, event, cidr) logger.info(f"checking authorization rules for the route") # check the rules match the payload rules = get_rules(client, event['ClientVpnEndpointId'], cidr) existing_groups = [rule['GroupId'] for rule in rules] if 'Groups' in event: # remove expired rules not defined in the payload anymore expired_rules = [rule for rule in rules if rule['GroupId'] not in event['Groups']] for rule in expired_rules: logger.info(f"removing expired authorization rule for group {rule['GroupId']} for route {cidr}") revoke_route_auth(client, event, cidr, rule['GroupId']) # add new rules defined in the payload new_rules = [group for group in event['Groups'] if group not in existing_groups] for group in new_rules: logger.info(f"creating new authorization rule for group {rule['GroupId']} for route {cidr}") authorize_route(client, event, cidr, group) else: # if amount of rules for the cidr is greater than 1 when no groups are specified in the payload # we'll assume that all groups have been removed from the payload so we'll remove all existing rules and add a rule for allow all if len(rules) > 1: logger.info(f"creating an allow all rule for route {cidr}") revoke_route_auth(client, event, cidr) authorize_route(client, event, cidr) # clean up any expired routes when the ips for an endpoint change expired_routes = [route for route in routes if route['DestinationCidr'] not in cidrs] for route in expired_routes: logger.info(f"removing expired route {route['DestinationCidr']} for endpoint {event['Record']}") try: revoke_route_auth(client, event, route['DestinationCidr']) except ClientError as e: if e.response['Error']['Code'] == 'InvalidClientVpnEndpointAuthorizationRuleNotFound': pass else: raise e try: delete_route(client, event['ClientVpnEndpointId'], route['TargetSubnet'], route['DestinationCidr']) except ClientError as e: if e.response['Error']['Code'] == 'InvalidClientVpnRouteNotFound': pass else: raise e return 'OK'