Simple and secure ssh based dynamic DNS update

Summary

This article describes how to automate remote updates of a dynamic DNS entry through SSH.

Introduction

My home network is connected to the Internet through a cable modem with an ISP DHCP assigned external IP address. I like to access the home network when I am away from home but can never remember whatever long-and-fairly-meaningless-hostname is currently assigned to my gateway. Of course, I don't even try to remember the IP address. This is, of course, the very problem that DNS is designed to solve: I just need to create a memorable DNS hostname entry for the current IP address, and make sure to keep the record synchronized then the address changes.

Solution

The problem of maintaining DNS entries for dynamic IP addresses is a well known one and there are solutions, both commercial and otherwise, for it. In this case, since I also control a DNS server on another network, I had another solution ready: using openssh as a secure communication channel to update the remote DNS tables whenever the external IP address of the gateway/filewall machine changed. Since the whole process is automated, it is necessary to use passwordless authentication keys with SSH rather than a password. To reduce the security risk, two extra steps are required:

Key options

The scope of an Openssh authentication key can be controlled by the use of options in the public key. In addition to disabling several client actions, the options can also specify the command to be run. This command is then run instead of the client requested command, if any. In this case, our client merely needs to connect and authenticate itself and does not even need to specify a command to execute.

Setting a user account

While setting session options is an important security measure, it does not address the issue of the user account that actually runs the command. Normally, the root user controls the DNS tables, but it is dangerous to run such processes under that username. The correct solution is to create a user account specifically for the task. In this case, I used the username 'dnsadmin' Briefly, the steps required are:

  1. Create the account. On a redhat based system, I said

    $ adduser dnsadmin -s /bin/sh -d /etc/tinydns/root
    

  2. Change ownership of the DNS data files to the dnsadmin user .begin_code $ chown -R dnsadmin:dnsadmin /etc/tinydns/root

  3. Create an .ssh directory in ~dnsadmin

    $ su dnsdmin -c 'mkdir /etc/tinydns/root/.ssh'
    

  4. Copy the generated public key to /etc/tinydns/root/.ssh/authorized_keys See the following section on how to create the key..

    1. Create an ssh key pair without a password with the command:

      $ ssh-keygen -t dsa -N "" -f dns.key
      

      In this case, the key type is dsa and there is no password on the key. Obviously this is a security issue, but it is strongly mitigated by the limits placed by the public key options. By specifying a private key output filename of "dns.key", I also get a file, "dns.key.pub", which contains the corresponding public key.

    2. Edit the public key file and prepend the ssh options to prevent login or unwarranted access:

      command="/usr/bin/perl /etc/tinydns/dynupdate",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty
      

      These options increase the security of the key tremendously. If the key were compromised, an attacker could only use it to connect to my machine and run a fairly harmless program with no side-effects.

    3. Copy the private key file dns.key to the gateway machine and place it in a secure location. I put mine in /root/.ssh/dns.key

    4. Copy the edited public key file dns.key.pub to the DNS machine and append it to /root/.ssh/authorized_keys, unless you are using a service specific username, in which case, the file is copied to, in this case, /etc/tinydns/root/.ssh/authorized_keys.

    5. The file "/etc/tinydns/dynupdate", which is a small perl script, does the update. I use djbns which has a line oriented configuration file that is particularly amenable to scripting. The script extracts the client's IP address from the conveniently handy environment variable SSH_CONNECTION and writes the appropriate tinydns configuration line (in this case an A record specification) to a file. The script then changes into the tinydns config directory and runs make, which merges the dynamic and static data into a file that is then compiled into cdb format for tinydns. The script, which would be even shorter without the error checking and safe file handling, is included here in its entirety:

      #!/usr/bin/perl
      
      use strict;
      
      my $d='/etc/tinydns/root';
      my $host = 'ext.praxis-sw.com';
      my $ttl = 1800;
      my $e;
      
      unless ($e = $ENV{SSH_CONNECTION}){
        die "cannot get connection environment variable\n";
      }
      
      if (my ($client_ip) = $e =~ /^(\S+)/){
              my $f=qq[$d/dynamic.data];
              my $t=$f . 'tmp';
              open(F, ">$t") or die "cannot open file $t. $!\n";
              print F qq[=$host:${client_ip}:$ttl\n];
              close F or die "cannot close file $t. $!\n";
              rename $t, $f or die "cannot rename file $t to $f. $!\n";
              chdir($d) or die "cannot chdir to $d. $!\n";
              exec '/usr/bin/make -s';
      
      }
      else {
              warn "cannot get client ip\n";
              exit 1;
      }
      
    6. The Makefile is small but useful.:

      data.cdb: static.data dynamic.data
              test -e data && chmod 0600 data
              ( echo "#Do not edit. Generated data!"; cat static.data dynamic.data ) > data
              chmod 0400 data
              /usr/local/bin/tinydns-data
      
    7. Now when I make an ssh connection from the gateway machine to the DNS machine using this key, the server updates the DNS entry and assigns the hostname ext.praxis-sw.com to the client's IP address.

    8. The final step is to add the ssh connection line:

      ssh -i /root/.ssh/dns.key -n joad.praxis-sw.com
      

      to the dhcp scripts. I use dhcpcd which executes the script /etc/dhcpcd/<interface>.exe whenever state changes so I put the line there.

    Conclusion

    With this simple and secure DNS updating process running, I can go anywhere and be sure that ext.praxis-sw.com points to my home network. I hope you have found this information useful. I would appreciate feedback or comments to improve it.