Générer ses certificats Let's Encrypt sans effort en s'appuyant sur Knot DNS
2025-11-23
D'aussi loin que je me souvienne, j'ai hébergé un serveur DNS faisant autorité. Je pense à peine après avoir loué mon premier VPS et ça a toujours été NSD. Pourquoi ? Je ne me souviens plus vraiment, Bind n'avait pas très bonne presse à ce moment-là et sa configuration n'est pas des plus sexy. Il ne m'en a pas fallu plus pour choisir NSD... J'ai probablement utilisé ce logiciel aussi, car j'apprenais à utiliser Docker et Xataz (présent sur un forum commun) en avait fait une image à l'époque.
Bien que ce logiciel, qui m'a accompagné tant d'années (j'ai même créé un rôle Ansible), ne m'a jamais fait défaut, j'avais deux choses à lui reprocher. Il ne régénérait pas de RRSIG tout seul ce qui fait qu'au bout de 2 semaines, si je n'avais pas modifié mes zones, toute mon infra s'effondrait, car ces signatures n'étaient plus valides (un cron pour relancer le playbook toutes les semaines solutionne le problème). Et un autre point qui m'a bloqué : il ne gère pas la RFC 2136 qui permet de mettre à jour une zone DNS sans avoir à modifier le fichier zone (faire un SED en SSH ça fonctionne, mais une erreur peut avoir de mauvaise conséquence).
Avec mon futur lab (si vous n'avez pas vu passer les articles je vous les redonnes par pure bontée d'âme 😇 : Mon lab - Automatiser le socle et Mon lab - Automatiser ses gateways ) j'ai pour volonté de rapprocher au maximum les certificats x509 des applications pour notamment éviter un énorme SPOF, utiliser davantage les IPv6 (au détriment de ceux qui sont en IPv4) mais également pour les applications type Ejabberd (XMPP) éviter d'avoir une tâche cron qui déplace les certificats du HAProxy à la VM hébergeant ce logiciel mais d'avoir deux certificats distincts.
Aujourd'hui niveau fonctionnement c'est très basique, je génère un certificat en standalone car HAProxy ne sait pas délivrer de fichier, je détecte quand l'URL possède le chemin de Let's encrypt et je l'expédie vers le serveur certbot qui tourne.
frontend http-in bind :80 bind :::80 mode http acl acl_letsencrypt path_beg /.well-known/acme-challenge/ use_backend backend_letsencrypt if acl_letsencrypt backend backend_letsencrypt mode http server srv_letsencrypt 127.0.0.1:63443
Cette configuration amène quelques limites. Les certificats ne seront générés que sur la machine du reverse proxy et cela ne fonctionne pas pour les certificats Wildcard, car il faut carrément utiliser le challenge DNS et ainsi aller modifier sa zone DNS directement, ce qui est difficilement automatisable.
C'est là que la RFC 2136 plus communément appelé DDNS ou Dynamic DNS (pour Dynamic Updates in the Domain Name System) vient à notre secours. Cette norme, qui date de 1997, propose un mécanisme pour modifier une zone DNS en réalisant une requête de type UPDATE qui indique quel enregistrement veut-il ajouter ou modifier. Le serveur DNS de son côté met à jour sa base de donnée, modifie le SERIAL du domaine et notifie les serveurs secondaires qui viendront récupérer les changements. Ça a été beaucoup utilisé pour notifier un serveur DNS du changement de notre IP, vu que les FAI commençaient à les changer de plus en plus régulièrement.
J'ai donc remplacé mon serveur NSD par un Knot, fait un rollover des DS records, grogné contre le manager débilement compliqué (et buggué) d'OVH où il me reste UN domaine que j'administre, puis vérifié avec ZoneMaster que tous mes domaines sont bons et vérifier si mon monitoring se met à hurler ou non. Bien entendu j'ai créé le rôle Knot pour Ansible.
Knot embarque d'autres avantages que gérer les requêtes UPDATE. Il sait notamment gérer la signature DNSSEC ainsi que les rollovers automatique, la signature RRSIG avec leurs rotations régulières. On peut même y rajouter des modules comme geoip pour modifier ses réponses DNS en fonction de la localisation de la requête.
Bon, trêve de blabla, passons à la technique !
Installer et configurer Knot
Déjà il faut installer Knot, j'en profite pour ajouter deux trois paquets qui ajoute des helpers.
dryusdan@mul:~$ sudo apt-get install knot knot-dnssecutils knot-doc
On retrouve une configuration basique dans le fichier /etc/knot/knot.conf. Basique, car les valeurs par défaut sont déjà bien suffisantes. D'ailleurs il s'agit de YAML simplifié, ce qui fait que le templating avec un poil plus complexe que lâcher un yaml.dump(). Ici je vais surtout rajouter les clés TSIG (pour avoir des serveurs DNS secondaires), créer une ACL pour autoriser les transferts et notify puis je définis mes peers avant de claquer mes domaines. J'aurais d'ailleurs pu définir mes zones directement dans la DB de Knot (à grand coup de commande), mais j'aime bien le format fichier. Sachant que les zones sont au même format que NSD (donc cette partie n'a pas été réécrite dans mon playbook) qui est le même que le format Bind9.
server:
# On écoute sur toutes les interfaces IPv4
listen: 0.0.0.0@53
# On écoute sur toutes les interfaces IPv6
listen: ::@53
# L'utilisateur utilisé par knot pour faire tourner le serveur
# (on évite root évidement)
user: knot:knot
log:
- target: syslog
any: info
database:
storage: /var/lib/knot
# Vous pouvez générer les clés avec la commande keymgr --tsig secondary
key:
- id: secondary
secret: FhrN0CHlZFXr1LQSgwO29JDkn3OO+DTFcdA8S6mvFbY=
algorithm: hmac-sha256
- id: lecygnenoir
algorithm: hmac-sha256
secret: McjZZASgChl0U59UlQyhQXDYtSguY/E1jD3emJWEUpo=
# On défini les remotes et on les associes à une clé
remote:
- id: secondary
address: [10.8.0.2]
key: secondary
- id: lecygnenoir
address: [10.10.10.10]
key: lecygnenoir
# Je n'autorise que le serveur DNS secondaire
# à être notifié et à faire des requêtes IXFR/AXFR avec la clé nommé secondary
# Si la directive automatic-acl est à on, techniquement cette section là n'est
# pas utile
acl:
- id: dryusdan_secondary_dns
key: [secondary]
address: [10.8.0.2]
action: [notify, transfer]
- id: lecygnenoir_secondary_dns
key: [lecygnenoir]
address: [10.10.10.10]
action: [notify, transfer]
policy:
- id: dnssec
signing-threads: 2
# Ou ECDSAP256SHA256 pour les tld type .net par exemple
algorithm: ECDSAP384SHA384
# Le but de ces templates va être d'éviter de dupliquer toutes ces lignes,
# mais il est possible de les surcharger
template:
# Le template par défaut récupère uniquement les fichiers .zone dans
# /var/lib/knot
- id: default
storage: /var/lib/knot
file: %s.zone
- id: dnssec
# Je ne défini qu'une ACL, car certains de mes domaines
# n'utilisent pas le DNS secondaire de lecygnenoir
acl: [dryusdan_secondary_dns]
# Ce domaine sera signé avec DNSSEC
dnssec-signing: True
# On attribue la policy DNSSEC défini plus haut
dnssec-policy: dnssec
# Pareil que dans le template default
storage: /var/lib/knot
file: %s.zone
# On demande à notifier dryusdan_secondary uniquement en cas de différence du SOA.
zonefile-load: difference
notify: secondary
zone:
- domain: dryusdan.space
template: dnssec
acl: [drysudan_secondary_dns, lecygnenoir]
notify: [secondary, lecygnenoir]
Et hop, le serveur Knot est fonctionnel. Pour la partie DNSSEC, je vous laisse lire l'article d'Alarig à ce sujet, c'est bien expliqué et ce n'est pas le but de mon article 😛.
Et certbot dans tout ça ?
Je ne l'ai pas oublié ! J'utilise le plugin certbot-dns-rfc2136
dryusdan@mul:~$ sudo apt-get install certbot python3-certbot-dns-rfc2136
Je crée une nouvelle clé TSIG pour certbot.
dryusdan@mul:~$ /usr/sbin/keymgr --tsig certbot
key:
- id: certbot
algorithm: hmac-sha256
secret: okYvWtZFj8XcGX7XBF2fuzV6H2RYXLuGk1d/I3SFwgo=
Puis je la mets dans knot.conf. Tant qu'à être dans ce fichier, j'en profite également pour rajouter une nouvelle ACL
acl:
- id: certbot_update
key: [certbot]
address: [127.0.0.1,10.55.8.6]
action: update
update-type: [TXT]
Vous l'aurez saisi, je crée une règle qui autorise la clé TSIG certbot provenant soit de localhost soit d'une autre machine d'effectuer un update UNIQUEMENT pour un champ TXT, ce qui, en cas de scénario catastrophe, permet de limiter au maximum les dégâts possibles. Là, seuls les enregistrements TXT demandé par certbot seront créés, le reste sera refusé.
J'oublie pas de reload Knot
dryusdan@mul:~$ sudo systemctl reload knot
Côté Certbot il faut créer un fichier ini contenant la clé TSIG pour faire cette requête. Je vais le placer dans /etc/letsencrypt/rfc2136.ini
# L'adresse IP du serveur Knot dns_rfc2136_server = 127.0.0.1 dns_rfc2136_port = 53 # Le nom de la clé TSIG dns_rfc2136_name = certbot # La clé TSIG dns_rfc2136_secret = okYvWtZFj8XcGX7XBF2fuzV6H2RYXLuGk1d/I3SFwgo= # Et son algorithme dns_rfc2136_algorithm = HMAC-SHA256 # TSIG sign SOA query (optional, default: false) dns_rfc2136_sign_query = false
Une fois ça fait, c'est terminé ^^
root@mul ~ # certbot certonly \ --dns-rfc2136 \ --dns-rfc2136-credentials /etc/letsencrypt/rfc2136.ini \ -d dryusdan.space \ -d *.dryusdan.space
Par défaut il y a un délai de 60 secondes (modifiable par --dns-rfc2136-propagation-seconds) avant que les checks soient effectués.
Pour vos certificats déjà existants, modifiez la section [renewalparams] du fichier /etc/letsencrypt/renewal/dryusdan.space.conf
[renewalparams] reuse_key = True account = 0123456789abcdef0123456789abcdef key_type = ecdsa elliptic_curve = secp384r1 authenticator = dns-rfc2136 dns_rfc2136_credentials = /etc/letsencrypt/rfc2136.ini server = https://acme-v02.api.letsencrypt.org/directory
Et voilà.
Maintenant plus besoin d'être présent pour générer des certificats wildcard ni même déplacer vos certificats entre votre reverse proxy et votre serveur XMPP 😊
Sur ce, portez vous bien :)
Photo de Daniil Komov
--------------------------------------------------------------------------------