Let's Encrypt

Posted on Sat 13 March 2021 in Hosting

Lorsqu’on parle de serveur web et de communication électronique au sens large, il y a un acteur devenu incontournable ces dernières années en matière de sécurisation : Let’s Encrypt.

Pour établir une communication sécurisée entre le client et le serveur, les technologies actuelles utilise un certificat signé cryptographiquement. L’émission et la signature d’un certificat se fait par une autorité de certification (AC ou CA – Certificate Authority – en anglais). Un certificat est valide pour un nom de domaine particulier. Et enfin, pour être réputé valide, l’autorité de certification qui a émis le certificat doit faire partie des autorités de certification de confiance du système.

Avant l’avènement de Let's Encrypt, il y avait 3 solutions pour avoir un certificat :

  • se créer soit même un certificat dit auto-signé, mais tous les clients lèvent alors des alertes lorsqu’ils rencontrent ce certificat puisqu’il ne fait partie d’aucune chaîne de confiance,
  • utiliser l’autorité CACert qui n’est pas nativement reconnue par les systèmes mais qui a mis en service presque 1,5 millions de certificat, en se basant sur une toile de confiance entre les utilisateurs de CACert, en somme un projet d’autorité de certification collaboratif,
  • acheter, souvent à prix d’or un certificat à une autorité de certification reconnue. Le premier résultat de recherche me donne un prix aux alentours de 350€ pour 1 an pour un seul domaine. À une époque, il existait StartSSL qui en vendait pour une cinquantaine d’euros, valable 2 ans pour un nombre illimité de sous domaine – j’ai dû en avoir un comme ça pendant quelques années il y a longtemps maintenant.

Bref, les solutions n’étaient pas optimale ou très cher.

Et puis… Let's Encrypt… Une autorité de certification, reconnue par les principaux systèmes d’exploitation et navigateurs, gratuite, et automatisée ! En contrepartie, le certificat n’est valable que 3 mois. Il suffit donc de bien prévoir son renouvellement automatique… Au moment de l’émission d’un nouveau certificat, Let's Encrypt vous propose un challenge que vous devrez résoudre pour démontrer que vous êtes bien le propriétaire du domaine pour lequel vous voulez un certificat. Il existe plusieurs type de challenge mais je me contenterai des 2 que j’utilise.

Communiquer avec Let’s Encrypt : Certbot

Pour communiquer avec les serveurs de Let's Encrypt et permettre la génération et le renouvellement de certificat, j’utilise le client certbot qui est disponible normalement dans les dépôt de votre distribution. Sur la debian :

apt install letsencrypt

Le challenge par webroot

La demande d’un nouveau certificat en utilisant la méthode webroot se fait via la commande suivante :

certbot certonly --webroot -w ${webroot} ${nom_de_domaine}

Pour résoudre ce challenge, certbot va placer un fichier acme-challenge dans le dossier ${webroot}/.well-known. Ce dernier doit donc être accessible via http://${nom_de_domaine}/.well-known/acme-challenge pour que les serveurs de Let’s Encrypt puissent contrôler le contenu qui doit correspondre.

En utilisant Nginx, j’ai un petit fichier letsencrypt.conf dans ma configuration qu’il me suffit d’inclure dans la configuration du virtualhost utilisant Let’s Encrypt :

location ^~ /.well-known/acme-challenge {
    alias /var/lib/letsencrypt/.well-known/acme-challenge;
    default_type "text/plain";
    try_files $uri =404;
}

Ainsi, toutes les requêtes vers /.well-known/acme_challenge sont déroutées vers le dossier /var/lib/letsencrypt que je donne à certbot au moment de l’émission du nouveau certificat.

Le challenge par DNS

Le challenge par webroot est plutôt simple et efficace, du moment qu’on a un serveur Web. Par contre, dans les cas plus complexes s’il n’y a pas d’hôte web attaché au domaine ou si on veut générer un certificat wildcard1, le webroot ne sera pas suffisant. Il faut passer au challenge DNS. Le principe est le même, certbot fourni une chaîne de validation qu’il faut mettre au bon endroit dans sa zone DNS pour prouver qu’on en est bien le propriétaire. Alors bien sûr, la mise à jour de la zone DNS peut se faire manuellement, mais je rappelle que les certificats Let’s Encrypt ne son valable QUE 3 mois, et faire un truc manuellement tous les 3 mois… moi je dis bof !

Heureusement, certbot nous permet de paramétrer des hooks : des scripts pouvant être appelés à l’émission du nouveau certificat, ainsi qu’à son renouvellement. 2 scripts pour être exact : un avant l’authentification, et un de nettoyage.

J’ai donc dans le fichier /usr/local/bin/certbot-dns-bind-authenticator.sh:

#!/usr/bin/env bash

