Skip to content

Instantly share code, notes, and snippets.

@boomshadow
Last active August 15, 2017 14:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save boomshadow/dd7aa11d786c4147070c to your computer and use it in GitHub Desktop.
Save boomshadow/dd7aa11d786c4147070c to your computer and use it in GitHub Desktop.
OpenVPN Dynamic DNS with Amazon Route53

Info

This script is used to automatically update an AWS Route 53 zone as soon as a client connects to VPN. I needed this for a large number of IoT microcontroller boards (think Beagle board or Raspberry Pi). The devices would VPN into the network and I needed to access them based on their hostname.

This assumes you have a working OpenVPN server running. This script is called by OpenVPN's 'learn-address'. OpenVPN will pass 3 arguments like so:

[operation] [ip address] [common name]
update 192.168.1.5 vpn_client_hostname

Installation

Install Ruby and Files

I know that your package provider is probably giving an old version of Ruby (ex: 1.9), but it's fine. The script will still work. Using something like RVM is problematic.

apt-get install ruby
gem install aws-sdk

# Place your aws_config.yml and learn_address_route_53.rb in /etc/openvpn/
chmod +x /etc/openvpn/learn_address_route_53.rb

Edit learn script

Edit learn_address_route_53.rb with your:

  • Hosted Zone ID (@zone_id). You can get this info from the Route 53 web console.
  • Your domain name (@domain).

Edit OpenVPN conf

Add these 2 lines to the bottom of your OpenVPN server.conf:

script-security 3 system
learn-address /etc/openvpn/learn_address_route_53.rb

Make sure your openvpn server.conf is not set to user 'nobody:nobody'. It will have issues reading the learn_address script and aws_config file.

Testing

You can manually test the ruby script by running the following:

/etc/openvpn/learn_address_route_53.rb update 192.168.1.5 test-hostname

You can see that OpenVPN is successfully passing info to the learn script by checking the logs:

tail -f /var/log/syslog

Example of a successful 'learn-address'

Feb  9 20:01:15 vpn ovpn-server[15866]: jacob/73.x.x.x:7708 MULTI_sva: pool returned IPv4=172.18.0.6, IPv6=(Not enabled)
Feb  9 20:01:15 vpn ovpn-server[15866]: jacob/73.x.x.x:7708 MULTI: Learn: 172.18.0.6 -> jacob/73.x.x.x:7708
Feb  9 20:01:15 vpn ovpn-server[15866]: jacob/73.x.x.x:7708 MULTI: primary virtual IP for jacob/73.x.x.x:7708: 172.18.0.6

Further Reading

Special thanks goes to the following resources:

access_key_id: XXXXXXX
secret_access_key: XXXXXXX
#!/usr/bin/env ruby
require 'yaml'
require 'aws-sdk'
def load_configuration
# load AWS credentials from disk
creds = YAML.load(File.read('/etc/openvpn/aws_config.yml'))
@route53 = Aws::Route53::Client.new(
region: "us-east-1",
access_key_id: creds['access_key_id'],
secret_access_key: creds['secret_access_key']
)
# Hosted Zone ID for your-domain.com. You can get this info from the web console
@zone_id = "ZXXXXXXXXXX"
@domain = "your-domain.com"
end
def learn_address
if ARGV.size < 3
abort "USAGE: #{$0} (add|update|delete) ADDRESS CN"
end
operation, @ip_address, @common_name = ARGV
case operation
when "add", "update"
create_or_update_dns_record
else
abort "Unknown or uneeded operation #{operation}"
end
end
def create_or_update_dns_record
load_configuration
resp = @route53.change_resource_record_sets({
:hosted_zone_id => @zone_id,
:change_batch => {
:comment => "update VPN client",
:changes => [{
:action => "UPSERT", # UPSERT will update a record or create it if it doesn't exist
:resource_record_set => {
:name => "#{@common_name}.#{@domain}",
:type => "A",
:ttl => 300,
:resource_records => [{
:value => @ip_address
}]
}
}]
}})
end
learn_address
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment