Dans cet article, on va configurer dehydrated.io pour produire des certificats ECDSA pour HAProxy, sans passer par un reload ou restart du service.

Configuration de dehydrated

Dehydrated est assez simple à configurer : il suffit de lister les domaines pour lesquels on veut générer un certificat dans /etc/dehydrated/domains.txt.

Ensuite, il faut s’enregistrer auprès de Let’s Encrypt :

sudo dehydrated --register --accept-terms

Il faut également modifier /etc/dehydrated/config pour

  1. définir l’emplacement des fichiers pour le challenge ACME
  2. créer des clés ECDSA

Le fichier de configuration doit donc être modifier ainsi :

CONFIG_D=/etc/dehydrated/conf.d
BASEDIR=/var/lib/dehydrated
WELLKNOWN="/var/www/dehydrated"
DOMAINS_TXT="/etc/dehydrated/domains.txt"
HOOK=${BASEDIR}/hook.sh
KEY_ALGO=secp384r1

Ce qui est important ici, c’est WELLKNOWN, qui définit l’emplacement des challenges, HOOK, qui nous servira plus tard pour lier dehydrated à HAProxy, et KEY_ALG, pour passer en ECDSA.

On ne générera pas les certificats tout de suite, il faut d’abord mettre la “colle” entre HAProxy et Dehydrated.

Configuration de NGinX

HAProxy n’étant pas capable de servir des fichiers (c’est un reverse-proxy après tout), il nous faut un serveur web. Bien sûr le lecteur est libre d’utiliser celui qu’il veut, mais dans ce billet je ne vais couvrir que NGinX. La définition du serveur est relativement simple, il faut juste servir /var/www/dehydrated sous l’arborescence /.well-known/acme-challenge/, en utilisant un autre port que 80 ou 443.

server {
        listen  *:54321;
        root    /var/www;
        error_log /var/www/error;
        location ^~ /.well-known/acme-challenge {
                alias /var/www/dehydrated;
        }
}

Configuration de HAProxy

Tout d’abord, il faut s’assurer qu’on a bien activé la socket d’administration si on veut pouvoir changer les certificats à chaud. Dans /etc/haproxy/haproxy.cfg :

global
    stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners

Toutes les options ne sont pas forcément nécessaires mais la socket doit a minima être de niveau admin

Ensuite, il reste la question de comment sont déclarés les certificats dans notre directive bind.

Cas 1: un seul certificat
Dans ce cas, aucune précaution n’est à prendre.
Cas 2: plusieurs certificats
Si vous avez plusieurs certificats, il va être nécessaire, pour que tout fonctionne comme attendu, que vous utilisiez un répertoire pour les stocker et les appeler, plutôt que de les lister individuellement.
bind *:443 ssl crt /etc/haproxy/ssl-certs/
Ainsi, HAProxy chargera tous les certificats de ce dossier.

Il faut également créer le nécessaire pour que HAProxy route les requêtes sur /.well-known/acme-challenge/ vers notre serveur NGinX. Il va aussi falloir ajouter une exception à nos directive http-request redirect, entre autres, pour ne pas interférer avec les challenges :

frontend fe_main
	...
        acl letsencrypt-acl path_beg /.well-known/acme-challenge/
	...
	# Redirection de HTTP vers HTTPS
        http-request redirect scheme https code 301 if !{ ssl_fc } !letsencrypt-acl
	...
	# Attention à bien placer ce use_backend AVANT tous les autres
        use_backend letsencrypt if letsencrypt-acl
	...

backend letsencrypt
        server nginx 127.0.0.1:54321

Hook dehydrated

Dehydrated est un script bash, et permet d’injecter des fonctions via un script dans /var/lib/dehydrated/hook.sh. Nous allons nous en servir pour injecter les certificats dans HAProxy, via sa socket d’administration. Les commandes sont :

  • new ssl cert <fichier de certificat> pour créer un certificat (dans le cas d’un nouveau certificat)
  • set ssl cert <fichier de certificat> <<\n<clé et certificat au format PEM> pour définir le contenu du certificat
  • commit ssl cert <fichier de certificat> pour terminer la transaction
  • add ssl crt-list <répertoire des certificats> <fichier de certificat> pour attacher le certificat à notre frontend (dans le cas d’un nouveau certificat)

Dehydrated fournit un squelette de fichier hook.sh dans /usr/share/doc/dehydrated/examples/hook.sh, on va le copier sous /var/lib/dehydrated/hook.sh et modifier la fonction deploy_cert() :

deploy_cert() {
    local DESTINATION="/etc/haproxy/ssl-certs"
    local FULLCERTFILE="${DESTINATION}/${DOMAIN}.pem"
    local HAPSOCKET="/run/haproxy/admin.sock"

    echo " + Deploying cert ${FULLCERTFILE}"
    # Update cert in RAM
    if ! [[ -f "${FULLCERTFILE}" ]]; then
        echo "new ssl cert ${FULLCERTFILE}" | socat stdio $HAPSOCKET
        echo -e "set ssl cert ${FULLCERTFILE} <<\n$(sed /^$/d "${KEYFILE}" "${FULLCHAINFILE}")\n" | socat stdio $HAPSOCKET
        echo "commit ssl cert ${FULLCERTFILE}" | socat stdio $HAPSOCKET
        echo "add ssl crt-list ${DESTINATION} ${FULLCERTFILE}" | socat stdio $HAPSOCKET
    else
        echo -e "set ssl cert ${FULLCERTFILE} <<\n$(sed /^$/d "${KEYFILE}" "${FULLCHAINFILE}")\n" | socat stdio $HAPSOCKET
        echo "commit ssl cert ${FULLCERTFILE}" | socat stdio $HAPSOCKET
    fi

    # Update cert on disk
    cat "${KEYFILE}" "${FULLCHAINFILE}" > "${FULLCERTFILE}"
}

Vous pouvez directement télécharger le script complet.

Il ne nous reste plus qu’à générer nos certificats :

sudo dehydrated -c

En cas de pépin, vous pouvez me trouver sur Mastodon