Python Flask NordVPN Map Interface

While exploring some additional VPN options for work, I decided to give NordVPN a try. They have a plethora of exit nodes, and have generally decent user feedback and claims that they do not log traffic. NordVPN’s Linux client is essentially a zip file full of OpenVPN configuration files. There are some other projects for managing NordVPN connections, but I decided to have a quick go at creating a graphical bare bones interface using Python and Flask.

final result

We’ve already identified Python and Flask as our base framework. There’s a very useful package aptly named Flask Google Maps which provides a convenient interface for instancing and interacting with Google Maps. The interface won’t take much to patch together as we are keeping things bare bones. So, we’ll focus on parsing out the data that came with NordVPN into something that’s useful.

Note: You could reuse this code for essentially any OpenVPN provider that offers multiple configuration profiles.

We need to identify the IP addresses in each .ovpn file and then pull some geocoordinate information. In order to find the IP from each OpenVPN config file, we’ll look for lines that start with the word remove and then parse the line for the IP.

def get_ip_from_file(filename):
	with open(filename, 'r') as fd:
		for line in fd:
			if line.startswith('remote '):
				return line.split(' ')[1]
	return None

There’s a Python package named geoip2 that offers this functionality. We’ll go ahead and setup an environment for our project and grab some dependencies. We’ll also download a database from GeoLite2 to populate the geolocation data.

mkdir vpnmap && cd vpnmap
virtualenv -p python3 ./env
. ./env/bin/activate
pip install Flask geoip2 flask-googlemaps
wget http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz
tar -xvf GeoLite2-City.tar.gz

In order to pull latitude and longitude information from the downloaded database, we simply import geoip2.database.Reader, and point it to the GeoLite2 database like so.

from geoip2.database import Reader
reader = Reader('GeoLite2-City_20180206/GeoLite2-City.mmdb')
city = reader.city(ip)
print(city.location.latitude)
print(city.location.longitude)

Now, we to correlate the information from the OpenVPN config files with the coordinate data in a way that flask_googlemaps can parse it and display our nodes. From the flask_googlemaps documentation, we see the following example marker. We need to make a marker for each node from our config files with the associated coordinate data.

{
     'icon': 'http://maps.google.com/mapfiles/ms/icons/green-dot.png',
     'lat': 37.4419,
     'lng': -122.1419,
     'infobox': "<b>Hello World</b>"
 }

We can ignore the icon field as it just changes the physical appearance of the marker. We can place HTML formatted data in infobox, but for now let’s just make it the IP name. We’ll also populate the lat/lng.

def get_lat_long(ip):
	city = reader.city(ip)
	data = {}
	data['lat'] = city.location.latitude if city.location.latitude else 0
	data['lng'] = city.location.longitude if city.location.longitude else 0
	return data
	
def gen_json(directory):
	data = []
	configs = [f for f in listdir(directory) if isfile(join(directory, f)) and f.endswith('.udp1194.ovpn')]
	for config in configs:
      ip = get_ip_from_file('%s/%s' % (directory, config))
      lat_lng = get_lat_long(ip)
      if lat_lng['lat'] == 0 and lat_lng['lng'] == 0:
          continue
      data.append(lat_lng)
      data[-1].infobox = ip
	return json.dumps(data)

For a personal challenge, go ahead and look at the example code in the README.md file with flask_googlemaps and modify it to present the map data as it is being parsed with the gen_json function.

Grab the Code

git clone https://github.com/bitrot-sh/pyvpnmap.git
# Move config.json.example to config.json and edit the values accordingly
python view.py

I re-used some code from openvpn-manager, which is another neat project and may be useful to some of you. Enjoy.

comments powered by Disqus