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.

  1. No, better. Because it’s trivial to integrate with other software using its hook mechanism.

  2. Think RewriteRule or RedirectMatch 307; and yes, acme workers from LE follow redirects.

  3. Think /usr/local, /etc, etc.

  4. And limits potential unintended consequences of that change.