Introduction

With the shift to a remote working environment, there has been a dramatic increase in the number of remote users connecting to AWS Client VPN to access resources inside of Amazon Virtual Private Cloud (Amazon VPC). Companies often want to know GeoIP Location of the Client VPN users so they can understand where these users are located geographically. This post shows you how to automate detection of the GeoIP location using a third party API, instead of checking each Public-IP manually.

Solution Overview

With a client handler, which invokes an AWS Lambda function, you can implement many custom solutions for your AWS Client VPN. This tutorial shows upi how to detect the city and country information of users’ Public IP using a third party GeoIP location API.

When a Client VPN user connects to Client VPN Endpoint, a Lambda function is invoked using the client handler setting on Client VPN. It sends these parameters to a Lambda Function:

The input to the Lambda function from the service uses JSON:

{ "connection-id": <connection ID>, "endpoint-id": <client VPN endpoint ID>, "common-name": <cert-common-name>, "username": <user identifier>, "platform": <OS platform>, "platform-version": <OS version>, "public-ip": <public IP address>, "client-openvpn-version": <client OpenVPN version>, "schema-version": "v1"
}

The Lambda function should return the following JSON to the service:

{ "allow": boolean, "error-msg-on-failed-posture-compliance": "", "posture-compliance-statuses": [], "schema-version": "v1"
}

Picture1 1

Workflow overview

  1. Client VPN user authenticates with Mutual Authentication based certificates.
  2. Client VPN Endpoint invokes the Lambda function.
  3. Lambda/Handler receives the request JSON and waits for response from API and return “True” or “False”.
  4. The VPN session is either allowed or denied.
  5. Handler logs the response in CloudWatch Logs with GeoIP location information based on Public IP used by Client to connect Client VPN.

For more detail on Client Handler, refer to this blog post: https://aws.amazon.com/blogs/networking-and-content-delivery/enforcing-vpn-access-policies-with-aws-client-vpn-connection-handler/

Pre-requirements

  • You must have a Client VPN already configured and running, as this is an additional configuration for an existing Client VPN.
  • A third party API to fetch the GeoIP detail based on the Client VPN user’s public IP.
  • Lambda functions must have internet access to send request to a third party API. If you have Lambda function in VPC, then you need a public or private subnet with internet access via internet gateway or NAT Gateway respectively.

Step 1: Create an access key or license key at third party GeoIP API provider

Choose any third party that provides GeoIP location API for public IP address like MaxMind, ipstack, or ipinfo. Create the access-id with them to send the request with public IP and retrieve the results.

Step 2: Create the Lambda function

To create a Lambda function

  1. Open the AWS Lambda console.
  2. Choose Create a function.
  3. For Function name, enter any name for your function (prefix with AWSClientVPN-)
  4. For Runtime, choose Python 3.8.
  5. Choose Create function. The following screenshot shows the create Lambda Function console.
    import json
  6. After creating the Lambda function, the configuration page opens. In the Function code section, enter the following Python code:
    import json
    import urllib3 def geolocation(public_ip, username, endpoint): http = urllib3.PoolManager() url = "http://ipinfo.io/"+ public_ip response = http.request('GET', url, retries = False) data = json.loads(response.data) data["endpoint"] = endpoint data["username"] = username print(data) return True def lambda_handler(event, context): allow = False error_msg = "User Authentication Failed" public_ip = event['public-ip'] username = event['username'] endpoint_id = event['endpoint-id'] allow = geolocation(public_ip, username, endpoint_id) return { "allow": allow, "error-msg-on-failed-posture-compliance": error_msg, "posture-compliance-statuses": [], "schema-version": "v1" } 
  7. Choose Save 

Step 3: Allow Client Handler on Client VPN

To modify a Client VPN endpoint:

  1. Open the Amazon VPC Management Console.
  2. In the navigation pane, choose Client VPN Endpoints.
  3. Select the Client VPN Endpoint to modify, choose Actions, and then choose Modify Client VPN Endpoint.
  4. For Client Connect Handler, choose Yes to allow the client connect handler to run custom code that allows or denies a new connection to the Client VPN endpoint. For Client Connect Handler ARN, specify the Amazon Resource Name (ARN) of the Lambda function (created in previous step).Picture2 1

Step 4: Result

  1. When a client user connects to Client VPN, Lambda logs the response to AWS/Lambda/<function-name>. Responses look like:

    {'ip': ‘Public-IP','hostname': '<hostname>, 'city': 'Dublin','region': 'Leinster', 'country': 'IE',

     'loc': '53.3331,-6.2489',

    'org': 'AS16509 Amazon.com, Inc.',

     'postal': 'D02',

    'timezone': 'Europe/Dublin',

    'readme': 'https://ipinfo.io/missingauth',

    'endpoint': 'cvpn-endpoint-xyz,

    'username': None

    }

  2. In CloudWatch Logs Insight, you can use the following query to display all user connected user with GeoIP location with timestamp.

    fields @timestamp, @message| filter @message like 'country'
    | display @timestamp, ip, city, country, username, endpoint

Picture3

 

How to allow access to Client VPN users from a particular country only

When creating the Lambda function (Step 1), use the following Lambda function, replacing the highlighted text with the country you want to allow access in. This example Lambda function python code only allows Client VPN users from the country of Ireland. (Country codes can be found in log-event in log-stream in Cloudwatch Lambda functoin Log-Group as we print (data) in the code). Each third party GeoAPI may have different results for GeoIP lookup based on public-ip.

import json
import urllib3 def geolocation(public_ip, username, endpoint): http = urllib3.PoolManager() url = "http://ipinfo.io/"+ public_ip response = http.request('GET', url, retries = False) data = json.loads(response.data) data["endpoint"] = endpoint data["username"] = username print(data) if data["country"]=="IE": return True else: return False def lambda_handler(event, context): allow = False error_msg = "User location restriction" public_ip = event['public-ip'] username = event['username'] endpoint_id = event['endpoint-id'] allow = geolocation(public_ip, username, endpoint_id) return { "allow": allow, "error-msg-on-failed-posture-compliance": error_msg, "posture-compliance-statuses": [], "schema-version": "v1" }

 

Summary

In this post, we showed you how to retrieve the city and country public IPs of Client VPN users connecting to endpoint. You can choose a third party GeoIP API of your choice in Lambda function python code. You can also allow users from a particular geographical location only in real time.

This solution is fully automated, and Lambda invocations correspond to the number of users (number of requests) connecting to Client VPN endpoint. Different use cases can be achieved by modifying the Lambda function for each Client VPN user’s request.

Learn more about AWS Lambda, a serverless compute service that lets you run code without provisioning or managing servers, creating workload-aware cluster scaling logic, maintaining event integrations, or managing runtimes.