# CERTBOT_DOMAIN: The domain being authenticated
# CERTBOT_VALIDATION: The validation string (HTTP-01 and DNS-01 only)
# CERTBOT_TOKEN: Resource name part of the HTTP-01 challenge (HTTP-01 only)
# CERTBOT_REMAINING_CHALLENGES: Number of challenges remaining after the current challenge
# CERTBOT_ALL_DOMAINS: A comma-separated list of all domains challenged for the current certificate

dns_domain=${CERTBOT_DOMAIN/yapbreak.fr/}
if [ -z ${dns_domain} ]; then
    TXT_ENTRY="_acme-challenge"
else
    dns_domain="$(echo "${dns_domain}" | rev | cut -c 2- | rev)"
    TXT_ENTRY="_acme-challenge.${dns_domain}"
fi

# I print complete entry here, so I can remove it in cleanup script
echo "${TXT_ENTRY} IN TXT \"${CERTBOT_VALIDATION}\""

if grep -q "${TXT_ENTRY} IN TXT" "/etc/bind/db.yapbreak.fr"; then
    sed -i "s|${TXT_ENTRY} IN TXT.*$|${TXT_ENTRY} IN TXT \"${CERTBOT_VALIDATION}\"|" /etc/bind/db.yapbreak.fr
else
    sed -i "s|; CERTBOT PLACEHOLDER|&\n${TXT_ENTRY} IN TXT \"$CERTBOT_VALIDATION\"|" /etc/bind/db.yapbreak.fr
fi
/usr/local/bin/signzone yapbreak.fr > /tmp/certbot_signzone.log 2>&1

# Need to wait a little bit allow propagation of new entry
sleep 30s

Puis, dans le fichier /usr/local/bin/certbot-dns-bind-cleanup.sh:

#!/usr/bin/env bash

# CERTBOT_DOMAIN: The domain being authenticated
# CERTBOT_VALIDATION: The validation string (HTTP-01 and DNS-01 only)
# CERTBOT_TOKEN: Resource name part of the HTTP-01 challenge (HTTP-01 only)
# CERTBOT_REMAINING_CHALLENGES: Number of challenges remaining after the current challenge
# CERTBOT_ALL_DOMAINS: A comma-separated list of all domains challenged for the current certificate
# CERTBOT_AUTH_OUTPUT: Whatever the auth script wrote to stdout

echo "Cleanup ${CERTBOT_AUTH_OUTPUT}"
sed -i "/^.*${CERTBOT_VALIDATION}.*$/d" /etc/bind/db.yapbreak.fr
/usr/local/bin/signzone yapbreak.fr > /tmp/certbot_signzone.log 2>&1

Notez que ces scripts :

  • sont spécifiques au domaine yapbreak.fr et ces sous domaine
  • la zone DNS yapbreak.fr est décrite dans /etc/bind/db.yapbreak.fr
  • la mise à jour de la zone est géré par le script /usr/local/bin/signzonequi est présenté dans l’article sur Le DNS

Il reste donc à demander l’émission d’un nouveau certificat avec la commande suivante :

certbot certonly \
        --manual \
        --preferred-challenges=dns \
        --manual-auth-hook /usr/local/bin/certbot-dns-bind-authenticator.sh \
        --manual-cleanup-hook /usr/local/bin/certbot-dns-bind-cleanup.sh \
        -d yapbreak.fr \
        -d "*.yapbreak.fr"

Cette ligne de commande va donc générer un certificat qui sera valide pour les domaines yapbreak.fr et *.yapbreak.fr.

Le renouvellement

Histoire de ne pas oublier de renouveler ces certificats, on continue dans l’automatisation en configurant un timer systemd qui va s’occuper de renouveler nos certificats :

Dans /etc/systemd/system/certbot.service:

[Unit]
Description=Let's Encrypt renewal

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --agree-tos
ExecStartPost=/usr/bin/systemctl --no-block reload nginx postfix dovecot

La dernière ligne sert à recharger les services qui utilise les certificats renouvelés.

Puis dans /etc/systemd/system/certbot.timer:

[Unit]
Description=Daily renewal of Let's Encrypt's certificates

[Timer]
OnCalendar=Daily
RandomizeDelaySec=1day
Persistent=true

[Install]
WantedBy=timers.target

Ce timer va donc exécuter tous les jours le service certbot décrit plus haut, qui lance la commande certbot renew qui ne renouvelle les certificats que lorsque c’est nécessaire.

Il ne reste plus qu’à recharger, à démarrer et à activer ce timer :

systemctl daemon-reload
systemctl enable --now certbot.timer

  1. C’est à dire un certificat qui sera valable pour tous les sous-domaines du domaine donnée. Par exemple *.yapbreak.fr sera valable pour links.yapbreak.fr mais aussi pour meet.yapbreak.fr