Route53 From First Principles

Posted on Sun 28 November 2021 in technology

One of my favorite AWS services -- and I have a lot of them! -- has to be Route53: it makes programmatically maintaining DNS entries for dynamic services extremely simple.

But what if you don't have access to AWS?

Enter BIND

As it turns out, all of the tools to recreate Route53 are available via bind. Installation on FreeBSD is easy:

pkg install bind911

You can correct the horrible mistake FreeBSD made in removing nslookup from the base system by installing bind-tools; don't intall it if you can get by with drill:

pkg install bind-tool

Follow any the a million tutorials out there to get a basic DNS server up and running. In my case, I defined a zone in /usr/local/etc/namedb/dynamic/anserinae.net.db for anserinae.net:

$ORIGIN .
$TTL 604800     ; 1 week
anserinae.net           IN SOA  installation01.anserinae.net. administrator.anserinae.net. (
                                4          ; serial
                                604800     ; refresh (1 week)
                                86400      ; retry (1 day)
                                2419200    ; expire (4 weeks)
                                604800     ; minimum (1 week)
                                )
                        NS      ns1.
                        A       208.97.148.219 ; how to resolve a naked lookup for anserinae.net
                        MX      10 mxe.    ; mailserver
$ORIGIN anserinae.net.

Add the zone definition to the list of zones served by bind in /usr/local/etc/namedb/named.conf.local; the following configuration also allows hosts in 192.168.7.0/24 to update the zone definition via RPC calls:

acl "AllowUpdateIPs" {
    192.168.7.0/24;
};

zone "anserinae.net" {
    type master;
    file "/usr/local/etc/namedb/dynamic/anserinae.net.db";
    allow-update{ AllowUpdateIPs; };
};

RPC calls are made via the rndc mechanism, and are authenticated using a pre-shared key. Simply installing the bind should have generated a key in /usr/local/etc/namedb/rndc.key, but just in case it didn't you can create one as follows:

key "rndc-key" {
        algorithm hmac-md5;
        secret "<your super secret md5 key>";
};

Plug your zone definition and rndc key into the main /usr/local/etc/namedb/named.conf by adding the following to the bottom of the file:

include "/usr/local/etc/namedb/rndc.key";
include "/usr/local/etc/namedb/named.conf.local";

Finally, rig named to run at startup by editing /etc/rc.conf:

named_enable="YES"

At this point restart your machine or start named to get the service running:

service named start

Updating BIND Remotely

Now that we have a bind server that can be updated remotely, we use our favorite programming language (Python, doi) to update it. First, a few preliminaries:

pip-3.8 install dnspython

Supposing we want to create a new alias forgedcharity.anserinae.net that resolves to 192.168.0.150 on a server running at 192.168.0.1, we can execute the following:

from dns import query, update, tsigkeyring
from dns.tsig import HMAC_MD5

bindhost='192.168.0.1'
ip='192.168.0.150'

keyring = tsigkeyring.from_text({
    'rndc-key' : '<your super secrete md5 key>'
})

update = update.Update('anserinae.net.', keyring=keyring, keyalgorithm=HMAC_MD5)
update.replace('forgedcharity', 1, 'A', ip)

response = query.tcp(update, bindhost, timeout=10)

If we check the zone file at /usr/local/etc/namdb/dynamic/anserinae.net.db, we now see:

$ORIGIN .
$TTL 604800     ; 1 week
anserinae.net           IN SOA  installation01.anserinae.net. administrator.anserinae.net. (
                                4          ; serial
                                604800     ; refresh (1 week)
                                86400      ; retry (1 day)
                                2419200    ; expire (4 weeks)
                                604800     ; minimum (1 week)
                                )
                        NS      ns1.
                        A       208.97.148.219
                        MX      10 mxe.
$ORIGIN anserinae.net.
$TTL 1  ; 1 second
forgedcharity           A       192.168.0.150

At this point, carrying out an nslookup for forgedcharity.anserinae.net should resolve correctly to the appropriate IP. Hooray!

Should I Do This In Production?

Probably not. Getting a barebones nameserver up and running is easy, but justifying the cost and complexity of deploying something like this when Route53 is available is difficult. As always, production turns fun proofs of concept into monstrosities... why not just let someone else do the heavy lifting for you?