Renouvellement automatique du certificat LetsEncrypt pour relai IRC Weechat

Et en bonus un service systemd pour contenir weechat !

Bonjour, j’espère que vous passez un bon confinement.

Aujourd’hui (enfin il y a quelques jours), c’est l’occasion de réparer mon relai Weechat auquel je ne pouvais plus me connecter depuis que le certificat à l’intérieur du relai — basé sur le certificat généré par LetsEncrypt pour microjoe.org — a expiré.

Le relai Weechat

Weechat est un des meilleurs clients IRC que je connaisse. Il est possible d’écrire des extensions dans de nombreux langages, et possède de base de nombreuse fonctionnalités.

Le fait qu’il puisse tourner dans terminal permet de le lancer sur un serveur sans session graphique. Pour les heureux·ses possesseurs d’un petit serveur, c’est alors l’idéal : on peut laisser tourner Weechat sur le serveur en continu, puis utiliser d’autres applications pour pouvoir se connecter au client par des applications intermédiaires : c’est le principe de relai.

Les deux applications que j’utilise actullement pour me connecter à mon Weechat distant depuis mes machines locales :

  • Glowing Bear — un client web pour se connecter depuis n’importe quel navigateur ;
  • WeechatDroid — un client Android pour le smartphone.

Ça fonctionne très bien et je reçois correctement les notifications, tant que j’ai un navigateur d’ouvert ou bien mon téléphone à portée de main. J’avais rencontré un problème de consommation de batterie au début de WeechatDroid, mais cela semble avoir été résolu depuis.

Sécuriser le relai

Par défaut le relai utilise un protocole TCP en clair pour faire circuler les messages de votre client IRC vers vos autres applications. Ceci n’est pas terrible d’un point de vue sécurité, car n’importe qui entre vous et le serveur peut alors voir ce qui transite. On peut se dire que ce n'est pas si grave pour un système de communication basé sur les salons publics, mais les conversations privées ainsi que l'authentification par mot de passe restent des choses sensibles à protéger.

Pour cela, on peut utiliser un certificat TLS pour sécuriser ce canal. C’est exactement comme on sécurise l’accès aux sites web HTTP en ajoutant TLS pour donner du HTTPS. On peut même reprendre le certificat de son site ouaib afin de l’utiliser pour le client IRC. Cela permet au client IRC d’être correctement reconnu par les clients qui ont moyen de vérifier que le certificat provient bien de LetsEncrypt.

Pour cela, on doit concaténer la clé privée ainsi que le certificat fullchain avant de placer le tout dans le dossier de configuration de WeeChat :

DEST_CERTIFICATE=/home/microjoe/.weechat/ssl/relay.pem
PRIVKEY="/etc/letsencrypt/live/.../privkey.pem"
FULLCHAIN="/etc/letsencrypt/live/.../fullchain.pem"

cat "$PRIVKEY" "$FULLCHAIN" > "$DEST_CERTIFICATE"

Et ensuite, il faut dire à Weechat d’utiliser ce certificat pour le relai :

relay.network.ssl_cert_key = "%h/ssl/relay.pem"

Et ça devrait fonctionner (sinon, essayer de regarder la documentation de Weechat).

Le principal problème de cette solution est le renouvellement des certificats. En effet, ceux-ci on une durée de vie d’environ 3 mois avec LetsEncrypt, ce qui veut dire qu’il faudrait répéter cette opération manuelle à chaque renouvellement. Bien que celui-ci soit automatique pour tout ce qui est site internet, ça ne l’est pas pour notre cas bien spécifique.

C’est ce qui fait que je me suis retrouvé à ne plus pouvoir me connecter à mon relai récemment : le certificat a été renouvelé, mais le relai utilisait encore l’ancien certificat qui est ensuite arrivé à expiration.

La solution

Heureusement, Certbot — que j’utilise pour générer les certificats LetsEncrypt — nous permet d’ajouter des hooks afin de lancer nos propres scripts lors du renouvellement des certificats. C’est très pratique et ça correspond exactement à ce que nous voulons faire pour renouveler automatiquement le certificat du relai.

Voici le script que j’ai pondu en lisant la documentation de Certbot :

#!/bin/sh

# This hook is called after a new certificate has been generated by certbot.
# It has to be installed in /etc/letsencrypt/renewal-hooks/post/
#
# We use it to copy a full {public,private} certificate chain to
# /home/$USER/.weechat, so that we allow remote connections using a valid TLS
# certificate.

set -eu

USER=microjoe
USER_ID=$(id -u $USER)
WEECHAT_DOMAIN=microjoe.org
DEST_CERTIFICATE=/home/$USER/.weechat/ssl/relay.pem

