How to install low dependency Let's Encrypt client
Problem statement
The certbot
client – a regular ACME-client (client to interface
with letsencrypt.org) – is rather involved when it comes to dependencies:
recent python, recent pip, crypto bindings for python, etc.
As a result, installing certbot
on a legacy system (e.g. openSUSE-13.2
)
is burdensome.
In this quick post I’m documenting for myself how to use a low-dependency variant that’s just as good1.
Discussion
dehydrated.io logo, I presume MIT license.
With a quick search, I have found dehydrated.io as an alternative. It’s 2.3k of bash, and depends on: sudo, curl, openssl, sed, grep, awk, and mktemp.
In other words, it’s a low dependency dream come true.
On top of that, the features are just enough to comfortably integrate into any workflow, as there are hooks for every action imaginable:
deploy_challenge
, clean_challenge
, sync_cert
, deploy_cert
, deploy_ocsp
,
unchanged_cert
, invalid_challenge
, request_failure
, generate_csr
,
startup_hook
, exit_hook
What follows, then, is my way of integrating it to a fairly old Linux distro, with Apache serving on port 80.
Solution
Note: comments are inlined in the scripts below, but generally should be copy-pastable.
(What’s missing below are some Apache-specific config bits, but I’m leaving those as an exercise for the reader2…)
Btw, I opted for installation in /opt/dehydrated
over the built-in support
for system-standard directories3. Main reason: ease of backup. One dir
to rule them all.
cd /opt
# Install
git clone https://github.com/dehydrated-io/dehydrated
cd dehydrated
rm -rf .git* LICENSE README.md CHANGELOG docs/
# Add dedicated user
useradd dehydrated -s /bin/false -d /opt/dehydrated -M
# Prepare docroot
mkdir -p /var/www/html/.well-known/acme-challenge
chmod a+rwx,o+t /var/www/html/.well-known/acme-challenge
# ↑ this could be restricted further to the dehydrated user
# Setup necessary directories
mkdir -p certs accounts chains tmp
chown dehydrated:root certs accounts chains tmp
chmod ug=rwx,o= certs accounts chains tmp
# Fetch current mozilla cert bundle (see CURL_OPTS in config)
curl -k https://curl.se/ca/cacert.pem > /opt/dehydrated/cacert.pem
# Configure
cat > config <<'EOF'
DEHYDRATED_USER=dehydrated
DEHYDRATED_GROUP=users
CA="letsencrypt-test"
# for prod setup uncomment the following:
#CA="letsencrypt"
CHALLENGETYPE="http-01"
BASEDIR=$SCRIPTDIR
DOMAINS_TXT="${BASEDIR}/domains.txt"
CERTDIR="${BASEDIR}/certs"
ACCOUNTDIR="${BASEDIR}/accounts"
WELLKNOWN="/var/www/html/.well-known/acme-challenge"
LOCKFILE="${BASEDIR}/tmp/lock"
CURL_OPTS="--cacert /opt/dehydrated/cacert.pem"
HOOK="${BASEDIR}/hook.sh"
KEY_ALGO=rsa
KEYSIZE=2048
EOF
# Add appropriate hook(s)
cat > hook.sh <<'EOF'
#!/bin/bash
deploy_cert() {
local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}"
local CHAINFILE="${5}" TIMESTAMP="${6}"
# site-specific configuration here
echo " + deploy hook successful. \\o/"
}
HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_cert)$ ]]; then
"$HANDLER" "$@"
fi
EOF
chown dehydrated:root hook.sh
chmod ug=rwx,o= hook.sh
# Configure the domains we want certs for
echo "$(hostname -f) $(hostname -f)" > domains.txt
And then it’s just:
# Register
./dehydrated --register --accept-terms
# Renew certs (cron-like)
./dehydrated -c
For the cronjob I came up with:
cat > /usr/local/sbin/cronjob-refresh-letsencrypt <<'EOF'
#!/bin/bash
TF=$(mktemp)
trap 'rm -f "$TF"' EXIT
/opt/dehydrated/dehydrated -c > "$TF"
if [ $? -ne 0 ]; then
echo "Renewing SSL certificate ended with: $?"
echo
cat "$TF"
fi
EOF
chmod a+x /usr/local/sbin/cronjob-refresh-letsencrypt
because there doesn’t seem to be a way to easily silence the info notices.
Closing words
I like dehydrated a lot. Clocking at 2.3k lines of bash and only depending on standard components, it’s the perfect antidote to the certbot dependency hell.
Plus it’s well engineered to be extensible.
Also, by overriding the SSL cert bundle I avoided the bullet of trying to update the system-wide root of trust. Since every distro does that differently, this is a huge time-saver4.