Dynamic DNS IP updating with node.js

By Matt. Filed in Home Server  |   
Tags: , , ,
Home del.icio.us this! Digg this! Share on Facebook! Share on LinkedIn! Stumble Upon this! Tweet this! Share on Reddit! RSS 2.0 

My situation isn’t an unusual one – I’ve got a server at home that I want to expose to the internet, but my ISP provides me with a dynamic IP address that changes every time my router reconnects. This doesn’t happen often (maybe once a month or so), but if it does change I still want to be able to access my machine remotely.

The usual course of action is to use a free service such as DynDNS – you choose a subdomain on one of the many domain names they have, and using any one of a number of software tools, you ping them whenever your IP changes. This works pretty well – in fact, my router (like many) has built-in support for DynDNS so the router itself will notify DynDNS when the IP address changes.

So why am I doing something different? Well, I’ve actually been using DynDNS for a few years, and there are a couple of downsides (all of these apply to their free service, and I’m sure you can remove these limitations by paying, but I’m too tight to do that):

  1. You have to use one of their subdomains, and then CNAME your DNS to it – actually there’s not much wrong with this, but I’d rather not have their subdomain in my DNS resolution track;
  2. You can’t ping them too frequently or they get sad (and cut you off), so you need to be sure you can tell when your IP has changed;
  3. If you don’t ping them often enough, you risk your account being suspended until you manually log in and reactivating it – this happened to me recently when my IP hadn’t changed for a few months.

But actually, none of the things I’ve just listed are persuasive enough to not use it. So again, why am I doing something different?

  1. I want my server, not my router, to be in charge of updating the DNS – no concrete reason for this, other than that I feel it’s logic that ought to be on my server where I can track/monitor/etc it more easily;
  2. I run my own DNS service on my VPS anyway, so I want to have control over all of it, not just all except the dynamic IP bit;
  3. Because I can.

And that’s pretty much the main reason – simply because I can. I just fancy having a go at it myself. Oh, and it’s another excuse to use node.js.

So, let’s set the scene:

  • Both my home server and VPS run node.js and nginx;
  • My VPS is using named/bind9 for DNS;
  • I want my home server to use a subdomain of the VPS domain, but with its own zone file so it can have “sub-subdomains” (e.g. *.home.matt-knight.co.uk).

There are loads of ways to do this, but I’ve been meaning to have a play around with is SSL client certificates, so I’m going to use them for this. I’ve looked at using SSL client certification for API authentication in the past, but never got round it it. Basically, when a client makes an HTTPS request, it provides a certificate and key to identify (authenticate and authorize) itself.

The plan is as follows:

  1. Create an SSL CA certificate, a server certificate and a client key;
  2. Set up a new SSL host in nginx on the VPS using the aforementioned SSL certificate;
  3. Enable (and enforce) client SSL certificate authentication in nginx;
  4. Expose a node.js app on the VPS running an HTTP server which will update the bind zone file and reload it;
  5. Write a node.js app to run on my home server and ping the VPS SSL endpoint at regular intervals – for example, once a minute.

As I say, there are probably certainly easier ways to do this (well, ultimately just use DynDNS!) but this should be fun! We’ll start with the SSL certificates.

SSL Certificates & Keys

A quick search on Google found Nathan Good’s web page: Client Side Certificate Auth in Nginx

Combining that with the nginx documentation on the HTTP SSL module, I created my certificates and removed the passphrase (this is important, otherwise you’ll have to enter the passphrase every time nginx is started). The code snippets below are copied from Nathan’s site, but also include extra steps to remove the passphrases – I also changed the signing to last for 10 years (3650 days) not 1 year (365 days) because it’s fine in this case.

Note, when you’re creating the server certificate, you should set the Common Name (CN) to the SSL host you’re going to use in nginx (e.g. dnsping.example.com). When you’re creating the client certificate, you should set the Common Name (CN) to the hostname you want to set an IP for (e.g. home.matt-knight.co.uk in my case) – you’ll see why later on.

  1. # Create the CA Key and Certificate for signing Client Certs
  2. openssl genrsa -des3 -out ca.key 4096
  3. openssl req -new -x509 -days 365 -key ca.key -out ca.crt
  4.  
  5. # Remove the passphrase from the CA key
  6. cp ca.key ca.key.org
  7. openssl rsa -in ca.key.org -out ca.key
  8. unlink ca.key.org
  9.  
  10. # Create the Server Key, CSR, and Certificate
  11. openssl genrsa -des3 -out server.key 1024
  12. openssl req -new -key server.key -out server.csr
  13.  
  14. # Remove the passphrase from the server key
  15. cp server.key server.key.org
  16. openssl rsa -in server.key.org -out server.key
  17. unlink server.key.org
  18.  
  19. # Self-sign the server certificate
  20. openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt
  21.  
  22. # Create the Client Key and CSR
  23. openssl genrsa -des3 -out client.key 1024
  24. openssl req -new -key client.key -out client.csr
  25.  
  26. # Remove the passphrase from the client key
  27. cp client.key client.key.org
  28. openssl rsa -in client.key.org -out client.key
  29. unlink client.key.org
  30.  
  31. # Sign the client certificate with our CA cert.  Unlike signing our own server cert, this is what we want to do.
  32. openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt
