Saturday, October 12, 2013

Dynamic Subdomains with OpenVPN and PyTinyDNS

Introduction

As I have covered in a previous post about PyTinyDNS there are multiple uses for a dynamic DNS service like this. One of my side projects is hosting a private Virtual Private Network (VPN). Along with hosting my own TLDs, I also wanted to have a custom solution for dynamically assigning a subdomain to each individual client that connects to the network. This way if the user happens to get assigned a different IP, others on the net will be able to easily connect to their services without any issues.

Redis

PyTinyDNS uses redis to make dynamic additions of domains and subdomains possible without adding extra config files or restarting the daemon. PyTinyDNS also comes with an import script that allows you to add either a text file full of domains or individual domains via the command line arguments. We'll strip the functionality from the import tool and use it to assign dynamic subdomains through OpenVPN's scripting features.

OpenVPN

OpenVPN comes with built-in options to add custom scripts that are triggered by multiple events. The following are example events that you can configure in order to take advantage of endless custom solutions.

  • --up (Executed after TCP/UDP socket bind and TUN/TAP open.)
  • --tls-verify (Executed when we have a still untrusted remote peer.)
  • --ipchange (Executed after connection authentication, or remote IP address change.)
  • --client-connect (Executed in --mode server mode immediately after client authentication.)
  • --route-up (Executed after connection authentication, either immediately after, or some number of seconds after as defined by the --route-delay option.)
  • --client-disconnect (Executed in --mode server mode on client instance shutdown.)
  • --down (Executed after TCP/UDP and TUN/TAP close.)
  • --learn-address (Executed in --mode server mode whenever an IPv4 address/route or MAC address is added to OpenVPN's internal routing table.)
  • --auth-user-pass-verify (Executed in --mode server mode on new client connections, when the client is still untrusted.)

The only event that we need to watch in order to add custom A PTRs to PyTinyDNS is --client-connect. Go ahead and create a directory to store the custom script in.

$ mkdir /etc/openvpn/scripts

Now at the bottom of your OpenVPN server.conf, add the following lines.

script-security 2
client-connect '/usr/bin/python /etc/openvpn/scripts/connect.py'

The Script

The following is a quick Python script used to add the correct subdomain based on the user's common name used in the client's certificate.

#!/usr/bin/python
import redis
import os
 
def insert_record(domain, ip, redis_server):
 r_server = redis.Redis(redis_server)

 try:
  r_server.hset('pytinydns.domains', domain, ip) 
 except:
  pass

def main():
 redis_server = 'localhost'

 try:
  insert_record(os.environ['common_name'] + ".myvpn.net.",os.environ['ifconfig_pool_remote_ip'],redis_server) 
 except:
  pass

 return 0

if __name__ == '__main__':
 main()

The reason for the pass statements is that we must return the value 0 or OpenVPN will deny the client entry. If records are not being added, check to make sure that the server is running and that it is in fact running on localhost.

Now you need to restart OpenVPN in order for the changes to server.conf to go into effect.

$ sudo service openvpn restart

Considerations

Using the default configs with PyTinyDNS, non locally resolved domains are forwarded to the system's default DNS server. In order to avoid information leakage of local common names, you could implement a method to not forward any requests with a particular domain name or disable this option completely by setting the PyTinyDNS option "Resolve_Nonmatch" to no.

1 comment: