Dynamic DNS with bind and nsupdate
March 24, 2017Last modified April 12, 2017
I have a home server with a dynamic IP-address. Previously I used an Asus router which comes with automatic dynamic DNS. This worked fine, but I kind of wanted to do this myself. So, I did it using bind and nsupdate.
I have a domain I use for my LAN, domain.tld
, and ext.domain.tld
points to my public
IP-address. I host my DNS with Underworld, and I
don't have rights to use nsupdate directly with them. So, I'll set up a
DNS-server on one of my servers, and let that server resolve lookups for the
subdomain ext.domain.tld
, and continue to let Underworld serve domain.tld
.
In this setup Underworld will be "super-master", cookie
(my main server) will
be master, and mrslave
will be the client (or, if you will, slave). Both
cookie
and mrslave
runs Ubuntu
16.04, but the configuration should be
pretty similar for older and newer versions of both Ubuntu and
Debian.
Creating a key-pair
To create a key-pair, we'll be using dnssec-keygen
.
dennis@cookie:/tmp$ dnssec-keygen -a HMAC-SHA512 -b 512 -n USER mrslave.domain.tld.
Kmrslave.domain.tld.+165+11930
This will give you two files. Kmrslave.domain.tld.+165+11930.private
and
Kmrslave.domain.tld.+165+11930.key
. the .key
-file will contain your public
key, and look like something like this:
mrslave.domain.tld. IN KEY 0 3 165 iYUzeD93iVtpXFik/6vH8TVnOUfo27k5a2gS4SYBXTQaSJUE/A7KhzXn nrFP2LeJ6nm9mfAA3cjzBGXV6yv9gA==
And the .private
-file will look like something like this:
Private-key-format: v1.3
Algorithm: 165 (HMAC_SHA512)
Key:
iYUzeD93iVtpXFik/6vH8TVnOUfo27k5a2gS4SYBXTQaSJUE/A7KhzXnnrFP2LeJ6nm9mfAA3cjzBGXV6yv9gA==
Bits: AAA=
Created: 20170324142537
Publish: 20170324142537
Activate: 20170324142537
As you might have noticed, both keys are the same, except for a space in the
.key
-file. You'll also notice that the Private-key-format
is v1.3
. With
v1.2
, we needed the .private
-file, but with v.1.3
we no longer need it. We
only need the key from the .key
-file, so you can just delete the
.private
-file, if you want to.
Installing and configuring bind9 on master
First things first. Let's install bind9
on the master server, and set it up so
that our server can answer requests for our subdomain.
dennis@cookie:~$ sudo apt install bind9 bind9utils bind9-doc
Now that bind is installed, we will have to configure it.
keys.conf
Let's start by creating a configuration-file to put keys in. Create
/etc/bind/keys.conf
, and include it in named.conf
by adding the following
line to the bottom on named.conf
:
include "/etc/bind/keys.conf";
Now we'll put the key we created (from the .key
-file, with the extra space)
earlier into keys.conf
.
key mrslave.domain.tld. {
algorithm HMAC-SHA512;
secret "iYUzeD93iVtpXFik/6vH8TVnOUfo27k5a2gS4SYBXTQaSJUE/A7KhzXn nrFP2LeJ6nm9mfAA3cjzBGXV6yv9gA=="
};
And then wee need to make sure the file is safe from prying eyes, and that bind can read the file.
dennis@cookie:~$ sudo chmod o-x /etc/bind/keys.conf
dennis@cookie:~$ sudo chgrp bind /etc/bind/keys.conf
named.conf.options
Add these two lines below directory "/var/cache/bind";
in
/etc/bind9/named.conf.options
:
recursion no;
allow-transfer { none; };
The file should then look something like this:
options {
directory "/var/cache/bind";
recursion no;
allow-transfer { none; };
dnssec-validation auto;
auth-nxdomain no; # conform to RFC1035
listen-on-v6 { any; };
};
named.conf.local
Now we have to add our zone to named.conf.local
, and create the zone-file.
Put the following into your named.conf.local
:
zone "ext.domain.tld" {
type master;
file "/etc/bind/pz/ext.domain.tld";
allow-update {
key mrslave.domain.tld.;
};
};
Using allow-update
here will allow the key mrslave.domain.tld
full access to the
zone ext.domain.tld
. If you want more fine-grained policies, you can use
update-policy
.
...
update-policy {
grant <key> <type> <zone> <record-types>;
};
So, if you only want to allow the key mrslave.domain.tld
to update the A
-record of
ext.domain.tld
, the file would look something like this:
zone "ext.domain.tld" {
type master;
file "/etc/bind/pz/ext.domain.tld";
update-policy {
grant mrslave.domain.tld. name ext.domain.tld. A;
};
};
Zone-file
If there are no errors, we can create our zone-file. We have to create the
directory /etc/bind/pz
, give bind permission to write to it, and place the
zone ext.domain.tld
there.
dennis@cookie:~$ sudo mkdir /etc/bind/pz
dennis@cookie:~$ sudo chgrp bind /etc/bind/pz
dennis@cookie:~$ sudo chmod g+w /etc/bind/pz
This is what my zone-file, ext.domain.tld
looks like:
$TTL 604800
@ IN SOA cookie.eriksen.im. webmaster.eriksen.im. (
2017032402 ; Serial
604000 ;
86400 ; Retry
2419200 ; Expire
604000 ) ; Negative Cache TTL
;
@ IN NS cookie.eriksen.im.
@ 600 IN A 127.0.0.1 ; TTL=600s
Apparmor
With this setup, when you start using nsupdate
, apparmor
will start
complaining with the following error:
Mar 25 17:21:46 cookie kernel: [4089644.355272] audit: type=1400 audit(1490458906.635:17): apparmor="DENIED" operation="mknod" profile="/usr/sbin/named" name="/etc/bind/pz/ext.domain.tld.jnl" pid=27119 comm="named" requested_mask="c" denied_mask="c" fsuid=131 ouid=131
In order to fix this, we have to give named
(bind) permission to write to the
directory /etc/bind/pz/
. We do this by inserting the line /etc/bind/pz/ rw,
right below /etc/bind/** r,
in the file /etc/apparmor.d/usr.sbin.named
. If
you've never edited this file, lines 19-24 should probably look something like
this:
/etc/bind/** r,
/etc/bind/pz/* rw,
/var/lib/bind/** rw,
/var/lib/bind/ rw,
/var/cache/bind/** lrw,
/var/cache/bind/ rw,
And then we reload apparmor.
dennis@cookie:~$ sudo systemctl reload apparmor.service
Testing
We can now check the configuration using check-nameconf
.
dennis@cookie:~$ check-nameconf
This will check named.conf.options
, named.local
, named.conf.default-zones
,
and named.conf
, as the three first are included in named.conf
.
We'll use named-checkzone
to test the zone-file.
dennis@cookie:~$ sudo named-checkzone ext.domain.tld /etc/bind/pz/ext.domain.tld
zone ext.domain.tld/IN: loaded serial 2017032402
OK
Alright. Now we've configured bind, and set up the zone. Now it's time to
restart bind, and see if it's working. Do a systemctl restart bind9
, and see
if it's working by testing it with dig
. It should look something like this:
dennis@cookie:~$ sudo systemctl restart bind9
dennis@cookie:~$ dig ext.domain.tld +short
127.0.0.1
Alright! We've got a working master DNS-server!
Setting up the client
First, we need to install nsupdate
. nsupdate
is part of the package
dnsutils
, so we'll install that.
dennis@mrslave:~$ sudo apt install dnsutils
"Configuring" nsupdate
When using nsupdate
, we'll need a key-file. As I mentioned earlier, the
.private
-file was needed when we were using Private-key-format v1.2
. Now the
key-file must be presented in bind
-format. So, we can just copy
/etc/bind/keys.conf
from earlier!
key mrslave.domain.tld. {
algorithm HMAC-SHA512;
secret "iYUzeD93iVtpXFik/6vH8TVnOUfo27k5a2gS4SYBXTQaSJUE/A7KhzXn nrFP2LeJ6nm9mfAA3cjzBGXV6yv9gA=="
};
Now that we've got the key-file, we can create a text-file containing the
update-commands we want to send. Let's call ut nsupdate.txt
, and it should
look like this:
server cookie.eriksen.im
zone ext.domain.tld
update delete ext.domain.tld. A
update add ext.domain.tld. 600 A 192.168.1.2
show
send
Testing
Now we can try to update DNS!
dennis@mrslave:~$ nsupdate -k mrslave.conf -v nsupdate.txt
Outgoing update query:
;; ->>HEADER<<- opcode: UPDATE, status: NOERROR, id: 0
;; flags:; ZONE: 0, PREREQ: 0, UPDATE: 0, ADDITIONAL: 0
;; ZONE SECTION:
;ext.domain.tld. IN SOA
;; UPDATE SECTION:
ext.domain.tld. 0 ANY A
ext.domain.tld. 600 IN A 192.168.1.2
The update was successfull. Now we can test to see if it actually works.
dennis@mrslave:~$ dig ext.domain.tld +short
192.168.1.2
Earlier, when creating /etc/bind/pz/ext.domain.tld
on the master, we set the
IP-address to 127.0.0.1
, so getting 192.168.1.2
in response now means that
it worked.
It worked!
Automating DNS-updates
As I mentioned earlier, this is my home-server, and I want it to update the DNS-record every time its IP-address updates. So, I have written a script that checks wether my public IP-address has changed, and then issues an update if it has.
I've uploaded the
script to my server,
so feel free to use it. Installing it is quite straight-forward. Put
nsupdate.sh
in /usr/local/sbin/
and configure it (you only need to change
the top four variables), and install the cron-file.