# Create the CA Key and Certificate for signing Client Certs
openssl genrsa -des3 -out ca.key 4096
openssl req -new -x509 -days 365 -key ca.key -out ca.crt

# Remove the passphrase from the CA key
cp ca.key ca.key.org
openssl rsa -in ca.key.org -out ca.key
unlink ca.key.org

# Create the Server Key, CSR, and Certificate
openssl genrsa -des3 -out server.key 1024
openssl req -new -key server.key -out server.csr

# Remove the passphrase from the server key
cp server.key server.key.org
openssl rsa -in server.key.org -out server.key
unlink server.key.org

# Self-sign the server certificate
openssl x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

# Create the Client Key and CSR
openssl genrsa -des3 -out client.key 1024
openssl req -new -key client.key -out client.csr

# Remove the passphrase from the client key
cp client.key client.key.org
openssl rsa -in client.key.org -out client.key
unlink client.key.org

# Sign the client certificate with our CA cert.  Unlike signing our own server cert, this is what we want to do.
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client.crt

Now we’ve got our certificates and keys. The client certificate/key need to be on my home server, and the server certificate/key and CA certificate need to be on my VPS. Obviously all of the keys need to be kept safe. Next, it’s time to set up nginx.

Configuring nginx

All we really need to do here is to create a new SSL host.

  1. upstream dnsping {
  2.     server unix:/tmp/dnsping.socket; # our node.js app will listen on this socket - /tmp/dnsping.socket is just an example
  3. }
  4.  
  5. server {
  6.     listen 443 ssl;
  7.  
  8.     server_name dnsping.example.com; # change this to your real hostname
  9.  
  10.     access_log /var/log/nginx/access.log; # change this to wherever you want to store access logs
  11.     error_log /var/log/nginx/error.log;   # change this to wherever you want to store error logs
  12.  
  13.     ssl_certificate        /etc/nginx/ssl/server.crt;
  14.     ssl_certificate_key    /etc/nginx/ssl/server.key;
  15.     ssl_client_certificate /etc/nginx/ssl/ca.crt;
  16.     ssl_verify_client      on;
  17.  
  18.     location / {
  19.         include proxy_params;
  20.  
  21.         proxy_pass http://dnsping/; # this references the upstream defined above
  22.  
  23.         proxy_set_header X-Verified $ssl_client_verify;
  24.         proxy_set_header X-DN $ssl_client_s_dn;
  25.     }
  26. }
upstream dnsping {
    server unix:/tmp/dnsping.socket; # our node.js app will listen on this socket - /tmp/dnsping.socket is just an example
}

server {
    listen 443 ssl;

    server_name dnsping.example.com; # change this to your real hostname

    access_log /var/log/nginx/access.log; # change this to wherever you want to store access logs
    error_log /var/log/nginx/error.log;   # change this to wherever you want to store error logs

    ssl_certificate        /etc/nginx/ssl/server.crt;
    ssl_certificate_key    /etc/nginx/ssl/server.key;
    ssl_client_certificate /etc/nginx/ssl/ca.crt;
    ssl_verify_client      on; 

    location / {
        include proxy_params;

        proxy_pass http://dnsping/; # this references the upstream defined above

        proxy_set_header X-Verified $ssl_client_verify;
        proxy_set_header X-DN $ssl_client_s_dn;
    }
}

You can run nginx -t to make sure it is all valid, and then tell nginx to reload the config:

  1. kill -HUP `cat /var/run/nginx.pid`
kill -HUP `cat /var/run/nginx.pid`

So, what is nginx going to do?

  1. It will listen for requests to https://dnsping.example.com;
  2. It will check for the presence of a valid SSL client certificate and reject the request if not found;
  3. It will set a header of X-Verified: SUCCESS if all is good;
  4. It will set the SSL Distinguished Name (DN) into a X-DN header;
  5. It will then forward the request to a UNIX socket at /tmp/dnsping.socket

That wasn’t too hard was it! Now onto the node.js app that will run on the VPS and listen to /tmp/dnsping.socket.

Next time….

Leave a Reply

Your email address will not be published. Required fields are marked *

*