Publié le 01/03/2018, rédigé par Gaulthier LALLEMAND

Let's Encrypt

Ajouter des hooks avant/après le renouvellement d'un certificat Let's Encrypt (Debian) ?

Prérequis : avoir créé un certificat Let's Encrypt avec Certbot.

Il peut être intéressant d'exécuter une commande système (ou même un script), avant et/ou après le renouvellement d'un certificat. Pour cela, on peut dire à Certbot d'exécuter une action (un hook).

Important : On ne peut utiliser ces hooks qu'avec la sous-commande renew de Certbot.

Les types de hook

Il existe trois types de hook :

Attention : Pour qu'un renouvellement ait lieu, il faut que les deux conditions suivantes soient vérifiées :

Ce qui signifie que si la commande est lancée mais qu'aucun certificat n'est prêt à être renouvelé, aucun hook n'est exécuté !

Par la ligne de commande

Commandes simples

Pour définir un hook, je peux ajouter l'option correspondante à la ligne de commande :

Quelques exemples de commandes :

# Effectue le renouvellement de TOUS les certificats.
# Redémarre le service "apache2" après chaque renouvellement réussi.
sudo certbot renew --quiet \
             --deploy-hook "systemctl restart apache2"
# Arrête le service "apache2".
# Procède au renouvellement de TOUS les certificats Certbot.
# Relance le service "apache2".
sudo certbot renew --quiet \
             --pre-hook "systemctl stop apache2" \
             --post-hook "systemctl start apache2"

Lors d'un renouvellement général (ie. sans l'option --cert-name), les commandes pre-hook et post-hook ne sont lancées qu'une seule fois (Cf l'ordre d'exécution à la fin de cet article) !

Pour associer des pre-hook et post-hook au certificat abc en particulier, je peux procéder ainsi :

# Vérifie que le certificat "abc" est prêt à être renouvelé.
# Arrête le service "apache2".
# Procède au renouvellement du certificat "abc".
# Démarre le service "apache2".
sudo certbot renew --quiet \
             --cert-name abc \
             --pre-hook "systemctl stop apache2" \
             --post-hook "systemctl start apache2"

De cette manière, je peux gérer plus finement les actions précédent et suivant chaque renouvellement de certificat.

Si je souhaite renouveler chaque certificat séparément, je devrai faire attention à ajouter une ligne dans cron pour chaque certificat. Sinon certains certificats risquent d'arriver au terme de leur période de validité sans crier gare (au mieux, je recevrai un email automatique de Let's Encrypt m'avertissant de l'écheance si j'ai fourni un email lors de la création du certificat).

Supprimer la pré-validation

Certbot effectue un contrôle des commandes passées aux options --xxx-hook, avant d'exécuter pour de vrai la commande elle-même. Il vérifie surtout que les programmes invoqués sont bien dans le PATH.

Cela peut être source de faux positifs, d'erreurs qui n'en sont pas. Aussi pour supprimer ce contrôle, il suffit d'ajouter l'option --disable-hook-validation :

sudo certbot renew --quiet --disable-hook-validation ...
Chemin vers un script

Si une simple commande ne me suffit pas, je peux également spécifier le chemin absolu vers un script :

# Certbot détermine l'interpréteur de commandes grâce au shebang
# présent sur la première ligne du script.
sudo certbot renew --quiet \
             --cert-name abc \
             --pre-hook "/chemin/absolu/vers/mon/script1" \
             --post-hook "/chemin/absolu/vers/mon/script2"
Ecrire plusieurs fois la même option

Il est possible d'écrire plusieurs fois la même option --xxx-hook, mais ça ne sert à rien. En effet, bien que cela ne déclenche pas d'erreur de Certbot, le comportement de celui-ci consiste à ne prendre en compte que la dernière version d'une option.

Par exemple :

# Dans ce cas, seul le script3 sera lancé en PRE-HOOK.
# Le script4 sera lancé en POST-HOOK de façon normale.
sudo certbot renew --quiet \
             --cert-name abc \
             --pre-hook "/chemin/absolu/vers/mon/script1" \
             --pre-hook "/chemin/absolu/vers/mon/script2" \
             --pre-hook "/chemin/absolu/vers/mon/script3" \
             --post-hook "/chemin/absolu/vers/mon/script4"

Grâce au répertoire renewal-hooks

À partir de la version 0.19.0, Certbot dispose d'une série de répertoires dans lesquels je peux mettre des scripts. Ils sont situés dans le répertoire suivant :

/etc/letsencrypt/renewal-hooks/

Il existe un répertoire par type de hook:

Pour chacun de ces répertoires, les scripts sont exécutés par ordre alphabétique des noms de fichiers.

Ordre d'exécution

Les scripts du répertoire renewal-hooks/ s'exécutent toujours avant la commande indiquée par l'option --xxx-hook correspondante. Par exemple, les scripts situés dans renewal-hooks/pre/ s'exécuteront avant la commande spécifiée par l'option --pre-hook.

En supposant que Certbot identifie au moins un certificat comme devant être renouvelé, l'ordre d'exécution des hooks est le suivant (juste après avoir lancé sudo certbot renew ...) :

Exécution des hook

Tester l'exécution des hooks

Pour vérifier l'ordre d'exécution des hooks, je peux simuler le renouvellement d'un certificat (ou de tous) à l'aide de l'option --dry-run.

Cette option fonctionne avec le goal renew. La sortie standard affichera alors des informations sur ce qui se serait passé en cas de réel renouvellement, et en particulier des informations sur les hooks. Par exemple:

sudo certbot renew --dry-run --cert-name abc \
                   --pre-hook "/chemin/absolu/vers/mon/script1" \
                   --post-hook "/chemin/absolu/vers/mon/script2"

Cas d'utilisation des hooks

Le cas classique d'utilisation des hooks est sûrement celui-ci: je veux renouveler mon certificat abc. Je lance donc la commande suivante:

sudo certbot renew --cert-name abc

Le terminal me prévient de l'échec du renouvellement et en particulier:

Attempting to renew cert (abc) from /etc/letsencrypt/renewal/abc.conf produced an unexpected error: Problem binding to port 80: Could not bind to IPv4 or IPv6.. Skipping.
All renewal attempts failed. The following certs could not be renewed:
  /etc/letsencrypt/live/abc/fullchain.pem (failure)

Le port 80 est injoignable. Et pour cause: il est déjà pris par Apache. Il faut donc stopper le service Apache avant le renouvellement, puis le relancer juste après:

sudo certbot renew --cert-name abc \
                   --pre-hook "systemctl stop apache2" \
                   --post-hook "systemctl start apache2"

Maintenant le renouvellement fonctionne !