for domain in $RENEWED_DOMAINS; do
        if [ "$domain" = "$WEECHAT_DOMAIN" ]; then
                PRIVKEY="$RENEWED_LINEAGE/privkey.pem"
                FULLCHAIN="$RENEWED_LINEAGE/fullchain.pem"

                touch "$DEST_CERTIFICATE"
                chmod 600 "$DEST_CERTIFICATE"
                chown $USER:$USER "$DEST_CERTIFICATE"

                cat "$PRIVKEY" "$FULLCHAIN" > "$DEST_CERTIFICATE"

                sudo -u $USER sh -c "XDG_RUNTIME_DIR=/var/run/user/$USER_ID systemctl --user restart weechat"
        fi
done

On est obligé de construire la variable d’environement XDG_RUNTIME_DIR car celle-ci n’est pas automatiquement mise en place lors de l’appel à sudo (il semblerait que celle-ci soit déclarée uniquement lors de sessions interactives, à l’aide de pam, d’après mes recherches sur le sujet).

Qu’à cela ne tienne, on peut utiliser la commande id -u $USER afin de retrouver l’UID nécessaire pour construire ce chemin. Il sera par la suite utilisé par systemctl pour communiquer avec l’instance en cours d’utilisation de systemd dans ma session utilisateur.

Service systemd weechat.service

Vous remarquez que redémarrer weechat après le renouvellement est possible en redémarrant le service weechat.service qui tourne dans ma session utilisateur. Il a été créé afin de pouvoir démarrer weechat automatiquement lors du démarrage du serveur qui hébèrge d’autres trucs (comme ce blog), et aussi également afin de pouvoir redémarrer proprement weechat lors du renouvellement du certificat.

J’ai essayé de mettre en place un script à base de kill -USR1 et d’autres manipulations obscures afin de pouvoir redémarrer weechat à l’aide d’un signal, mais le fait que weechat tourne en premier plan et non pas en tâche de fond complique fortement la besogne. D’ou le service systemd ainsi qu’un petit script permettant de démarrer et couper weechat à l’aide de tmux.

Note

2020-04-25

On m’a signalé suite à la publication de cet article qu’il était possible de demander à Weechat de recharger les nouveaux certificats sans pour autant avoir à le redémarrer.

Pour cela, il faut installer le plugin « fifo » de Weechat, et ensuite on peut envoyer des commandes directement en écrivant dans un fichier. Vu qu’il existe une commande que je ne connaissait pas pour recharger les certificats du relai, ça peut être fait en une ligne comme ceci :

printf '%b' '*/relay sslcertkey\n' > ~/.weechat/weechat_fifo

Merci à Lord et à Seb pour cette astuce. Pour plus de détails, je vous invite à lire cet article sur le blog de Lord.

Cette solution n’est pas suffisante pour mon usage car ça ne va pas répondre au deuxième problème que j’essaye de résoudre en utilisant systemd : lancer automatiquement Weechat après un redémarrage de la machine hôte. Et étant donné que le redémarrage dû à un changement de certificat sera plutôt rare (une fois tous les trois mois), je pense garder ma solution actuelle un peu plus basique.

Le service weechat.service ressemble à ça :

[Unit]
Description=weechat client inside dedicated tmux session

# Activate linger to always start systemd user process:
# sudo loginctl enable-linger microjoe

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/home/microjoe/.local/bin/weechat-wrapper.sh start
ExecStop=/home/microjoe/.local/bin/weechat-wrapper.sh stop
Restart=on-failure
RestartSec=60

[Install]
WantedBy=default.target

Et le script weechat-wrapper.sh prend cette forme :

#!/bin/sh

SESSION=weechat

start_session() {
        tmux new-session -d -s "$SESSION"
        tmux send-keys 'weechat' 'C-m'
}

stop_session() {
        tmux kill-session -t "$SESSION"
}

case "$1" in
        start)
                start_session
                ;;

        stop)
                stop_session
                ;;

        restart)
                stop_session
                start_session
                ;;

        *)
                >&2 echo "unsupported action $1"
                exit 1
                ;;
esac

Si on veut accéder à l’instance de weechat lancée par ce combo service/script, on peut alors tout naturellement dans un terminal attacher la session tmux dédiée :

$ tmux attach -t weechat

Et hop, on retrouve notre session tmux contenant weechat.

Conclusion

J’espère que cet article vous aura été utile, même si j’ai conscience qu’il faut déjà connaitre un peu weechat et LetsEncrypt pour comprendre de quoi je parle. Mais si ça peut susciter des vocations et rendre IRC accessible à un petit peu plus de monde, alors c’est déjà ça.

La bise (ah non, de loin alors).

Une question ou remarque ? N'hésitez pas à me contacter en envoyant un mail à microjoe, suivi d'un arobase, puis encore microjoe et enfin un « point org ».