Let's Encrypt Everything!
I’ve been looking into migrating to Let’s Encrypt for a while now, but due to my server setup for some reason the webroot method just wasn’t working for me (and is ugly in general). For a very long time I’ve always validated my domains ownership via a DNS TXT record and I find that much more elegant (personal preference).
I didn’t realize Let’s Encrypt had a --preferred-challenges dns
option, which as far as I know only works with the manual plugin. I tried it. It was ugly. The reason being it was, obviously, a manual process. Therefore every time you run the certbot
command you need to manually edit your TXT record with the value that will be generated.
I usually tend to stay away from third-party solutions, but from the sparse documentation I’ve seen dehydrated seemed like the way to go. It seems pretty well vetted so I’ll go with that. Here’s how my setup ended up looking like.
git clone https://github.com/certbot/certbot.git /opt/certbot
git clone https://github.com/lukas2511/dehydrated.git /opt/dehydrated
# I lied, I first forked the above repo and cloned my fork because no real reasons
cd /opt/dehydrated
git clone https://github.com/NatsumiHoshino/letsencrypt-cloudflare-hook.git hooks/cloudflare
pip install -r hooks/cloudflare/requirements.txt
Next, I simply need to create a bash script for each domain I want to validate. It would look something like:
#!/usr/bin/env bash
export CF_EMAIL='<cloudflare email>'
export CF_KEY='<cloudflare key>'
/opt/dehydrated/dehydrated \
--cron \
--domain mydomain.example.com \
--challenge dns-01 \
--hook '/opt/dehydrated/hooks/cloudflare/hook.py'
Then this script can be run on as a service (my preferred method) or as a cron job.
However this is where it gets interesting. I have some devices on which it is not easy to install the Let’s Encrypt client, such as my router or printer (because why not?). However, with this approach, I can validate ANY domain I own. For my router, I append the following to the above script:
if [ /mnt/my-cifs-share/cert.pem -ot /opt/dehydrated/certs/router.example.com/fullchain.pem ]; then
logger "[certbot] A new cert has been created, uploading"
cp -f /opt/dehydrated/certs/router.example.com/fullchain.pem /mnt/my-cifs-share/cert.pem
cp -f /opt/dehydrated/certs/router.example.com/privkey.pem /mnt/my-cifs-share/key.pem
else
logger "[certbot] Cert still valid"
fi
What this does is copy the new certificate and private key to a mounted share if the file is newer, in other words if a certificate renewal has occurred. On the router side, I handle the remaining tasks:
#!/bin/sh
if [ /cifs1/cert.pem -nt /tmp/etc/cert.pem ]; then
logger "[certbot] New cert found, updating"
cp -f /cifs1/cert.pem /tmp/etc/cert.pem
cp -f /cifs1/key.pem /tmp/etc/key.pem
tar -C / -czf /tmp/cert.tgz /tmp/etc/cert.pem /tmp/etc/key.pem
nvram setfb64 https_crt_file /tmp/cert.tgz
nvram commit
logger "[certbot] Restarting httpd"
service httpd restart
rm /tmp/cert.tgz
else
logger "[certbot] Cert valid, exiting"
fi
I have this script scheduled to run twice daily and it checks for a new certificate in the mounted share. If the certificate has been renewed it will copy the new files over, save the new certificate to nvram, and restart httpd. As usual though I’ll have to wait until the next renewal to see if things go as planned…