Using Dynamic DNS with Digital Ocean Firewalls

How far down the rabbit hole are you willing to go?
Photo by Compare Fibre on Unsplash

My Broadband provider does not support fixed IP addresses. So for a long time now, I’ve been regularly updating my Digital Ocean firewall to grant SSH access to my local network. A chance conversation with my brother introduced me to dynamic DNS … and a rabbit hole that I thought worthy of a quick write up!

At first I thought that all I need was a dynamic DNS setup, and a means to automatically update what that pointed at when my IP address changed. However, it turns out that Digital Ocean firewalls do not support domain names in the rules, only IP addresses. So what I actually need is:

  1. A domain name that will point to my IP address
  2. An automated means of updating the IP address of that domain.
  3. A way of getting the IP address from the domain name, and updating the Digital Ocean firewall with any change.

This is definitely the easy bit as there are plenty of providers for such things.

I must admit I didn’t spend a bunch of time comparing options, dynu.com looks a bit dated, but it has a free tier, and fairly solid reputation, and I didn’t bother looking further than that. However, I did decide that I didn’t want to run their standard OS X client, so I instead setup ddclient on my laptop. They do provide fairly comprehensive documentation for this. Assuming you have homebrew:

brew install ddclient

then edit the config file:

vi /usr/local/etc/ddclient/ddclient.conf

For dynu:

# ddclient configuration for Dynu
#
# /etc/ddclient.conf
#daemon=60 # Comment this line for OS X
syslog=yes # Log update msgs to syslog.
mail=root # Mail all msgs to root.
mail-failure=root # Mail failed update msgs to root.
use=web, web=checkip.dynu.com/, web-skip='IP Address' # Get ip from server.
server=api.dynu.com # IP update server.
protocol=dyndns2
login=<username>
password=<password> # Password or MD5/SHA256 of password.
<ddns name> # List one or more hostnames one on each line.

You can run it manually to check your configuration with the following command:

sudo ./usr/local/opt/ddclient/sbin/ddclient -verbose -noquiet

The daemon is commented out because you need to use LaunchDaemon on OSX:

sudo cp -fv /usr/local/opt/ddclient/*.plist /Library/LaunchDaemons sudo chown root /Library/LaunchDaemons/homebrew.mxcl.ddclient.plist sudo launchctl load /Library/LaunchDaemons/homebrew.mxcl.ddclient.plist

When I was testing, I did discover that there’s a cache that ddclient generates … if you are mucking around and want to ensure changes are propagating properly, you’ll want to get rid of it:

rm /usr/local/var/run/ddclient/ddclient.cache

Better minds than mine have looked at this problem before, and I quickly found Chris Wiegman’s post on the subject. Unfortunately I hit a bit of a snag using his script “as-is”, another significant motivator behind writing this post.

The basic idea is to set up a machine to regularly resolve the IP address of your Dynamic DNS, and then use the Digital Ocean API to update the firewall when necessary.

The first step was to create a new firewall on DO with a single empty rule, and then I chose to install the DO API tools on one of my existing droplets. If you don’t have snap there are lots of good instructions available:

sudo snap install doctl

Then authenticate by running

doctl auth init

and following the instructions (you’ll need an API token) it gives you.

Then we need to get the ID of the firewall we’re going to keep up to date, you can get the details of all your firewalls with the following:

doctl compute firewall list

Here comes the deviation from Chris’s setup. Just using the update command to set the inbound rule on your firewall will remove any servers you’ve applied the firewall to. So I’ve updated Chris’s script to retrieve the current list of servers currently setup on the firewall, and include that list in the update command.

The full script for this is as follows:

#!/bin/bash

start=`date +"%Y-%m-%d %T"`
firewallId=<firewall-id>

add_rule() {
local ip=$1
currentDroplets=$(doctl compute firewall get $firewallId -o=json | jq -r '(.[0].droplet_ids) | @csv')
doctl compute firewall update $firewallId --name DynamicDns --inbound-rules "protocol:tcp,ports:22,address:${ip}" --droplet-ids "${currentDroplets}"
echo "${start} Added rules for ${ip} and maintained droplet Ids ${currentDroplets}"
}


host=<hostname>

ip=$(dig +short $host | tail -n 1)

if [ -z ${ip} ]; then
echo "${start} Failed to resolve the ip address of ${host}." 1>&2
exit 1
fi

add_rule $ip

Addendum 2021–01–08: I noticed that on my setup, crontab was failing to find the doctl command, I have updated my script to use the full path to the command, which in my case was /snap/bin/doctl

You can then setup a cron to run the script as regularly as you like, although as Chris points out, you may need to bear in mind any API rate limiting that DO has in place.

Big thanks to Chris for his original post, and for asking me to give him somewhere to point to for this update!

just some guy, y'know?

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store