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.

Xataz

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).

rôle Ansible

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.

Mon lab - Automatiser le socle
Mon lab - Automatiser ses gateways
Ejabberd

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.

RFC 2136

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
ZoneMaster
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.

Knot

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 😛.

l'article d'Alarig

Et certbot dans tout ça ?

Je ne l'ai pas oublié ! J'utilise le plugin certbot-dns-rfc2136

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 :)

image1

Photo de Daniil Komov

Daniil Komov

--------------------------------------------------------------------------------

🏠 Home