Background
Both this site and my home server use HTTPS certificates provided by Let's Encrypt.
Previously I was using the http-01
challenge method, but once wildcard
certificates became available, I decided to switch to the dns-01
method for simplicity.
My domains are currently registered through Namecheap which provides an API for modifying DNS records. The only catch is that it can only be accessed via manually-whitelisted IP addresses. Because the server I'm trying to access the API from is assigned a dynamic IP, this restriction was a bit of an issue.
Back in November when I initially made the switch to the DNS-based challenge, I set it up the easy way: I manually added my current IP to the whitelist, added a TODO entry to fix it somehow, and set a reminder scheduled for the week before the cert would expire telling me to update the IP whitelist. Fast-forward to today when I was reminded to update my IP whitelist. Instead of continuing to kick the can down the road, I decided to actually fix the issue.
Setting up a temporary proxy
My home server is connected to the internet though a normal residential connection which is assigned a dynamic IP address. However, the server that I run this website on is configured with a static IP since it's a hosted VPS. By proxying all traffic to the Namecheap API through my VPS, I could add my VPS's static IP to the whitelist and not have to worry about my home IP changing all the time.
SSH is perfect tool for this. The OpenSSH client supports port forwarding using the -D
flag.
By then setting the HTTP[S]_PROXY
environment variables to point to the forwarded port, programs
that support those environment variables will transparently forward all their requests through the
proxy.
After a bunch of research and testing, I came up with the following script to easily set up the temporary proxy:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
This script is saved as ~/.config/tempproxy.rc
and can be sourced to automatically set up a proxy
session and have it be torn down when the script exits.
You'll want to use key-based authentication with an unencrypted private key so that you don't need to type anything to initiate the SSH session. For this reason you'll probably want to create a limited user on the target system that can only really do port forwarding. There's a good post on this here.
Talking to the API through the proxy
To allow programs that use the tempproxy.rc
script to talk to the Namecheap API, the IP address of
the VPS was added to the whitelist. Now that the proxying issue was taken care of, I just needed
to wire up the actual certificate renewal process to use it.
The tool I'm using to talk to the Namecheap DNS API is lexicon. It can handle manipulating the
DNS records of a ton of providers and integrates really nicely with my ACME client of choice,
dehydrated. Also, because it's using the PyNamecheap Python library, which in turn uses
requests under the hood, it will automatically use the HTTP*_PROXY
environment
variables when making requests.
The only tricky bit is that the base install of lexicon
won't automatically pull in the packages
required for accessing the Namecheap API. Likewise, requests
won't install packages to support
SOCKS proxies. To install all the required packages you'll need to run a command like:
1 |
|
Since lexicon can use environment variables as configuration, I created another small source-able
file at ~/.config/lexicon.rc
:
1 2 3 4 5 6 7 8 |
|
With that in place, the existing automation script that I had previously set up when I switched to
using the dns-01
challenge just needed some minor tweaks to source the two new files. I've
provided the script below, along with some other useful ones that use the DNS API.
Scripts
do-letsencrypt.sh
This is the main script that handles all the domain renewals. It's called via cron
on the first
of every month.
Fun fact: The previous http-01
version of this script was a mess - it involved juggling nginx
configurations with symlinks, manually opening up the .well-known/acme-challenge
endpoint, and
some other terrible hacks. This version is much nicer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
dns.sh
This one is really simple - it just augments the normal lexicon CLI interface with the proxy and configuration variables.
1 2 3 4 5 6 7 |
|
1 2 3 4 5 |
|
dns-update.sh
An updated version of my previous script that uses the API instead of the
DynamicDNS service Namecheap provides. Called via cron
every 30 minutes.
Update
A previous version of this script didn't try to delete the DNS entry before setting the new one,
resulting in multiple IPs being set as the same A
record.
This is because the update function of the Namecheap plugin for lexicon is implemented as a 2-step delete and add. When it tries to delete the old record, it looks for one with the same content as the old one. This means that if you update a record with a new IP, it doesn't find the old record to delete first, leaving you with all of your old IPs set as individual A records.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
|