AWS Media Blog

Automate detecting GeoIP location of Client VPN users using Lambda function

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"
}

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).

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

 

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.

Abhishek Gupta

Abhishek Gupta

Abhishek is a Cloud Support Engineer and CloudWatch SME for Amazon Web Services. He helps global customers in designing, deploying and troubleshooting large-scale networks build on AWS. He specializes in VPN, VPC, Direct Connect and Cloudwatch.