Skip to main content

Lets Encrypt Behind The Firewall

·3 mins

Dehydrated is a client for getting certificates from an ACME server (think let’s encrypt). Being written in bash, its dependencies are quite simple: curl/sed/grep/mktemp. Dehydrated allows us to write simple hooks that, in this post, we’ll use to do dns validation against our authoritative bind server. That way, you can use let’s encrypt certificates even if your server is behind a firewall! (provided it has access to your dns authoritative server)

Prerequisites #

For the rest of this post, we’ll assume that the hostname of the server we’re executing dehydrated on is vm.xiu.io and the hostname for which we want a certificate is www.xiu.io.

useradd -d /home/letsencrypt -m
apt update
apt install curl bind9utils
mkdir /home/letsencrypt/nskey
cd /home/letsencrypt/nskey
dnssec-keygen -a HMAC-MD5 -b 512 -n HOST -C vm.xiu.io

We have to install bind9utils to be able to generate our dnssec key, this key will then be used to push updates to specific dns records to our authoritative server.

Install dehydrated #

su - letsencrypt
wget https://github.com/lukas2511/dehydrated/archive/master.tar.gz
tar xvf master.tar.gz
mv dehydrated-master dehydrated
cp dehydrated/docs/examples/config .

Then edit config with your preferred editor and change the following:

CHALLENGETYPE="dns-01"
[...]
BASEDIR=/home/letsencrypt
[...]
HOOK=/home/letsencrypt/hook/nsupdate.sh

Also, create a hook directory and then put the following in hook/nsupdate.sh:

#!/usr/bin/env bash
 
set -e
set -u
set -o pipefail
 
NSUPDATE="nsupdate -k /home/letsencrypt/nskey/<.key FILE WE JUST GENERATED>"
DNSSERVER="<IP OF THE AUTHORITATIVE SERVER>"
TTL=300
 
case "$1" in
    "deploy_challenge")
        ZONE=$(echo $2 | rev | cut -d'.' -f-2 | rev)
        printf "server %s\nzone %s.\nupdate add _acme-challenge.%s. %d in TXT \"%s\"\nshow\nsend\n" "${DNSSERVER}" "${ZONE}" "${2}" "${TTL}" "${4}" | $NSUPDATE
	sleep 30 # sleeping a bit to make sure changes have been propagated
        ;;
    "clean_challenge")
        ZONE=$(echo $2 | rev | cut -d'.' -f-2 | rev)
        printf "server %s\nzone %s.\nupdate delete _acme-challenge.%s. %d in TXT \"%s\"\nsend\n" "${DNSSERVER}" "${ZONE}" "${2}" "${TTL}" "${4}" | $NSUPDATE
        ;;
    "deploy_cert")
        # do nothing for now
        ;;
    "unchanged_cert")
        # do nothing for now
        ;;
    "startup_hook")
        ;;
    "exit_hook")
        ;;
    *)
        echo Unknown hook "${*}"
        exit 1
        ;;
esac
 
exit 0
chmod +x hook/nsupdate.sh

Configure bind #

Now that we have our key, we can tell bind to accept modifications from it. Edit /etc/bind/names.conf.local and add the following:

key "vm.xiu.io" {
	algorithm hmac-md5;
	secret "<CONTENT OF Kvm.xiu.io....private>"
};

Then find where your zone is configured (zone “domain.tld” {};) and add the following in it:

zone "xiu.io" {
[...]
	grant vm.xiu.io name _acme-challenge.www.xiu.io. TXT;
[...]
};

And reload bind:

rndc reload

Profit! #

Now add the certificate you need in domains.txt:

echo "www.xiu.io" > /home/letsencrypt/domains.txt

Tip: if you add multiple domains on the same line, then it’ll create a certificate with multiple subjectAltNames.

The first time you start dehydrated, it’ll ask for you to review and accept let’s encrypt TOS.

/home/letsencrypt/dehydrated/dehydrated -c -f /home/letsencrypt/dehydrated/config

Execute it a second time, if all goes well, you should get the following:

+ Creating fullchain.pem...
+ Done!

You can now find your certificates in /home/letsencrypt/certs/!

The -c option is meant to be executed regularly, it’ll renew certificates when needed. You can create a cronjob for that!