Ce blog n'a d'autre prétention que de me permettre de mettre à la disposition de tous des petits textes que j'écris. On y parle surtout d'informatique mais d'autres sujets apparaissent parfois.
Première rédaction de cet article le 6 novembre 2025
Ce matin, si vous avez testé, un de mes domaines personnels,
bortzmeyer.fr, présentait des problèmes
DNSSEC. Que s'est-il passé ? Était-ce de ma
faute ? Va t-on tous mourir ?
Le signalement a été fait par un collègue (via
Matrix puisqu'il était en télétravail et pas
moi) : son résolveur DNS
répondait SERVFAIL (SERver FAILure) pour
l'enregistrement de type TXT de
bortzmeyer.fr. C'est le type utilisé pour
SPF donc c'est
ennuyeux (le domaine principal, bortzmeyer.org,
avait le même problème) :
% dig bortzmeyer.fr TXT ; <<>> DiG 9.20.15-1~deb13u1-Debian <<>> bortzmeyer.fr TXT ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: SERVFAIL, id: 28082 ;; flags: qr rd ra; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 1232 ; EDE: 6 (DNSSEC Bogus) ;; QUESTION SECTION: ;bortzmeyer.fr. IN TXT ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) ;; WHEN: Thu Nov 06 08:17:48 UTC 2025 ;; MSG SIZE rcvd: 48
On voit que c'est un problème DNSSEC puisque l'EDE (Extended DNS Error, cf. RFC 8914) nous le dit (« 6 (DNSSEC Bogus) ») et aussi parce que, si on désactive la validation (cd = Checking Disabled), ça marche :
% dig +cd bortzmeyer.fr TXT ; <<>> DiG 9.20.15-1~deb13u1-Debian <<>> +cd bortzmeyer.fr TXT ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 44332 ;; flags: qr rd ra cd; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags: do; udp: 1232 ; EDE: 6 (DNSSEC Bogus) ;; QUESTION SECTION: ;bortzmeyer.fr. IN TXT ;; ANSWER SECTION: bortzmeyer.fr. 45 IN TXT "DNS is innocent" bortzmeyer.fr. 45 IN TXT "v=spf1 mx -all" bortzmeyer.fr. 45 IN TXT "BTC" "1HtNJ6ZFUc9yu9u2qAwB4tGdGwPQasQGax" bortzmeyer.fr. 45 IN RRSIG TXT 13 2 86400 ( 20251113173453 20251030142433 32088 bortzmeyer.fr. J1arZUBP+G5nc54zetyD45rqDyURJeZyFjRRkixQrPok BPxNkyt1RHzdH78a0rM93Zzu0q+N/zzxMQScl0KVHw== ) bortzmeyer.fr. 45 IN RRSIG TXT 13 2 86400 ( 20251113215344 20251030142433 32088 bortzmeyer.fr. bWAlKMSQPDALOeEmwwfh47qGjloBK3YRH3rGydVJBis4 lFIsIsE09bkqviBiGyjNqpS/loFaiMS4FRIeh06jwg== ) ;; Query time: 0 msec ;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP) ;; WHEN: Thu Nov 06 08:17:51 UTC 2025 ;; MSG SIZE rcvd: 372
Le résultat de dig donne aussi une première explication : il y a deux signatures DNSSEC, faites avec la même clé, la 32088. Ce n'est pas normal. (Les enregistrements des autres types, comme SOA ou MX, n'avaient pas de problème.)
Le test avec Zonemaster montrait que les enregistrements NSEC3, qui servent à prouver la non-existence d'un nom ou d'un type d'enregistrement, étaient également cassés. Le test avec DNSviz donne une idée du problème : « RRSIG bortzmeyer.fr/TXT alg 13, id 32088: The cryptographic signature of the RRSIG RR does not properly validate. ».
Un test avec les sondes RIPE Atlas montre que le problème n'est pas local. Toutes les sondes utilisant un résolveur DNS validant voient le problème (les seules où ça marche sont celles qui ne valident pas) :
% blaeu-resolve --displayvalidation --requested 200 --type TXT bortzmeyer.fr [ERROR: SERVFAIL] : 147 occurrences ["btc" "1htnj6zfuc9yu9u2qawb4tgdgwpqasqgax" "dns is innocent" "v=spf1 mx -all"] : 31 occurrences Test #136108974 done at 2025-11-06T08:24:58Z
L'explication du problème a été trouvée en examinant les fichiers de zone, avant et après la signature. Dans le fichier non signé, celui que j'édite, on trouve cette ligne :
; Type WALLET, enregistré à l'IANA, mais pas encore connu des logiciels @ IN TYPE262 \# 39 03425443 223148744E4A365A465563397975397532714177423474476447775051617351476178
Mais dans le fichier signé par OpenDNSSEC, on trouve :
bortzmeyer.fr. 86400 IN TXT "BTC" "1HtNJ6ZFUc9yu9u2qAwB4tGdGwPQasQGax"
Le type 262 (WALLET) est devenu un type 16 (TXT). Ce n'est pas du tout normal, c'est clairement une bogue du signeur d'OpenDNSSEC. Creusons un peu. Le type WALLET est un type d'enregistrement, et ce type est officiellement enregistré à l'IANA. Cela fait plus d'un an qu'il est annoncé dans mes domaines personnels. Comme, à l'époque, il n'était pas connu des logiciels, j'ai utilisé la syntaxe des types inconnus, syntaxe normalisée depuis plus de vingt ans, dans le RFC 3597. La traduction en TXT n'a pas de sens, car, si l'enregistrement de type WALLET a bien des données structurées comme le TXT, c'est un type différent. Le signeur a apparemment calculé une signature pour les TXT, une pour le WALLET, puis traduit le WALLET en TXT, cassant tout (les signatures s'appliquent à un ensemble d'enregistrements, pas à un enregistrement unique, donc deux TXT n'ont qu'une signature) :
bortzmeyer.fr. 86400 IN TXT "v=spf1 mx -all" bortzmeyer.fr. 86400 IN TXT "DNS is innocent" bortzmeyer.fr. 86400 IN RRSIG TXT 13 2 86400 20251113215344 20251030142433 32088 bortzmeyer.fr. bWAlKMSQPDALOeEmwwfh47qGjloBK3YRH 3rGydVJBis4lFIsIsE09bkqviBiGyjNqpS/loFaiMS4FRIeh06jwg== bortzmeyer.fr. 86400 IN TXT "BTC" "1HtNJ6ZFUc9yu9u2qAwB4tGdGwPQasQGax" bortzmeyer.fr. 86400 IN RRSIG TXT 13 2 86400 20251113173453 20251030142433 32088 bortzmeyer.fr. J1arZUBP+G5nc54zetyD45rqDyURJeZyF jRRkixQrPokBPxNkyt1RHzdH78a0rM93Zzu0q+N/zzxMQScl0KVHw==
C'est ce fichier erroné (l'erreur cassait aussi les chaines NSEC3) qui a été produit et distribué aux serveurs faisant autorité, provoquant les problèmes détectés. La rupture des NSEC3 se voyait lorsqu'on demandait un nom non existant (ici avec les sondes Atlas) :
% blaeu-resolve --displayvalidation --requested 200 --type TXT doesnotexist.bortzmeyer.fr [ERROR: SERVFAIL] : 144 occurrences [ERROR: NXDOMAIN] : 28 occurrences Test #136110101 done at 2025-11-06T08:35:32Z
Ce problème a été signalé sur la liste de diffusion d'OpenDNSSEC. Notez que ce logiciel est en fin de vie (son successeur, Cascade, est actuellement en version alpha) donc je ne sais pas encore si la bogue sera traitée.
Pour réparer le problème, j'ai retiré l'enregistrement WALLET et tout remarche. Ça va mieux (ici, certains résolveurs avaient encore les données erronnées dans leur mémoire) :
% blaeu-resolve --displayvalidation --requested 200 --type TXT bortzmeyer.fr ["dns is innocent" "v=spf1 mx -all"] : 65 occurrences [ (Authentic Data flag) "dns is innocent" "v=spf1 mx -all"] : 128 occurrences [ERROR: SERVFAIL] : 4 occurrences [] : 1 occurrences Test #136145680 done at 2025-11-06T14:29:36Z
Le plus curieux est que cette bogue se soit déclenchée en l'absence de tout changement. Douze jours auparavant, il y avait le même fichier de zone (l'enregistrement WALLET datait de plus d'un an) et tout allait bien.
Quelle(s) leçon(s) à en tirer ? Que l'informatique, c'est compliqué, que les logiciels ont des bogues et que, si on est très intolérant aux problèmes, il ne faut pas chercher les cas exotiques. Et que le bon fonctionnement de l'Internet ne repose pas sur des règles bureaucratiques et des processus mais sur la réactivité, à la fois pour signaler les problèmes (merci à Marc van der Wal pour cela), et pour les traiter lorsqu'ils sont signalés.
Auteur(s) du livre : Bill François
Éditeur : Champs
978-2-0804-9396-5
Publié en 2023
Première rédaction de cet article le 5 novembre 2025
Vous aimez les trucs qu'il y a dans l'eau, genre poissons, langoustes ou autres bestioles bien plus bizarres ? Pas pour les manger, non, mais pour s'instruire. Dans ce livre très vivant, vous apprendrez certainement plein de choses sur les habitant·es des mers.
L'auteur raconte les caractéristiques les plus étonnantes d'un bon nombre de « génies des mers » (connaitre un peu de physique peut aider, car l'auteur utilise souvent des concepts de cette science). Comment l'exocet arrive à « voler » (divulgâchage : il ne vole pas) ; pourquoi n'y a t-il pas de petits animaux à sang chaud dans les mers ; comment le cachalot gère sa respiration avant de plonger (autre divulgâchage : il ne remplit pas ses poumons d'air, cela l'empêcherait de plonger) ; pourquoi les torpilles qui vivent dans l'Amazone produisent du courant sous une tension plus élevée ; pourquoi le requin du Groenland vit si vieux… Et il y en a beaucoup d'autres, parfois connus, parfois inconnus (de moi).
C'est bien écrit, très dynamique, et, après l'avoir lu, vous aurez envie de plonger dans l'océan le plus proche pour aller voir de vous-même.
Première rédaction de cet article le 3 novembre 2025
Puisque je suis occupé à transférer beaucoup de noms de domaine vers un autre Bureau d'Enregistrement (BE), c'est l'occasion de se demander « et si c'est un nom de domaine internationalisé, que se passe-t-il ? ».
Sur les transferts d'un BE à un autre, j'ai déjà commis un article sur ce
sujet. Mais j'ai dû, depuis, transférer un IDN,
potamochère.fr. Vous
savez que les IDN sont, pour des raisons complexes (et non, ce n'est
pas parce que le DNS est limité à ASCII,
essayez vous-même, vous verrez) encodés en
Punycode (RFC 3492). potamochère.fr est donc encodé
en xn--potamochre-66a.fr. Normalement, ce n'est
qu'un détail technique, invisible aux utilisateurs mais, hélas,
dix-neuf ans après la sortie du premier RFC sur les IDN (RFC 3490), certains logiciels continuent à présenter à
l'utilisateur la forme en Punycode, en violation du RFC 2277.
Ainsi, le BE gagnant permet de commander le transfert en
utilisant la forme en joli Unicode lisible
mais, lors de l'affichage de la liste des serveurs actuels, le BE
gagnant montre du Punycode : 
Même chose pour la configuration des serveurs : 
Ce qui est amusant est que les courriers de ce même BE respectent tous le bon encodage et sont donc lisibles :
Subject: [potamochère.fr] Notification de transfert From: support@netim.com Date: Sun, 2 Nov 2025 12:19:26 +0100 (CET) 02/11/2025 Cher client, Ceci est une notification au sujet du transfert de votre domaine potamochère.fr vers NETIM.
Alors qu'en général, le courrier est moins internationalisé que le Web.
D'un BE à l'autre, les choses sont différentes. Par exemple, chez le BE perdant :
Subject: [GANDI] IMPORTANT: Transfert sortant vers un autre prestataire du domaine xn--potamochre-66a.fr From: no-reply@gandi.net Date: Sun, 02 Nov 2025 11:20:29 -0000 A l'attention de : Stéphane Bortzmeyer Transfert du nom de domaine: xn--potamochre-66a.fr Registrar demandeur: <NETIM SARL>
Et idem sur leur serveur Web : 
Corriger tous les endroits où l'IDN n'est pas traité proprement va prendre du temps…
Première rédaction de cet article le 2 novembre 2025
J'ai déjà parlé de Cascade, un logiciel actuellement en développement, qui automatise un certain nombre de tâches nécessaires pour DNSSEC, comme la re-signature ou le remplacement des clés. Le projet avance vite donc voyons quelques nouveaux essais.
D'abord, puisque Cascade tire son nom du traitement en plusieurs étapes d'une tâche, avec la possibilité d'insérer des validations à chaque étape, essayons la validation manuelle (j'ai déjà montré la validation automatique dans mon précédent article). Comme pour la validation automatique, vous pouvez valider la zone avant la signature et/ou après. Comme pour la validation automatique, vous devez indiquer, dans le fichier qui décrit une politique de gestion, que des examens (review) sont obligatoires :
% cat /etc/cascade/policies/default.toml … [loader.review] required = true … [signer.review] required = true …
Par contre, et contrairement à ce qui se passe pour la validation
automatique, vous n'indiquez pas de programme à
exécuter (hook). Cela signifiera, pour Cascade,
qu'il faut une validation manuelle. Vous rechargez alors les
politiques (cascade policy reload) et (c'est
une bogue de la version de développement), vous redémarrez le
serveur. Lorsqu'une nouvelle version d'une zone est prête, et que
vous l'indiquez au serveur (cascade zone reload
example.org), vous voyez ceci :
% cascade zone status example.org Status report for zone 'example.org' using policy 'default' ✔ Waited for a new version of the example.org zone ✔ Loaded version 2025102700 Loaded at 2025-10-27T08:47:38+00:00 (13s ago) Loaded 243 B and 4 records from the filesystem in 0 seconds • Waiting for approval to sign version 2025102700 ! Zone will be held until manually approved Approve with: cascade zone approve --unsigned example.org 2025102700 Reject with: cascade zone reject --unsigned example.org 2025102700
La zone n'est pas encore signée, vous devez la
valider. (Rappelez-vous qu'on peut imposer une validation de la zone
non signée, de la zone signée, ou bien des deux.) Et Cascade vous
indique comment faire, mais pas comment récupérer la zone à
signer. Pour cela, le plus simple est un transfert de zones (RFC 5936) depuis le serveur indiqué par les
directives .servers dans la configuration de Cascade.
% cat /etc/cascade/config.toml … [loader] review.servers = ["127.0.0.1:4541", "[::1]:4541"] [signer] review.servers = ["127.0.0.1:4542", "[::1]:4542"] … % dig @127.0.0.1 -p 4541 example.org AXFR …
On peut alors examiner la zone. Mettons qu'elle nous satisfasse. On
va alors suivre les instructions qu'affichait cascade zone
status :
% cascade zone approve --unsigned example.org 2025102700 Approved unsigned zone 'example.org' with serial number 2025102700
Personnellement, je ne vois pas trop l'intérêt d'examiner manuellement la zone non signée (on pourrait aussi examiner le fichier de zone qu'on a donné à Cascade) et je vais plutôt regarder la zone signée, qui est servie sur un autre port.
Autre activité, Cascade permet également d'automatiser une opération délicate : le remplacement des clés. Cette opération est nécessaire si on découvre qu'une clé privée a été compromise. Et on peut aussi estimer que le remplacement est utile, même en l'absence de compromission, pour pratiquer les procédures et vérifier que tout se passera bien lorsqu'on aura vraiment besoin d'un remplacement. Supposons une politique avec deux clés, la KSK (Key Signing Key, qui ne signe que les clés) et la ZSK (Zone Signing Key, qui signe le reste). D'abord, l'état initial :
% dig +multi +dnssec internautique.fr DNSKEY … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 30159 ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 … ;; ANSWER SECTION: internautique.fr. 3600 IN DNSKEY 257 3 15 ( IDypc2j/kbE3iHtnbry715I6jwgVEPMLz1+QV9GNgMA= ) ; KSK; alg = ED25519 ; key id = 33113 internautique.fr. 3600 IN DNSKEY 256 3 15 ( K+sBrJFESzsLwD6AFGW+zZrydRjk5QRtbshG4AKV/jI= ) ; ZSK; alg = ED25519 ; key id = 56167 internautique.fr. 3600 IN RRSIG DNSKEY 15 2 3600 ( 20251109195246 20251025195246 33113 internautique.fr. ARucHmrIB8ivtduHEzphj2SJEGRZ7BlP5c7686dD2DTy TDJpZc9Rcerrh5okki3rKKOsFMqOwXunlNl+cgGcBA== ) … ;; WHEN: Mon Oct 27 09:06:13 CET 2025
Nous avons dans le DNS la KSK, d'identification 33113, et la ZSK, d'identification 56167. C'est aussi ce que nous dit Cascade :
% cascade zone status --detailed internautique.fr
…
✔ Published version 2025102612
Published zone available on 127.0.0.1:8053
DNSSEC keys:
ZSK tagged 56167:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+56167.key
Actively used for signing
KSK tagged 33113:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key
Actively used for signing
Details:
key file:///var/db/cascade/keys/Kinternautique.fr.+015+56167.key expires at 2025-11-02T14:51:53Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key expires at 2025-12-26T14:51:53Z
Et c'est aussi ce qu'on peut voir avec DNSviz. Maintenant, commençons le remplacement de la ZSK :
% cascade keyset internautique.fr zsk start-roll Manual key roll for internautique.fr successful
Et voici le résultat, une nouvelle ZSK est apparue, la 30906 :
% cascade zone status --detailed internautique.fr
Status report for zone 'internautique.fr' using policy 'default'
✔ Waited for a new version of the internautique.fr zone
✔ Loaded version 2025101721
Loaded at 2025-10-26T14:51:53+00:00 (17h 16m 7s ago)
Loaded 333 B and 7 records from the filesystem in 0 seconds
✔ Waited for approval to sign version 2025101721
✔ Approval received to sign version 2025101721, signing requested
✔ Signed version 2025101721 as version 2025102701
Signing requested at 2025-10-27T08:07:36+00:00 (24s ago)
Signing started at 2025-10-27T08:07:36+00:00 (24s ago)
Signing finished at 2025-10-27T08:07:36+00:00 (24s ago)
Collected 8 records in 0s, sorted in 0s
Generated 6 NSEC(3) records in 0s
Generated 8 signatures in 0s (8 sig/s)
Inserted signatures in 0s (8 sig/s)
Took 0s in total, using 1 threads
Current action: Finished
✔ Waited for approval to publish version 2025102701
✔ Published version 2025102701
Published zone available on 127.0.0.1:8053
DNSSEC keys:
KSK tagged 33113:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key
Actively used for signing
ZSK tagged 30906:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key
ZSK tagged 56167:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+56167.key
Actively used for signing
Details:
ZskRoll: CacheExpire1(3600)
Wait until 2025-10-27T09:07:36Z to let caches expire
For the next step run:
cascade keyset internautique.fr zsk cache-expired1
automation is enabled for this step.
key file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key expires at 2025-12-26T14:51:53Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key expires at 2025-11-03T08:07:27Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+56167.key expires at 2025-11-02T14:51:53Z
À la fin, c'est bon, le remplacement s'est fait sans autre intervention, et en respectant les durées nécessaires pour que, à tout moment, tous les résolveurs DNS de la planète aient un chemin de validation complet, depuis la racine :
% cascade zone status --detailed internautique.fr
Status report for zone 'internautique.fr' using policy 'default'
✔ Waited for a new version of the internautique.fr zone
✔ Loaded version 2025101721
Loaded at 2025-10-26T14:51:53+00:00 (23h 45m 37s ago)
Loaded 333 B and 7 records from the filesystem in 0 seconds
✔ Waited for approval to sign version 2025101721
✔ Approval received to sign version 2025101721, signing requested
✔ Signed version 2025101721 as version 2025102705
Signing requested at 2025-10-27T12:07:56+00:00 (2h 29m 34s ago)
Signing started at 2025-10-27T12:07:56+00:00 (2h 29m 34s ago)
Signing finished at 2025-10-27T12:07:56+00:00 (2h 29m 34s ago)
Collected 8 records in 0s, sorted in 0s
Generated 5 NSEC(3) records in 0s
Generated 8 signatures in 0s (8 sig/s)
Inserted signatures in 0s (8 sig/s)
Took 0s in total, using 1 threads
Current action: Finished
✔ Waited for approval to publish version 2025102705
✔ Published version 2025102705
Published zone available on 127.0.0.1:8053
DNSSEC keys:
KSK tagged 33113:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key
Actively used for signing
ZSK tagged 30906:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key
Actively used for signing
Details:
key file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key expires at 2025-11-03T08:07:27Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key expires at 2025-12-26T14:51:53Z
Vous pouvez admirer l'état final dans DNSviz.
Maintenant, remplaçons la KSK. C'est un peu plus délicat car il y
a une opération manuelle à faire, auprès de la zone parente
(.fr dans notre
cas).
On voit l'état initial sur DNSviz. Démarrons l'opération :
% cascade keyset internautique.fr ksk start-roll
Manual key roll for internautique.fr successful
% cascade zone status --detailed internautique.fr
Status report for zone 'internautique.fr' using policy 'default'
✔ Waited for a new version of the internautique.fr zone
✔ Loaded version 2025101721
Loaded at 2025-10-26T14:51:53+00:00 (1day 19h 29m 50s ago)
Loaded 333 B and 7 records from the filesystem in 0 seconds
✔ Waited for approval to sign version 2025101721
✔ Approval received to sign version 2025101721, signing requested
✔ Signed version 2025101721 as version 2025102801
Signing requested at 2025-10-28T10:21:36+00:00 (7s ago)
Signing started at 2025-10-28T10:21:36+00:00 (7s ago)
Signing finished at 2025-10-28T10:21:36+00:00 (7s ago)
Collected 8 records in 0s, sorted in 0s
Generated 7 NSEC(3) records in 0s
Generated 8 signatures in 0s (8 sig/s)
Inserted signatures in 0s (8 sig/s)
Took 0s in total, using 1 threads
Current action: Finished
✔ Waited for approval to publish version 2025102801
✔ Published version 2025102801
Published zone available on 127.0.0.1:8053
DNSSEC keys:
KSK tagged 49915:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+49915.key
Actively used for signing
ZSK tagged 30906:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key
Actively used for signing
KSK tagged 33113:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key
Actively used for signing
Details:
KskRoll: CacheExpire1(3600)
Wait until 2025-10-28T11:21:36Z to let caches expire
For the next step run:
cascade keyset internautique.fr ksk cache-expired1
automation is enabled for this step.
key file:///var/db/cascade/keys/Kinternautique.fr.+015+49915.key expires at 2025-12-28T10:21:27Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key expires at 2025-12-26T14:51:53Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key expires at 2025-11-03T08:07:27Z
La nouvelle KSK, la 49915, est publiée. Une fois le temps nécessaire écoulé (toujours cette histoire de la mémorisation, par les résolveurs, des données), Cascade nous dit qu'il faut maintenant changer la DS :
% cascade zone status --detailed internautique.fr
Status report for zone 'internautique.fr' using policy 'default'
✔ Waited for a new version of the internautique.fr zone
✔ Loaded version 2025101721
Loaded at 2025-10-26T14:51:53+00:00 (1day 23h 59m 24s ago)
Loaded 333 B and 7 records from the filesystem in 0 seconds
✔ Waited for approval to sign version 2025101721
✔ Approval received to sign version 2025101721, signing requested
✔ Signed version 2025101721 as version 2025102806
Signing requested at 2025-10-28T14:22:01+00:00 (29m 15s ago)
Signing started at 2025-10-28T14:22:01+00:00 (29m 15s ago)
Signing finished at 2025-10-28T14:22:01+00:00 (29m 15s ago)
Collected 8 records in 0s, sorted in 0s
Generated 7 NSEC(3) records in 0s
Generated 8 signatures in 0s (8 sig/s)
Inserted signatures in 0s (8 sig/s)
Took 0s in total, using 1 threads
Current action: Finished
✔ Waited for approval to publish version 2025102806
✔ Published version 2025102806
Published zone available on 127.0.0.1:8053
DNSSEC keys:
KSK tagged 33113:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key
Actively used for signing
KSK tagged 49915:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+49915.key
Actively used for signing
ZSK tagged 30906:
Reference: file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key
Actively used for signing
Details:
KskRoll: Propagation2
Check that all nameservers of the parent zone have the following RRset (or equivalent):
internautique.fr. 3600 IN DS 49915 15 2 0ADF31249F82B92BC7A8068CB2D6117C35F78E5994F1AFF7BCFE6999A71F5091
For the next step run:
cascade keyset internautique.fr ksk propagation2-complete <ttl>
automation is enabled for this step.
Automatic key roll state:
Roll KskRoll, state Propagation2:
Wait until the new DS RRset has propagated to all nameservers
of the parent zone. Try again after 2025-10-28T15:22:01Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+30906.key expires at 2025-11-03T08:07:27Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+33113.key expires at 2025-12-26T14:51:53Z
key file:///var/db/cascade/keys/Kinternautique.fr.+015+49915.key expires at 2025-12-28T10:21:27Z
Vous avez vu, à la fin ? Il faut placer la DS
internautique.fr. 3600 IN DS 49915 15 2
0ADF31249F82B92BC7A8068CB2D6117C35F78E5994F1AFF7BCFE6999A71F5091
dans la zone parente, ce qui se fait typiquement via l'interface
(Web ou API) de son BE.
Ce sera la seule opération manuelle qu'il faudra faire.
L'ancienne KSK sera supprimée automatiquement en temps utile. (Ici, la 33113 est encore présente, et les clés seront signées avec les deux KSK.)
Dans l'opération précédente, on avait deux clés, la KSK et la ZSK. C'est la configuration la plus courante. Mais elle n'est pas obligatoire. Elle avait été promue à une époque car elle résolvait un problème spécifique à l'algorithme de cryptographie RSA : ce dernier laissait le choix entre des clés courtes, compactes et rapides mais peu sûres, et des clés longues, dont les avantages et inconvénients étaient à l'opposé. Avec RSA, avoir une ZSK courte et changée souvent, ainsi qu'une KSK longue et changée plus rarement, était logique. Mais avec les algorithmes comme ceux fondés sur des courbes elliptiques, ce n'est plus utile. Autant n'avoir qu'une clé, qu'on nomme souvent CSK, pour Common Signing Key. Dans Cascade, cela se configure dans la politique (rappelez-vous que vous pouvez avoir plusieurs politiques, donc qu'une même instance de Cascade peut gérer des zones avec CSK et des zones avec KSK/ZSK) :
% cat /etc/cascade/policies/one-key.toml … use-csk = true
Et voici ce qu'affiche Cascade :
% cascade zone status --detailed cours-dns.fr
Status report for zone 'cours-dns.fr' using policy 'one-key'
✔ Waited for a new version of the cours-dns.fr zone
✔ Loaded <serial number not yet known>
Loaded at 2025-10-31T13:23:23+00:00 (1day 5h 36m 3s ago)
Loaded 261 B and 5 records from the filesystem in 0 seconds
✔ Waited for approval to sign <serial number not yet known>
• Approval received to sign <serial number not yet known>, signing requested
DNSSEC keys:
CSK tagged 50973:
Reference: file:///var/db/cascade/keys/Kcours-dns.fr.+015+50973.key
Actively used for signing
Details:
key file:///var/db/cascade/keys/Kcours-dns.fr.+015+50973.key expires at 2026-10-30T13:01:19Z
Et vous pouvez voir graphiquement la clé unique avec DNSviz.
Allez, un autre point, la technique utilisée pour indiquer les noms absents. DNSSEC en permet deux, les NSEC du RFC 4034 et les NSEC3 du RFC 5155. Cascade permet de configurer l'un ou l'autre dans la politique :
type = "nsec" # type = "nsec3"
Ah, et une nouveauté relativement récente de Cascade, un moyen simple de tester si le démon tourne bien :
% cascade health Ok
Ce qui est très pratique dans une supervision automatisée :
% sudo -u nagios /usr/local/lib/nagios/plugins/check_cascade Cascade OK
Première rédaction de cet article le 16 octobre 2025
Allez, encore un résolveur DNS public européen, celui de Freifunk München. Rien d'extraordinaire mais rappelez-vous que, dans ce domaine, pluralisme et diversité sont cruciaux.
Des résolveurs DNS publics, il y en a beaucoup (j'en gère même un). Les utilisateurices et administrateurices système s'en servent pour des raisons variées, par exemple échapper à la censure. Mais ils ne sont pas tous équivalents, en terme de caractéristiques techniques, de fonctions (certains sont menteurs), de politique de gestion des données personnelles, etc. Le service de Freifunk München :
Faisons quelques essais techniques. Les adresses et noms à
utiliser sont
185.150.99.255, 5.1.66.255,
2001:678:e68:f000::, 2001:678:ed0:f000::,
dot.ffmuc.net,
https://doh.ffmuc.net/dns-query et
doq.ffmuc.net (je n'ai pas testé ce dernier).
D'abord, avec
dig :
% dig @2001:678:ed0:f000:: sci-hub.se
…
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24707
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
…
;; ANSWER SECTION:
sci-hub.se. 60 IN A 186.2.163.219
;; Query time: 34 msec
;; SERVER: 2001:678:ed0:f000::#53(2001:678:ed0:f000::) (UDP)
;; WHEN: Thu Oct 16 14:38:17 CEST 2025
;; MSG SIZE rcvd: 55
OK, c'est bon, tout marche, et en un temps raisonnable depuis ma connexion Free à Paris. (Évidemment, un résolveur public ne sera jamais aussi rapide qu'un résolveur local et il n'est donc pas raisonnable d'utiliser un résolveur public « pour les performances ».)
Et avec DoT ?
% dig +tls @2001:678:ed0:f000:: liberation.fr … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 31755 ;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1 … ;; ANSWER SECTION: liberation.fr. 300 IN A 3.165.113.53 liberation.fr. 300 IN A 3.165.113.101 liberation.fr. 300 IN A 3.165.113.127 liberation.fr. 300 IN A 3.165.113.118 ;; Query time: 62 msec ;; SERVER: 2001:678:ed0:f000::#853(2001:678:ed0:f000::) (TLS) ;; WHEN: Thu Oct 16 14:40:02 CEST 2025 ;; MSG SIZE rcvd: 106
Et du DoH ?
% kdig +https=https://doh.ffmuc.net/dns-query @185.150.99.255 bortzmeyer.fr NS
;; TLS session (TLS1.3)-(ECDHE-SECP256R1)-(RSA-PSS-RSAE-SHA256)-(AES-128-GCM)
;; HTTP session (HTTP/2-POST)-(doh.ffmuc.net/dns-query)-(status: 200)
;; ->>HEADER<<- opcode: QUERY; status: NOERROR; id: 0
;; Flags: qr rd ra ad; QUERY: 1; ANSWER: 7; AUTHORITY: 0; ADDITIONAL: 1
;; EDNS PSEUDOSECTION:
;; Version: 0; flags: ; UDP size: 512 B; ext-rcode: NOERROR
;; PADDING: 238 B
…
;; ANSWER SECTION:
bortzmeyer.fr. 85495 IN NS ns2.1984hosting.com.
bortzmeyer.fr. 85495 IN NS ns0.1984.is.
bortzmeyer.fr. 85495 IN NS ns4.bortzmeyer.org.
bortzmeyer.fr. 85495 IN NS ns-global.kjsl.com.
bortzmeyer.fr. 85495 IN NS puck.nether.net.
bortzmeyer.fr. 85495 IN NS ns2.bortzmeyer.org.
bortzmeyer.fr. 85495 IN NS ns2.1984.is.
;; Received 468 B
;; Time 2025-10-16 14:42:39 CEST
;; From 185.150.99.255@443(HTTPS) in 91.2 ms
C'est parfait, tout marche. Pour la dernière requête, celle faite avec DoH, notez que le résolveur valide bien les domaines signés.
Configurons maintenant un résolveur local pour faire suivre à
ffmuc, pour profiter de la mémorisation des
réponses par celui-ci. On va utiliser
Unbound et faire suivre en
TLS :
forward-zone:
name: "."
# Freifunk München
forward-addr: 2001:678:ed0:f000::@853#dot.ffmuc.net
forward-tls-upstream: yes
Et tout marche, notre résolveur local fera suivre ce qu'il ne sait
pas déjà à ffmuc.
ffmuc a-t-il plusieurs machines,
réparties par anycast ? On
peut regarder les identités de ces serveurs avec NSID (RFC 5001) :
% dig +nsid @5.1.66.255 www.phy.cam.ac.uk
…
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 512
; NSID: 64 6f 74 2e 66 66 6d 75 63 2e 6e 65 74 ("dot.ffmuc.net")
;; QUESTION SECTION:
;www.phy.cam.ac.uk. IN A
;; ANSWER SECTION:
www.phy.cam.ac.uk. 3591 IN CNAME tm-128-232-132-117.tm.uis.cam.ac.uk.
tm-128-232-132-117.tm.uis.cam.ac.uk. 3600 IN A 128.232.132.117
…
Le NSID est juste le nom du service. Un test avec les sondes RIPE Atlas montre qu'il n'y a en effet qu'un seul serveur.
Notez enfin que la configuration technique de ce service est publique ainsi que celle des autres services.
Est-ce que je vais utiliser ce service ? Non, il en existe plusieurs autres qui me conviennent (dont le mien, bien sûr, mais aussi celui de FDN) mais avoir le choix est une bonne chose.
Première rédaction de cet article le 16 octobre 2025
Dans beaucoup de TLD, le ou la simple titulaire ne peut pas acheter directement un nom de domaine, il faut passer par un Bureau d'Enregistrement (BE). Le transfert, l'opération qui consiste à changer de bureau d'enregistrement, est parfois une opération frustrante. Je suis en train de transférer mes noms de domaine personnels (et ils sont tous signés avec DNSSEC) donc voici quelques notes, au cas où.
Dans le cas de .fr,
l'obligation de passer par un BE est dans
la loi : « L'attribution des noms de domaine est assurée par
les offices d'enregistrement [sic ; il s'agit des
registres], par l'intermédiaire des
bureaux d'enregistrement. » (article L45-4 du Code des
postes et des communications électroniques). Pour les TLD sous contrat avec
l'ICANN, cette obligation est dans le contrat
entre l'ICANN et le registre. Vous pouvez trouver le nom du BE via
les
différents outils d'accès à l'information, cette information
est publique.
Le choix d'un BE est une opération complexe, déroutante pour la ou le titulaire d'un nom de domaine. Et on peut être amené à changer de BE, par exemple pour des raisons tarifaires. Ainsi, je transfère en ce moment mes domaines personnels, l'ancien BE, qui était une entreprise indépendante, ayant été racheté par un grand groupe qui a immédiatement considérablement augmenté les tarifs. (Problème courant quand une petite entreprise est rachetée par un grand groupe : celui-ci se dépêche de supprimer tout ce qui faisait l'intérêt de la petite entreprise pour les clients. On a vu cela avec Capitaine Train, par exemple.) Ces noms de domaine ont deux particularités :
Outre les contraintes techniques, le transfert est une opération délicate puisque les deux BE, le perdant et le gagnant, sont a priori des concurrents et que certains BE perdants font preuve d'une extrême mauvaise volonté, essayant de retenir de force leur client par tous les moyens. Le transfert est donc piloté par le BE gagnant, puisqu'il est le plus motivé à ce que l'opération réussisse. Et, pour compliquer encore la question, il faut noter qu'il y a un problème de sécurité : un transfert peut être malveillant, par exemple parce qu'un méchant essaie de détourner un nom de domaine à son profit.
Les règles qui encadrent le transfert d'un nom de domaine varient
selon les registres. Durant ma migration,
j'ai transféré du .org,
du .net et du
.fr.
Le cheminement normal est le suivant :
Voici la demande faite auprès d'un BE gagnant (ici, Netim) :
Ensuite, comme je l'ai dit, il faut s'assurer que la demande de
transfert n'est pas en fait une tentative de détournement du nom de
domaine. La principale mesure de sécurité est nommée, selon les BE,
code d'autorisation, clé d'autorisation, code de transfert,
auth code, authInfo… C'est un
mot de passe qui a été généré par l'ancien BE et transmis au
registre, et qui, indiqué par le titulaire au nouveau BE, permet de
vérifier que la demande est légitime. (Si vous êtes fana de
technique et de sécurité, le RFC 9154 propose
une technique alternative.) Voici ici l'obtention de ce
code sur l'ancien BE (ici, Gandi). Je vous rassure, le code affiché
n'est plus le bon :
Il existe d'autres façons de transmettre ce mot de passe au titulaire, par exemple par courrier électronique, ce qui permet de valider l'adresse mais expose à pas mal de risques de sécurité. La sécurité, ce n'est de toute façon jamais simple et il faut rappeler que, s'il y a des BE qui tentent de gêner les transferts sortants, en invoquant la sécurité, il y a également des malveillants qui tentent de profiter d'une sécurité trop faible pour détourner un nom. Un compromis est donc nécessaire.
Typiquement, vous recevez une notification du BE perdant vous informant de la demande de transfert, ce qui peut permettre de s'y opposer, si elle n'était pas légitime. En effet, pour éviter qu'un BE de mauvaise volonté ne bloque le transfert indéfiniment en ne donnant pas accès au code d'autorisation, le transfert est typiquement effectué automatiquement au bout d'un certain temps. Faites donc bien attention aux messages de votre BE ! Et rappelez-vous qu'il y a autant de transferts légitimes retardés ou bloqués par des procédures kafkaïennes qu'il y a de domaines détournés parce que les procédures de sécurité étaient insuffisantes. J'en profite pour insister sur ce point : gérez vos noms de domaines. Que vous soyez particulier, profession libérale, association, petite entreprise, pensez à désigner plusieurs personnes responsables qui vont recevoir les messages du BE et les lire, et agir. Vous trouvez facilement en ligne d'innombrables témoignages de gens qui ont perdu leur nom de domaine par négligence.
Voici un exemple de message reçu de l'ancien BE (ici, Gandi) :
Subject: [GANDI] IMPORTANT: Transfert sortant vers un autre prestataire du domaine sources.org From: no-reply@gandi.net To: [Moi] Date: Mon, 01 Sep 2025 13:50:47 -0000 X-Mailer: Gandi Notification Mailer v1.2.12 Vous êtes contacté car vous êtes inscrit comme contact du nom de domaine suivant. A l'attention de : Stéphane Bortzmeyer Transfert du domaine: sources.org Registrar demandeur: <NETIM SARL> GANDI a été notifié d'une demande de transfert pour ce nom de domaine. GANDI a reçu à la date 2025-09-01 13:44:18Z une notification selon laquelle vous avez demandé le transfert de ce nom de domain vers un autre bureau d'enregistrement. Si vous souhaitez poursuivre le transfert, vous n'avez pas besoin de répondre à cette notification. Faute de réponse de votre part avant le 2025-09-06 13:44:18, le transfert sera automatiquement validé. Vous pouvez cependant accélérer ou refuser le transfert, en vous rendant à cette adresse avant le 2025-09-06 13:44:18: [URL où il faudra taper le code d'autorisation].
Vérifiez ensuite avec un RDDS que le domaine a bien été transféré. Ici, on va utiliser le classique client whois :
% whois cyberstructure.fr … domain: cyberstructure.fr status: ACTIVE … registrar: NETIM
Parfait, le domaine est bien passé chez Netim.
Revenons à la technique : pensez à vérifier (avec des outils
comme Zonemaster et DNSviz) le domaine après le
transfert. J'ai vu par exemple un BE qui, lors d'un transfert
entrant, limitait le nombre de serveurs faisant autorité à cinq et
supprimait silencieusement les autres. Un exemple avec
dig, où on interroge un des serveurs faisant
autorité pour .fr :
% dig @d.nic.fr cyberstructure.fr NS … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 18712 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 9, ADDITIONAL: 1 … ;; AUTHORITY SECTION: cyberstructure.fr. 3600 IN NS fns1.42.pl. cyberstructure.fr. 3600 IN NS ns4.bortzmeyer.org. cyberstructure.fr. 3600 IN NS fns2.42.pl. cyberstructure.fr. 3600 IN NS ns2.afraid.org. cyberstructure.fr. 3600 IN NS puck.nether.net. cyberstructure.fr. 3600 IN NS ns2.bortzmeyer.org. cyberstructure.fr. 3600 IN NS ns1.bortzmeyer.org.
Et vérifiez que la liste des serveurs faisant autorité pour votre
domaine est la bonne. (Attention, la mise à jour des serveurs du
domaine parent peut ne pas être immédiate ; pour
.fr, à l'heure actuelle, c'est toutes les dix minutes.)
Et DNSSEC ? Vous avez peut-être remarqué, sur l'image où le BE perdant affichait le code d'autorisation, un avertissement sur DNSSEC. La sécurité supplémentaire qu'apporte DNSSEC se paie d'une complication supplémentaire pendant le transfert : si on utilise le BE comme hébergeur (ce n'est pas mon cas), il faut, soit assurer le passage d'une clé DNSSEC à une autre (ce qui, étant donné le fait que les résolveurs DNS mémorisent des informations, va nécessiter la publication simultanée de deux clés, et une collaboration étroite entre les deux BE, dont je rappelle qu'ils sont concurrents), soit supprimer DNSSEC temporairement, ce que conseille ici le BE. Dans mon cas, comme je suis mon propre hébergeur, il n'y a pas trop de problèmes, à part qu'entre Gandi et Netim, l'enregistrement DS (Delegation Signer) a été supprimé lors du transfert (chaque BE dit que c'est de la faute de l'autre) et qu'il a donc fallu le rétablir ensuite (voyez ci-dessus le point sur l'importance d'un test technique après transfert).
Voilà, dans mon cas, les choses se sont plutôt bien passées, il
n'y a pas eu de blocage, et les transferts ont été bouclés en une
heure ou deux. Il me reste à transférer le domaine de ce blog
bortzmeyer.org, que j'ai gardé pour la fin ; la
méthode de bon sens est en effet de commencer par les domaines les
moins importants.
Première rédaction de cet article le 13 octobre 2025
Annoncé officiellement le 7 octobre, Cascade est le successeur d'OpenDNSSEC. Ce programme sert à gérer automatiquement les opérations répétitives liées à DNSSEC comme la re-signature ou le remplacement d'une clé. (Notez que Cascade évolue vite en ce moment et que cet article ne sera pas mis à jour. J'en écrirai d'autres.)
OpenDNSSEC est, depuis 15
ans, le logiciel utilisé par mes domaines personnels (comme
bortzmeyer.org par lequel vous êtes passé pour
voir cet article) mais aussi par plusieurs TLD comme
.fr. Mais il est désormais en fin de vie (cela
a été
annoncé le 3 octobre), il n'y a plus que les mises à jour de
sécurité. La même organisation, NLnet Labs, a développé un
successeur, Cascade. Celui-ci
est actuellement en alpha (ne l'utilisez pas pour la
production !) et voici un premier essai.
DNSSEC, contrairement à ce qui se passe avec le DNS d'antan, nécessite des actions périodiques. La plus évidente est la re-signature, car les signatures DNSSEC ont une date d'expiration (pour éviter les attaques par rejeu). Ici, cette signature expire le 18 octobre 2025 (le « 20251018235702 ») :
% dig +dnssec cyberstructure.fr TXT
…
;; ANSWER SECTION:
cyberstructure.fr. 86400 IN TXT "v=spf1 mx -all"
cyberstructure.fr. 86400 IN RRSIG TXT 13 2 86400 (
20251018235702 20251004235301 34065 cyberstructure.fr.
Hg/ModpW3pTLgUIcgp7yrVUxUyVAXdEIOtfONpN3pxG0
…
Mais il y a aussi la nécessité de pouvoir changer les clés cryptographiques utilisées, par exemple en cas de compromission, avérée ou suspectée. (La plupart des organisations changent les clés systématiquement, compromission ou pas, notamment pour être sûr de pouvoir le faire sans incident le jour où ça sera vraiment indispensable.) Cascade, comme OpenDNSSEC avant lui, automatise toutes ces tâches.
Cascade est développé en Rust, le langage à la mode, et qui pourrait remplacer une bonne partie du logiciel d'infrastructure de l'Internet, qui est quasi-uniquement en C (et C++). Il doit son nom au fait qu'une zone DNS qu'on va signer passe par plusieurs étapes successives, où on peut insérer différentes opérations, par exemple de validation.
Jouons donc un peu avec Cascade (j'ai bien dit que c'était en version alpha ; ne le faites pas en production). Notez que vous pouvez lire la documentation officielle au lieu de cet article. Mais j'ai bien dit que le logiciel était en version alpha, ne vous étonnez pas s'il manque beaucoup de choses dans la documentation (et dans le code, bien sûr). Il existe déjà des paquetages tout faits (mais évidemment pas encore distribués avec votre système d'exploitation) mais on va compiler, c'est plus amusant. Il faut donc une version de Rust qui marche. Rust est très pénible, avec ses changements permanents, qui font que certain·es conseillent même de récupérer et d'installer automatiquement une nouvelle version du compilateur chaque nuit. Mais, bon, la version qui est dans Debian stable (la version 13 de Debian) marche bien. Cascade est un logiciel libre, et il est hébergé sur GitHub :
% git clone https://github.com/NLnetLabs/cascade.git … # Installer les dépendances % apt install cargo pkg-config libssl-dev … % rustc --version rustc 1.85.0 (4d91de4e4 2025-02-17) (built from a source tarball) % cd cascade % cargo build …
Et vous n'avez plus qu'attendre que votre machine compile quelques
centaines de crates
Rust. Vous vous retrouvez alors avec deux exécutables,
./target/debug/cascade et
./target/debug/cascaded. Le second est le
démon qui va tourner en permanence. (Si vous
venez d'OpenDNSSEC, notez qu'il n'y a cette fois qu'un seul démon.)
Notez que ce démon, bien qu'il inclue un serveur DNS minimal, ne doit
pas être exposé sur l'Internet. La
configuration recommandée est de l'utiliser comme serveur maitre
caché, sur lequel s'alimenteront un ou plusieurs des serveurs faisant autorité pour
vos domaines. (Si vous venez d'OpenDNSSEC, notez que Cascade ne sait
actuellement pas écrire la zone sur le disque, il faut faire un
transfert de zones, cf. RFC 5936.) Le premier
exécutable, lui, est le programme que vous lancez pour accomplir
telle ou telle tâche, et qui parle au démon, qui fera le boulot.
Sinon, une autre solution, pour compiler, est de suivre les instructions :
cargo install --locked --git https://github.com/nlnetlabs/cascade cargo install --locked --branch keyset --git https://github.com/nlnetlabs/dnst
Et les exécutables atterriront dans votre ~/.cargo/bin.
Regardons les options des deux programmes :
% ./target/debug/cascade
Usage: cascade [OPTIONS] <COMMAND>
Commands:
config Manage Cascade's configuration
zone Manage zones
policy Manage policies
keyset Execute manual key roll or key removal commands
hsm Manage HSMs
template Print example config or policy files
Options:
-s, --server <IP:PORT> The cascade server instance to connect to [default: 127.0.0.1:4539]
--log-level <LEVEL> The minimum severity of messages to log [default: warning] [possible values: trace, debug, info,
warning, error, critical]
-h, --help Print help
-V, --version Print version
% ./target/debug/cascaded -h
Usage: cascaded [OPTIONS]
Options:
--check-config
Check the configuration and exit
--state <PATH>
The global state file to use
-c, --config <PATH>
The configuration file to load
--log-level <LEVEL>
The minimum severity of messages to log [possible values: trace, debug, info, warning, error, critical]
-l, --log <TARGET>
Where logs should be written to [possible values: stdout, stderr, file:<PATH>, syslog]
-d, --daemonize
Whether Cascade should fork on startup
-h, --help
Print help
-V, --version
Print version
Si vous avez procédé comme moi, un dernier truc à compiler, il nous faut aussi cascade-dnst, par la même organisation :
% cargo install --locked --branch keyset --git https://github.com/nlnetlabs/dnst
Vérifiez bien que les exécutables sont dans votre
PATH (par exemple, que
~/.cargo/bin y soit). Pour le démon, vous
pouvez aussi utiliser la variable
dnst-binary-path dans le
config.toml.
Pour utiliser Cascade, vous devrez, comme avec OpenDNSSEC, définir une politique (quel algorithme de cryptographie, quelle durée de vie des signatures, quand remplacer les clés, etc), indiquer quelles zones Cascade doit gérer, et avoir un serveur faisant autorité qui va récupérer la zone sur Cascade.
Maintenant, configurons le logiciel, toujours en suivant la
documentation. (Si vous oubliez cette étape, vous aurez un
message « Cascade couldn't be configured: could not load
the config file '/etc/cascade/config.toml': No such file or
directory (os error 2) ».) Vous avez avec Cascade un
fichier de configuration d'exemple,
./etc/config.template.toml, à la syntaxe
TOML. Le fichier d'exemple peut être utilisé
tel quel dans beaucoup de cas. J'ai juste modifié les paramètres de
journalisation :
[daemon]
log-level = "debug"
log-target = { type = "file", path = "/dev/stdout"}
# Mais log-target = { type = "syslog" } est bien aussi.
Lançons maintenant le démon (voyez plus loin les mécanismes pour
l'avoir en permanence). D'abord, on crée les répertoires configurés
dans le config.toml :
% sudo mkdir /var/lib/cascade % sudo chown $USER /var/lib/cascade % sudo mkdir /etc/cascade/policies
Puis on y va :
% ./target/debug/cascaded [2025-10-09T15:56:07.480Z] INFO cascaded: State file not found; starting from scratch … [2025-10-09T15:56:09.182Z] DEBUG cascade::state: Saved the global state (to '/var/lib/cascade/state.db') …
Maintenant que le démon tourne, testons le programme de contrôle :
% ./target/debug/cascade zone list %
Pas de message d'erreur, mais pas de zones configurées, c'est normal. (Si le démon ne tournait pas, vous auriez eu « [2025-10-09T15:59:37.183Z] ERROR cascade: Error: HTTP request failed: error sending request for url (http://127.0.0.1:4539/zone/) ».)
Maintenant, il faut créer une politique. La plus courte est :
version = "v1"
mais elle n'est pas très utile. Pour avoir un point de départ, on va demander à Cascade de nous générer un exemple :
% ./target/debug/cascade template policy > mypolicy.toml
(On peut aussi utiliser l'exemple en
etc/policy.template.toml.)
La politique contient l'algorithme de cryptographie utilisé (j'ai
choisi Ed25519, cf. RFC 8080) :
algorithm = "ED25519"
Je n'ai rien changé d'autre.
On copie le fichier
en /etc/cascade/policies/default.toml (on peut
évidemment avoir plusieurs politiques, je vous laisse choisir les
noms). Et on indique à Cascade de la charger (ce n'est à faire
qu'une fois, ce sera mémorisé) :
% ./target/debug/cascade policy reload
Policies reloaded:
- default added
% ./target/debug/cascade policy list
default
% ./target/debug/cascade policy show default
default:
zones: <none>
…
signer:
serial policy: date counter
signature inception offset: 86400 seconds
signature validity offset: 1209600 seconds
denial: NSEC
…
Bon, on peut maintenant ajouter une zone à gérer.
On crée un fichier de zone (je l'ai mis dans
/etc/cascade/zones mais vous faites comme vous
voulez). Et on dit à Cascade d'y aller, en indiquant la politique à
suivre :
% ./target/debug/cascade zone add --source /etc/cascade/zones/rutaba.ga --policy default rutaba.ga Added zone rutaba.ga
Et voilà :
% ./target/debug/cascade zone list rutaba.ga % ./target/debug/cascade zone status rutaba.ga Status report for zone 'rutaba.ga' using policy 'default' ✔ Waited for a new version of the rutaba.ga zone ✔ Loaded version 2025100901 Loaded at 2025-10-09T16:40:36+00:00 (17s ago) Loaded 246 B and 5 records from the filesystem in 0 seconds ✔ Auto approving signing of version 2025100901, no checks enabled in policy. ✔ Approval received to sign version 2025100901, signing requested ✔ Signed version 2025100901 as version 2025100902 Signing requested at 2025-10-09T16:40:36+00:00 (17s ago) Signing started at 2025-10-09T16:40:36+00:00 (17s ago) Signing finished at 2025-10-09T16:40:36+00:00 (17s ago) Collected 5 records in 0s, sorted in 0s Generated 4 NSEC(3) records in 0s Generated 5 signatures in 0s (5 sig/s) Inserted signatures in 0s (5 sig/s) Took 0s in total, using 1 threads Current action: Finished ✔ Auto approving publication of version 2025100902, no checks enabled in policy. ✔ Published version 2025100902 Published zone available on 127.0.0.1:4543
(L'option --detailed après
status vous donnera… des détails.)
Vous pouvez maintenant récupérer la zone signée :
% dig @localhost -p 4543 rutaba.ga AXFR
…
rutaba.ga. 600 IN SOA ns4.bortzmeyer.org. hostmaster.bortzmeyer.org. (
2025100902 ; serial
…
rutaba.ga. 3600 IN DNSKEY 257 3 15 (
oQjxu7aDwhDyqZbqPfx0e3I4Y+UowV0eYlssuXgiMw0=
) ; KSK; alg = ED25519 ; key id = 57782
…
rutaba.ga. 600 IN NS ns4.bortzmeyer.org.
rutaba.ga. 600 IN NS ns1.bortzmeyer.org.
…
rutaba.ga. 600 IN RRSIG SOA 15 2 600 (
20251023164036 20251008164036 64849 rutaba.ga.
xn3KC4qzitoUz5ABlJDTtMd9VGPU2l8dYpsd4bYjeCqo
vpjBdWcBbwwk1HENj5ESCLEIobpjNS3/rA4qidUvCg== )
rutaba.ga. 600 IN RRSIG NS 15 2 600 (
20251023164036 20251008164036 64849 rutaba.ga.
gWrzM1jYO7GJ234bH8JDejrxMhwtHrGWjxRM30gz37Fb
dxnSv2/DiAgOt5OhdTPKYc7+IX4gLR1DPAUlJgXqDA== )
rutaba.ga. 600 IN RRSIG DNSKEY 15 2 3600 (
20251023163739 20251008163739 57782 rutaba.ga.
t3wZ31rY7TgXY+QZXd/Yy7QtTmYdJy6v7Kuj6IN8G+m4
GycTLLn9W3lXErMS7f93RfkvXfERrwzbHqrTp1/oDg== )
…
;; Query time: 1 msec
;; SERVER: ::1#4543(localhost) (TCP)
;; WHEN: Thu Oct 09 16:43:24 UTC 2025
;; XFR size: 15 records (messages 1, bytes 1201)
(On verra plus loin comment configurer le « vrai » serveur de noms pour utiliser cette zone.)
Comme OpenDNSSEC, Cascade regarde sa montre pour savoir si un
délai suffisant s'est écoulé (rappelez-vous que les résolveurs DNS ont une mémoire, et que cela a
une grande importance opérationnelle ; dans le DNS, rien n'est
jamais instantané). Mais, contrairement à OpenDNSSEC, il demande au
résolveur local des tests (à l'heure actuelle, la machine qui fait
tourner Cascade doit donc avoir accès à un résolveur qui marche). En
attendant, cascade zone status --detailed vous
affichera des choses comme :
Roll AlgorithmRoll, state Propagation1:
Wait until the new DNSKEY RRset has propagated to all nameservers.
Try again after 2025-10-09T16:47:42Z
Voyons plutôt une fonction essentielle de Cascade : les étapes de
validation. Pour l'instant, je n'en ai configuré aucune. Mais, en
production, on veut éviter à tout prix ces ennuyeuses erreurs, qui plantent DNSSEC et
toute la zone. On va donc forcément valider la zone signée avant de
la publier. Le principe est d'écrire un programme (typiquement un
script shell) qui va lancer un programme de validation comme validns ou le
dnssec-verify de
BIND. On peut avoir cette étape de validation
avant la signature (pour tester le fichier de zone original) ou
après (ce que je fais ici). Essayons pour commencer avec un script
de validation méchant qui refuse tout :
% cat /etc/cascade/reviews/always-reject.sh
#!/bin/sh
logger "Rejecting ${CASCADE_ZONE} of serial ${CASCADE_SERIAL} from ${CASCADE_SERVER}"
♯ Cascade va utiliser le code de retour pou savoir si la zone est
# validée ou pas.
exit 1
On configure la politique (pas la configuration générale : valider ou pas fait partie de la politique) :
% cat /etc/cascade/policies/default.toml … # How signed zones are reviewed. [signer.review] required = true cmd-hook = "/etc/cascade/reviews/always-reject.sh"
On recharge la politique (cascade policy
reload) et la validation est configurée (Cascade étant en
version alpha, pour l'instant, il faut redémarrer le démon mais ce
n'est pas normal). Le résultat est l'attendu :
% ./target/debug/cascade zone status rutaba.ga … • Waiting for approval to publish version 2025101004 Configured to invoke /etc/cascade/reviews/always-reject.sh x An error occurred that prevents further processing of this zone version: x Signed zone was rejected at the review stage.
Et dans le journal du démon :
[2025-10-10T06:32:49.335Z] INFO cascade::units::zone_server: [RS2]: Executed hook '/etc/cascade/reviews/always-reject.sh' for signed zone 'rutaba.ga' at serial 2025101004
[2025-10-10T06:32:49.339Z] DEBUG cascade::units::zone_server: [RS2]: Hook '/etc/cascade/reviews/always-reject.sh' exited with status exit status: 1
[2025-10-10T06:32:49.339Z] DEBUG cascade::targets::central_command: [CC]: Event received: ReviewZone { name: Name(rutaba.ga.), stage: Signed, serial: Serial(2025101004), decision: Reject }
…
[2025-10-10T06:32:49.339Z] DEBUG cascade::targets::central_command: [CC]: Event received: SignedZoneRejectedEvent { zone_name: Name(rutaba.ga.), zone_serial: Serial(2025101004) }
Écrivons maintenant un script plus sérieux. Il va obtenir la zone
à valider via un transfert de zones. Notez que le
port utilisé est obtenu via une variable
d'environnement ; Cascade sert la zone sur des ports différents
selon qu'elle a été validée ou pas (cf. les paramètres
servers dans config.toml). Donc, le « vrai » serveur,
présenté plus loin, n'utilisera pas le même port. Voici le script de
validation utilisable en production :
% cat /etc/cascade/reviews/validate-signed.bash
#!/bin/bash
# Yes, bash is required, to parse CASCADE_SERVER
set -e
logger -p daemon.notice -t cascade "Validating ${CASCADE_ZONE} of serial ${CASCADE_SERIAL} from ${CASCADE_SERVER}"
# Parsing magic by mozzieongit
SERVER=${CASCADE_SERVER%:*}
SERVER_IP="${SERVER//[\[\]]/}" # remove brackets from IPv6
SERVER_PORT=${CASCADE_SERVER##*:} # Using double '##' in case its an IPv6
tmp_zone=$(mktemp /tmp/.cascade_zone.XXXXXXXXXX)
# Clean when leaving
trap "rm -f ${tmp_zone}; exit 1" 1 2 3 15
trap "rm -f ${tmp_zone}" EXIT
# Too bad dig logs some errors on standard output :-(
dig @${SERVER_IP} -p ${SERVER_PORT} ${CASCADE_ZONE} AXFR > ${tmp_zone}
# validns does not handle Ed25519
# validns -z ${CASCADE_ZONE} -p all ${tmp_zone}
# or:
dnssec-verify -q -o ${CASCADE_ZONE} ${tmp_zone}
# Or:
# ldns-verify ${tmp_zone}
# Le code de retour du programme de vérification va être celui du script.
Et la zone est acceptée :
[2025-10-10T07:01:01.462Z] DEBUG cascade::units::zone_server: [RS2] Received command: SeekApprovalForSignedZone { zone_name: Name(rutaba.ga.), zone_serial: Serial(2025101001) }
[2025-10-10T07:01:01.462Z] INFO cascade::units::zone_server: [RS2]: Seeking approval for signed zone 'rutaba.ga' at serial 2025101001.
[2025-10-10T07:01:01.463Z] INFO cascade::units::zone_server: [RS2]: Executed hook '/etc/cascade/reviews/validns.sh' for signed zone 'rutaba.ga' at serial 2025101001
[2025-10-10T07:01:01.485Z] INFO domain::net::server::middleware::cookies: preprocess; request_ip=127.0.0.1
[2025-10-10T07:01:01.485Z] INFO domain::net::server::middleware::xfr::service: AXFR for rutaba.ga from 127.0.0.1:35159
[2025-10-10T07:01:01.498Z] DEBUG cascade::units::zone_server: [RS2]: Hook '/etc/cascade/reviews/validns.sh' exited with status exit status: 0
[2025-10-10T07:01:01.498Z] DEBUG cascade::targets::central_command: [CC]: Event received: ReviewZone { name: Name(rutaba.ga.), stage: Signed, serial: Serial(2025101001), decision: Approve }
[2025-10-10T07:01:01.498Z] INFO cascade::targets::central_command: [CC]: Passing back zone review
Bon, maintenant, configurons un « vrai » serveur de noms pour
charger la zone et la servir à tous les résolveurs de
l'Internet. J'utilise nsd. Dans le
nsd.conf, je mets :
zone:
name: "internautique.fr"
zonefile: "/var/lib/nsd/%s.zone"
request-xfr: AXFR 127.0.0.1@8053 NOKEY
allow-notify: 127.0.0.1 NOKEY
# Mettre ici les autres serveurs faisant autorité, qu'il faut notifier et autoriser.
On va autoriser 127.0.0.1 (la machine locale,
où tourne Cascade) à nous notifier les changements de la zone
signée, et on demandera les transferts de zone à cette même machine,
sur le port où écoute Cascade (ici 8053). nsd va alors charger la
zone, éventuellement notifier les autres serveurs faisant autorité
et tout roule.
Si vous connaissez DNSSEC, vous avez vu qu'il manque une étape : ajouter l'enregistrement DS (Delegation Signer) dans la zone parente. Cascade n'affiche pas ce DS tout de suite, il sait que les résolveurs ont une mémoire et il attend d'avoir vu la zone signée publiée et que la durée configurée soit écoulée. En version alpha, cette étape ne marche pas toujours donc on va le faire à la main (ne le faites pas en production !) :
% dig @127.0.0.1 -p 8053 AXFR internautique.fr > /tmp/internautique.fr
% dnssec-dsfromkey -f /tmp/internautique.fr internautique.fr
internautique.fr. IN DS 30790 15 2 BB8987B69144BFB6863D9F86983EE1FB465629CDB3EE8DC43EE6B7C11056598F
Et c'est ce DS qu'on va transmettre à la zone parente (peut-être via son BE). Les programmes de test disponibles en ligne montrent une configuration (presque) correcte : Zonemaster et DNSviz.
Avant-dernière chose à faire : faire tourner le démon en permanence sur la machine. Cascade vient avec un fichier de configuration pour systemd mais une bogue subtile l'empêche actuellement de fonctionner. J'ai donc utilisé un plus classique script rc.local :
% cat /etc/boot.d/cascade
#!/bin/sh
# You may change this one:
DEBUG=1
set -e
# Don't touch
export PATH=${PATH}:/home/cascade/.cargo/bin
if [ ${DEBUG} -ge 1 ]; then
DAEMON_OPTS="--log-level debug"
export RUST_BACKTRACE=full
else
DAEMON_OPTS="--log-level info"
fi
# Does not work with su (for unknown reasons): "Permission denied"
sudo -E -u cascade /home/cascade/.cargo/bin/cascaded --daemonize ${DAEMON_OPTS} --state=/var/lib/cascade/state.db --config=/etc/cascade/config.toml
# su -p cascade -c '/home/cascade/.cargo/bin/cascaded --state=/var/lib/cascade/state.db --config=/etc/cascade/config.toml'
Enfin, quand on sera en production, il faudra évidemment superviser Cascade. J'utilise un petit script qui suit l'API Nagios :
% cat /usr/local/lib/nagios/plugins/check_cascade
#!/bin/sh
# Simple Nagios/Icinga plugin for Cascade.
USAGE="Usage: $0 -e string_to_expect"
TEMP=$(getopt -o "e:h" -- "$@")
if [ $? != 0 ]; then
echo $USAGE
exit 1
fi
eval set -- "$TEMP"
while true ; do
case "$1" in
-e) EXPECTED=$2; shift 2;;
-h) echo $USAGE; exit 3;;
--) shift ; break ;;
*) echo "Internal error!"; exit 3 ;;
esac
done
logger "Test Cascade, expect \"${EXPECTED}\""
E_OUT=$(/home/cascade/.cargo/bin/cascade zone list 2>/dev/null)
if [ $? -ne 0 ];
then
echo "Cascade did not respond"
exit 2
fi
if [ ! -z "${EXPECTED}" ]; then
L_CNT=$(echo ${E_OUT} | grep "${EXPECTED}" 2>/dev/null | wc -l)
if [ ${L_CNT} -eq 0 ]; then
echo "Cascade zone list did not return a match on ${EXPECTED}"
exit 2
fi
fi
echo "Cascade OK ${S_OUT}"
exit 0
Et qui se configure comme cela dans Icinga :
object CheckCommand "cascade" {
command = [ PluginContribDir + "/check_cascade" ]
arguments = {
"-e" = "$cascade_expect$",
}
}
object Host "ayla-cascade" {
import "generic-host"
check_command = "remote_always_true"
vars.cascade = true
vars.cascade_expect="internautique\\.fr"
}
Et en conclusion, un peu d'opéra, avec l'air « Cascader la vertu » de La Belle Hélène, interprété par Gaëlle Arquez.
Date de publication du RFC : Octobre 2025
Auteur(s) du RFC : B. Viguier (ABN AMRO Bank), D. Wong
(zkSecurity), G. Van Assche
(STMicroelectronics), Q. Dang
(NIST), J. Daemen (Radboud
University)
Pour information
Réalisé dans le cadre du groupe de recherche IRTF cfrg
Première rédaction de cet article le 13 octobre 2025
Vous trouvez qu'il n'y a pas assez d'algorithmes en cryptographie ? En voici quatre de plus, ici pour condenser cryptographiquement des données, plus rapidement que les algorithmes existants.
Il s'agit de quatre fonctions, TurboSHAKE128 et TurboSHAKE256 (variantes de TurboSHAKE) ainsi que de KT128 et KT256 (variantes de Kangaroo Twelve qui est décrit dans cet article). Toutes sont des XOF (eXtendable Output Function), des fonctions dont le résultat peut être infiniment long (comme ce que produit un générateur pseudo-aléatoire), mais qu'on tronque à la valeur souhaitée (cf. section 2.1). Kangaroo Twelve (section 3) est bâti sur TurboSHAKE et apporte le parallélisme : il permet de répartir le travail sur plusieurs processeurs alors que SHA-3 et TurboSHAKE sont strictement séquentiels. TurboSHAKE (section 2), lui, est bâti sur Keccak (qui est aussi la base de SHA-3, la cryptographie, c'est compliqué). D'ailleurs, le logiciel qui met en œuvre les algorithmes de ce RFC est développé par l'équipe de Keccak.
Ne comptez pas sur moi pour des explications cryptographiques détaillées, je n'y connais rien, lisez les sections 2 et 3 du RFC. Mais Sakura, le système utilisé par Kangaroo Twelve pour répartir le travail semble intéressant (cf. l'article original).
En section 4 du RFC, vous trouverez comment utiliser Kangaroo Twelve pour faire un MAC (on condense le message puis on condense la concaténation de la clé et du résultat de la première condensation).
Et si vous voulez mettre en œuvre Kangaroo Twelve vous-même, la
section 5 vous offre de nombreux vecteurs de
test. Mais, avant, regardez l'annexe A qui vous offre du
pseudo-code et rappelle qu'il y a une
implémentation en C, XKCP (par l'équipe Keccak)
et une en Python,
décrite dans l'article « KangarooTwelve: fast
hashing based on Keccak-p » (les vecteurs de test
du RFC viennent de là, normal, ce sont les mêmes auteurs). Enfin,
une
troisième est en Zig.
Commençons en Python (j'ai fait une copie du code de l'article dans
et le
programme de test est kangarootwelve.py). Si on prend ce vecteur
de test dans le RFC :
test-kangarootwelve.py
KT128(M=ptn(17 bytes), C=`00`^0, 32):
`6B F7 5F A2 23 91 98 DB 47 72 E3 64 78 F8 E1 9B
0F 37 12 05 F6 A9 A9 3A 27 3F 51 DF 37 12 28 88`
Le programme affiche :
% python test-kangarootwelve.py 6b f7 5f a2 23 91 98 db 47 72 e3 64 78 f8 e1 9b 0f 37 12 05 f6 a9 a9 3a 27 3f 51 df 37 12 28 88
Ce qui est bien la bonne valeur.
Et avec XKCP, compilons la bibliothèque et le programme de test :
test-kangarootwelve.c
git submodule update --init make generic64/libXKCP.so gcc -I ./bin/generic64/libXKCP.so.headers -L ./bin/generic64 test-kangarootwelve.c -lXKCP # ou # gcc -I ./bin/generic64/libXKCP.so.headers test-kangarootwelve.c ./bin/generic64/libXKCP.so LD_LIBRARY_PATH=./bin/generic64 ./a.out
Et le programme affiche bien le vecteur de test indiqué dans le RFC.
Kangaroo Twelve a été ajouté au registre IANA, et les quatre algorithmes sont dans le registre de COSE (RFC 8152).
Date de publication du RFC : Septembre 2025
Auteur(s) du RFC : J. Stenstam (The Swedish Internet
Foundation), P. Thomassen (deSEC, Secure Systems
Engineering), J. Levine (Standcore
LLC)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF dnsop
Première rédaction de cet article le 5 octobre 2025
Vous le savez, le protocole DNS permet plusieurs sortes de messages, identifiés par un code d'opération. Si le classique QUERY, pour demander à résoudre un nom en informations pratiques, est le plus connu, il y a aussi le UPDATE (modifier les données), le DSO (DNS Stateful Operations) et quelques autres. Et puis il y a le NOTIFY, qui sert à indiquer à un serveur DNS qu'il y a quelque chose de nouveau et d'intéressant. NOTIFY est surtout connu pour son utilisation afin de… notifier un serveur secondaire que le primaire a une nouvelle version de la zone (c'est dans le RFC 1996). Ce nouveau RFC généralise le concept et permet d'utiliser NOTIFY pour d'autres choses comme un changement dans la liste des serveurs faisant autorité ou un changement de clé DNSSEC.
Si vous avez oublié à quoi servait initialement NOTIFY, relisez le RFC 1996. Il permettait de donner une indication (uniquement une indication, le NOTIFY ne fait pas autorité) comme quoi il faudrait envisager un nouveau transfert de zone (RFC 5936) pour mettre à jour un serveur. Cela permettait des mises à jour plus rapides qu'avec le système traditionnel où chaque serveur esclave devait de temps en temps demander à son maître s'il y avait du nouveau. Mais il y a d'autres cas où un serveur DNS voudrait dire à un autre qu'il y a quelque chose à regarder, par exemple si une nouvelle clé doit être utilisée. D'où l'extension d'utilisation que permet notre RFC 9859. Elle ne change pas le protocole, elle se contente d'utiliser plus largement une fonction existante.
Bon, mais c'est bien joli de dire qu'on peut notifier pour bien
des choses mais on notifie qui ? Dans le cas traditionnel d'une
nouvelle version de la zone, le primaire savait qu'il devait
notifier ses secondaires, qu'il connait (après tout, il doit leur
autoriser le transfert de zone et, dans le pire des cas, il peut
toujours regarder l'ensemble d'enregistrements NS de sa zone). Mais
si on généralise le NOTIFY, on peut ne pas savoir qui notifier (les
autres mécanismes de notification, comme une API ou comme la mise à
jour dynamique du RFC 2136, ont d'ailleurs le
même problème). La section 3 du RFC couvre ce problème. La méthode
recommandée est de publier un enregistrement de type DSYNC, normalisé
dans notre RFC, section 2. Il se place sous le nom
_dsync de la zone :
sousdomaine._dsync IN DSYNC CDS NOTIFY 5359 cds-scanner.example.net.
Notez qu'un joker est possible, par exemple :
*._dsync IN DSYNC CDS NOTIFY 5359 cds-scanner.example.net. *._dsync IN DSYNC CSYNC NOTIFY 5359 cds-scanner.example.net.
Ce nom _dsync a été ajouté dans le registre
des noms précédés d'un trait bas (RFC 8552).
Voici un autre exemple
d'enregistrement DSYNC :
_dsync.example.net. IN DSYNC CDS NOTIFY 5359 cds-scanner.example.net.
Que dit-il ? Qu'example.net, a priori un
hébergeur DNS, voudrait que ses clients, lorsqu'ils ont un nouvel
enregistrement CDS (indiquant l'activation d'une nouvelle clé
DNSSEC, cf. RFC 8078),
notifient cds-scanner.example.net sur le
port 5359. Ce nouveau type DSYNC a été ajouté
au registre des types, avec la valeur 66.
La section 4.1 détaille comment on trouve le serveur à qui
envoyer la notification en utilisant
_dsync. Imaginons qu'on gère
extra.sub.exodus-privacy.eu.org et qu'on
veuille notifier la zone parente. On insère le composant
_dsync après le premier composant (cela donne
extra._dsync.sub.exodus-privacy.eu.org) et on
fait une requête pour ce nom et le type DSYNC. Si ça marche, c'est
bon, sinon, on utilise le SOA dans la réponse pour trouver la zone
parente, et on met le _dsync devant. Si ça ne
donne toujours rien, on retire les composants avant la zone parente
et on recommence. Donc, on interrogera successivement (jusqu'au
premier succès),
extra._dsync.sub.exodus-privacy.eu.org,
extra.sub._dsync.exodus-privacy.eu.org et
_dsync.exodus-privacy.eu.org.
Et le NOTIFY dans l'enregistrement DSYNC d'exemple plus haut ? C'est le plan (scheme, à ne pas confondre avec le code d'opération - opcode - NOTIFY) car DSYNC, dans le futur, pourra servir à autre chose que les notifications. Pour l'instant, dans le nouveau registre des plans, il n'y a que NOTIFY mais dans le futur, d'autres pourront être ajoutés (suivant la politique « Examen par un expert » du RFC 8126).
Comment utiliser ce mécanisme de notification généralisé pour
traiter les enregistrements CDS et CDNSKEY des RFC 7344, RFC 8078 et RFC 9615 ? Ces enregistrements servent à signaler à la zone
parente la disponibilité de nouvelles clés DNSSEC. La section 4 de
notre RFC détaille comment le mécanisme de notification permet
d'indiquer qu'un CDS ou un CDNSKEY a changé. Cela évite au
gestionnaire de la zone parente de balayer toute la zone ce qui,
pour un TLD
de plusieurs millions de noms comme
.fr serait long et
pénible. La solution de ce RFC 9859 est donc de
chercher les DSYNC puis d'envoyer les NOTIFY, au moins deux, un pour
le type CDS et un pour le type CDNSKEY (on ne sait pas forcément à
l'avance lesquels utilisent la zone parente). La zone parente doit
effectuer les mêmes vérifications que lorsqu'elle a détecté un
nouveau CDS (ou CDNSKEY) : RFC 9615 si on
active une zone signée pour la première fois et RFC 7344 et RFC 8078 autrement.
Les messages utilisant le code d'opération NOTIFY sont censés produire une réponse (RFC 1996, section 4.7). Si aucune réponse n'arrive avant l'expiration du délai de garde, on réessaie. Si on a trop réessayé, on peut signaler le problème avec la technique du RFC 9567.
Il est important de se souvenir qu'une notification n'est pas sérieusement authentifiée, et que le récepteur doit donc, s'il choisit d'agir sur une notification, être prudent. Dans le RFC 1996, rien de grave ne pouvait arriver, le récepteur du NOTIFY demandait juste le SOA de la zone puis, si nécessaire, un transfert de zone. Mais avec les CDS et CDNSKEY, des attaques plus sérieuses sont possibles et le destinataire de la notification doit donc effectuer davantage de vérifications (par exemple, si la zone est déjà signée, faire une requête pour le CDS ou le CDNSKEY et vérifier que la réponse est valide et sécurisée, si elle ne l'est pas, faire tous les contrôles du RFC 9615). La notification elle-même n'est pas un problème de sécurité (elle dit juste « tu devrais regarder cela »), c'est l'action qui en résulte qui doit être bien réfléchie. Voilà pourquoi les notifications, même généralisées, ne sont pas plus sécurisées que cela. (Voir aussi la section 5 du RFC, qui insiste sur ce point ; la notification peut accélérer les choses mais ne doit pas à elle-même changer quelque chose.) Il est quand même préférable de limiter le nombre de notifications qu'on traite, au cas où un client malveillant vous bombarde de notifications.
Ces notifications généralisées pourront aussi s'utiliser pour les CSYNC du RFC 7477.
Qui met en œuvre ce RFC à l'heure actuelle ? Des opérateurs comme
deSEC ont annoncé que c'était
en cours, côté client. Côté serveur, les registres de
.ch et
.se ont annoncé que
c'était en cours, ou bien prévu.
Voici un exemple d'un client en Python (utilisant dnspython) qui notifie pour un nouveau CDS :
import dns.message
import dns.query
import dns.opcode
notify = dns.message.make_query("bortzmeyer.fr", "CDS")
notify.set_opcode(dns.opcode.NOTIFY)
response = dns.query.udp(notify, "192.0.2.211")
print(response)
Et en C ? Vous pouvez
utiliser le programme d'exemple , qui utilise l'excellente
bibliothèque ldns. Compiler
et exécuter :
generalized-notify.c
% gcc -o generalized-notify -Wall generalized-notify.c -lldns % ./generalized-notify bortzmeyer.fr 192.0.2.211 Reply code: NOERROR
Je note à ce sujet que certains serveurs faisant autorité, lorsqu'ils ne font pas autorité pour le domaine signalé, répondent REFUSED, ce qui est logique, mais on voit aussi des FORMERR ou NXDIOMAIN (!), sans doute parce que le type CDS ne leur plait pas.
L'annexe A du RFC prend de la hauteur et décrit plus en détail le
problème du balayage DNS. Comment savoir qu'il y a du nouveau, sans
tout examiner (dans le cas du SOA, au rythme indiqué par le champ
Refresh ; mais il n'y a pas de telle solution pour les CDS et
CDNSKEY). Le DNS traditionnel ne marchait que sur un modèle
pull (et c'est pour cela qu'il est faux de parler
de propagation DNS) mais les
NOTIFY du RFC 1996 introduisaient un peu de
push. Heureusement car balayer
.com à la recherche de
nouveaux enregistrements serait lent (et attirerait probablement
l'attention des IDS). Pour les CDS et CDNSKEY, cela serait
d'autant plus agaçant qu'ils seront a priori peu nombreux et que la
plupart des requêtes ne donneront donc rien.
Date de publication du RFC : Septembre 2025
Auteur(s) du RFC : S. Hollenbeck (Verisign Labs), W. Carroll (Verisign), G. Akiwate (Stanford University)
Réalisé dans le cadre du groupe de travail IETF regext
Première rédaction de cet article le 5 octobre 2025
Dans un registre de noms de domaine, il existe une classe d'objets pour les domaines et parfois une pour les serveurs de noms (hosts). C'est en se basant sur les objets de ces classes que des informations sont ensuite publiées dans le DNS. Que se passe-t-il si on retire un objet de ces classes, alors que d'autres objets en dépendaient ? Va-t-on scier une branche sur laquelle quelqu'un est assis ? Ce RFC fait le point sur les solutions existantes, notant que certaines sont moins bonnes que d'autres, notamment pour la sécurité.
Prenons un exemple simple : le domaine
monbeaudomaine.example est enregistré auprès du
registre
du TLD .example. Ses
serveurs de noms sont ns1.domaine-d-un-copain.example et
ns1.hébergeur.example et on va supposer que le
registre de .example traite les serveurs de
noms comme une classe distincte dans sa base de données. Maintenant,
supposons que le domaine
domaine-d-un-copain.example soit supprimé parce
que son titulaire n'en voit plus l'utilité. Que va devenir le
serveur de noms
ns1.domaine-d-un-copain.example ? Il existe de
nombreuses façons de traiter ce problème, et c'est le rôle de ce RFC
de les analyser toutes.
Ainsi, la section 3.2.2 du RFC 5731 dit que
ce n'est pas bien de détruire un domaine si des objets de type
serveur de noms sont sous ce domaine. Dans l'exemple précédent, le
registre de .example aurait refusé la
suppression de
domaine-d-un-copain.example. Mais cela laisse
le problème entier : si le titulaire ne veut plus payer et donc veut
supprimer le domaine, que faire ?
Un cas similaire se produit si on veut supprimer un serveur de
noms. Si le client EPP demande au serveur EPP
du registre de .example la suppression de
l'objet ns1.domaine-d-un-copain.example, que
doit faire le registre, sachant que ce serveur de noms est utilisé
par monbeaudomaine.example ? La section 3.2.2
du RFC 5732 dit que le registre devrait
refuser la suppression. C'est d'autant plus gênant que, dans le
modèle RRR (Registrant-Registrar-Registry), domaine et serveur(s) peuvent
être gérés par des BE différents, n'ayant
pas le droit de toucher aux objets des autres BE.
Vous pouvez trouver des bonnes explications et des exemples réels dans les supports d'une présentation qui avait été faite à l'IETF.
Quelles sont donc les recommandations concrètes de ce RFC ? La section 6 les résume. Au choix :
sacrificial.invalid,
utilisant un TLD existant,
est recommandé.Le protocole EPP permet de changer le nom
d'un serveur de noms (section 3.2.5 du RFC 5732). Une pratique déjà observée est donc de renommer les
serveurs de noms. Dans l'exemple ci-dessus, recevant la demande de
suppression de domaine-d-un-copain.example, le
registre (ou le BE s'il le peut) renommerait
ns1.domaine-d-un-copain.example en, mettons,
ns1.domaine-d-un-copain.renamed.invalid. Ici,
.invalid, TLD réservé
par le RFC 6761, ne poserait pas vraiment de
problème mais renommer dans un domaine ouvert à l'enregistrement
pourrait créer un risque de sécurité, si le domaine de destination
n'existe pas, un méchant pouvant alors l'enregistrer et être ainsi en
mesure de répondre aux requêtes DNS.
La section 3 du RFC explique brièvement pourquoi les RFC 5731 et RFC 5732 déconseillent fortement de permettre la suppression d'un domaine tant que des serveurs de noms dans ce domaine existent. Il y a deux risques de sécurité si on détruisait le domaine en laissant les serveurs de noms tels quels dans la base de données du registre : un de déni de service (le nom ne se résout plus et le serveur va donc être inutile) et un de détournement (si un méchant peut ré-enregistrer le nom de domaine supprimé). Il y a aussi le problème de la colle orpheline, décrit dans le rapport du SSAC, « Comment on Orphan Glue Records in the Draft Applicant Guidebook ».
Si la clé qui identifie un serveur de noms est un numéro quelconque et pas son nom, on peut renommer le serveur sans changer les délégations, ce qui est particulièrement utile si le client EPP n'a pas le droit de changer des délégations qui appartiennent à un autre client du registre. Le nouveau nom ne va en général pas être associé à un serveur opérationnel : on sacrifie un serveur pour pouvoir supprimer le domaine parent. Mais cela entraine quelques risques (section 4 du RFC et l'article d' Akiwate, G., Savage, S., Voelker, G., et K. Claffy, « Risky BIZness: Risks Derived from Registrar Name Management »). Si on renomme vers un nom actuellement inexistant, le domaine peut être détourné si un malveillant enregistre ensuite ce domaine.
Compte tenu de tout cela, la section 5 du RFC étudie les différentes pratiques possibles, leurs avantages et leurs inconvénients. Pour les illustrer, je vais utiliser une base de données simple, décrite en SQL (les essais ont été faits avec PostgreSQL). Voici par exemple une création d'une telle base :
CREATE TABLE Domains (name TEXT UNIQUE);
-- Ici, la table Nameservers n'offre aucune valeur ajoutée par rapport
-- au fait de tout mettre dans Domains. Mais ce ne sera pas le cas par
-- la suite.
CREATE TABLE Nameservers (name TEXT UNIQUE);
CREATE TABLE Delegation (domain TEXT REFERENCES Domains(name),
server TEXT REFERENCES Nameservers(name));
INSERT INTO Domains VALUES ('monbeaudomaine.example');
INSERT INTO Domains VALUES ('domaine-d-un-copain.example');
INSERT INTO Nameservers VALUES ('ns1.domaine-d-un-copain.example');
INSERT INTO Nameservers VALUES ('ns1.hébergeur.example');
INSERT INTO Delegation VALUES ('monbeaudomaine.example', 'ns1.domaine-d-un-copain.example');
INSERT INTO Delegation VALUES ('monbeaudomaine.example', 'ns1.hébergeur.example');
Dans ce premier cas très simple, la suppression du domaine
domaine-d-un-copain.example est triviale :
registry=> DELETE FROM Domains WHERE name='domaine-d-un-copain.example'; DELETE 1
Mais elle laisse la possibilité de colle
orpheline et surtout d'un détournement de monbeaudomaine.example si
quelqu'un ré-enregistre
domaine-d-un-copain.example. Ce premier essai
n'est pas conforme aux exigences des RFC 5731
et RFC 5732. On va essayer de
faire mieux.
Si on interdit (à juste titre) la suppression d'un domaine lorsque des serveurs de noms sont nommés dans ce domaine, on peut arriver à supprimer un domaine en supprimant d'abord les serveurs de noms qui sont nommés dans ce domaine (section 5.2) :
CREATE TABLE Domains (name TEXT UNIQUE);
-- Ajout d'une dépendance au domaine parent, pour éviter les suppressions.
CREATE TABLE Nameservers (name TEXT UNIQUE, parent TEXT REFERENCES Domains(name));
CREATE TABLE Delegation (domain TEXT REFERENCES Domains(name),
server TEXT REFERENCES Nameservers(name));
INSERT INTO Domains VALUES ('monbeaudomaine.example');
INSERT INTO Domains VALUES ('domaine-d-un-copain.example');
INSERT INTO Domains VALUES ('hébergeur.example');
-- Pour une vraie base, on écrirait du code SQL qui extrait le parent
-- automatiquement.
INSERT INTO Nameservers VALUES ('ns1.domaine-d-un-copain.example', 'domaine-d-un-copain.example');
INSERT INTO Nameservers VALUES ('ns1.hébergeur.example', 'hébergeur.example');
INSERT INTO Delegation VALUES ('monbeaudomaine.example', 'ns1.domaine-d-un-copain.example');
INSERT INTO Delegation VALUES ('monbeaudomaine.example', 'ns1.hébergeur.example');
registry=> DELETE FROM Domains WHERE name='domaine-d-un-copain.example';
ERROR: update or delete on table "domains" violates foreign key constraint "nameservers_parent_fkey" on table "nameservers"
DETAIL: Key (name)=(domaine-d-un-copain.example) is still referenced from table "nameservers".
-- Ce comportement est ce que recommandent les RFC 5731 et 5732.
-- Cela oblige le client à supprimer les serveurs de noms d'abord, ce qui à
-- son tour nécessite potentiellement de changer les délégations :
registry=> DELETE FROM Delegation WHERE server = 'ns1.domaine-d-un-copain.example';
DELETE 1
registry=> DELETE FROM Nameservers WHERE name='ns1.domaine-d-un-copain.example';
DELETE 1
registry=> DELETE FROM Domains WHERE name='domaine-d-un-copain.example';
DELETE 1
Ici, il y a eu suppression explicite des serveurs de noms par le
client (section 5.2.2.1). Cela peut poser des problèmes de permission, dans le cadre du
système RRR, si tous les objets ne sont pas
chez le même BE. Mais la suppression explicite est
une des trois solutions recommandées, notamment si on ajoute la
possibilité de rétablir l'état précédent (commande EPP
<restore>, RFC 3915).
On peut aussi envisager une suppression implicite (section 5.2.1.1), le registre se
chargeant du nettoyage (c'est le rôle
de la directive SQL ON DELETE
CASCADE) :
CREATE TABLE Domains (name TEXT UNIQUE);
CREATE TABLE Nameservers (name TEXT UNIQUE,
parent TEXT REFERENCES Domains(name) ON DELETE CASCADE);
CREATE TABLE Delegation (domain TEXT REFERENCES Domains(name) ON DELETE CASCADE,
server TEXT REFERENCES Nameservers(name) ON DELETE CASCADE);
INSERT INTO Domains VALUES ('monbeaudomaine.example');
INSERT INTO Domains VALUES ('domaine-d-un-copain.example');
INSERT INTO Domains VALUES ('hébergeur.example');
-- Pour une vraie base, on écrirait du code SQL qui extrait le parent
-- automatiquement.
INSERT INTO Nameservers VALUES ('ns1.domaine-d-un-copain.example', 'domaine-d-un-copain.example');
INSERT INTO Nameservers VALUES ('ns1.hébergeur.example', 'hébergeur.example');
INSERT INTO Delegation VALUES ('monbeaudomaine.example', 'ns1.domaine-d-un-copain.example');
INSERT INTO Delegation VALUES ('monbeaudomaine.example', 'ns1.hébergeur.example');
registry=> SELECT Domains.name,Nameservers.name FROM Domains,Delegation,Nameservers WHERE Delegation.domain=Domains.name AND Delegation.server=Nameservers.name;
name | name
------------------------+---------------------------------
monbeaudomaine.example | ns1.domaine-d-un-copain.example
monbeaudomaine.example | ns1.hébergeur.example
(2 rows)
registry=> DELETE FROM Domains WHERE name='domaine-d-un-copain.example';
DELETE 1
-- Serveur de noms et délégation ont été détruits en cascade. Ce n'est
-- pas déraisonnable mais c'est quand même un peu effrayant.
registry=> SELECT Domains.name,Nameservers.name FROM Domains,Delegation,Nameservers WHERE Delegation.domain=Domains.name AND Delegation.server=Nameservers.name;
name | name
------------------------+-----------------------
monbeaudomaine.example | ns1.hébergeur.example
(1 row)
Cette solution est simple et efficace mais détruire implicitement des objets de la base de données peut inquiéter les responsables de cette base. Et cela peut laisser un domaine avec trop peu de serveurs de noms pour assurer sa continuité de service voire, dans le pire des cas, sans serveurs du tout. Et il serait bon de prévenir le client de cette suppression implicite, par exemple par le mécanisme de poll d'EPP (RFC 8590).
Si on interdit (à juste titre, le RFC le recommande) la suppression d'un
domaine lorsque des serveurs de noms sont nommés dans ce domaine, une
solution possible est de
renommer les serveurs avant de supprimer le domaine (section 5.1). Le nouveau nom
permet d'indiquer clairement la raison du renommage. Mais ce
renommage laisse dans la base des « déchets » qu'il faudra nettoyer
un jour. Cette catégorie
contient de nombreuses variantes. Par exemple, on peut renommer dans
un TLD spécial (RFC 6761), ici, .invalid :
CREATE TABLE Domains (name TEXT UNIQUE);
-- On introduit un identificateur du serveur de noms qui n'est *pas*
-- son nom, pour permettre le renommage.
CREATE TABLE Nameservers (id SERIAL UNIQUE, name TEXT UNIQUE,
parent TEXT REFERENCES Domains(name));
CREATE TABLE Delegation (domain TEXT REFERENCES Domains(name),
server INTEGER REFERENCES Nameservers(id));
INSERT INTO Domains VALUES ('monbeaudomaine.example');
INSERT INTO Domains VALUES ('domaine-d-un-copain.example');
INSERT INTO Domains VALUES ('hébergeur.example');
-- Pour le renommage, un nom qui indique clairement le but :
INSERT INTO Domains VALUES ('renamed.invalid');
-- Pour une vraie base, on écrirait du code SQL qui extrait le parent
-- automatiquement.
INSERT INTO Nameservers (name, parent) VALUES ('ns1.domaine-d-un-copain.example', 'domaine-d-un-copain.example');
INSERT INTO Nameservers (name, parent) VALUES ('ns1.hébergeur.example', 'hébergeur.example');
INSERT INTO Delegation VALUES ('monbeaudomaine.example',
(SELECT id FROM Nameservers WHERE name='ns1.domaine-d-un-copain.example'));
INSERT INTO Delegation VALUES ('monbeaudomaine.example',
(SELECT id FROM Nameservers WHERE name='ns1.hébergeur.example'));
registry=> SELECT Domains.name,Nameservers.name FROM Domains,Delegation,Nameservers WHERE Delegation.domain=Domains.name AND Delegation.server=Nameservers.id;
name | name
------------------------+---------------------------------
monbeaudomaine.example | ns1.domaine-d-un-copain.example
monbeaudomaine.example | ns1.hébergeur.example
(2 rows)
registry=> UPDATE Nameservers SET name='ns1.domaine-d-un-copain.example.renamed.invalid', parent='renamed.invalid' WHERE id=1;
UPDATE 1
registry=> DELETE FROM Domains WHERE name='domaine-d-un-copain.example';
DELETE 1
registry=> SELECT Domains.name,Nameservers.name FROM Domains,Delegation,Nameservers WHERE Delegation.domain=Domains.name AND Delegation.server=Nameservers.id;
name | name
------------------------+-------------------------------------------------
monbeaudomaine.example | ns1.domaine-d-un-copain.example.renamed.invalid
monbeaudomaine.example | ns1.hébergeur.example
(2 rows)
Par contre, il ne faut pas utiliser .alt, qui
est explicitement réservé aux protocoles non-DNS (RFC 9476). Notez que certains serveurs EPP peuvent tester le
nom des serveurs de noms et refuser des TLD « inconnus ».
Dans la nature, on a pu observer d'autres pratiques, comme de
renommer dans un sous-domaine de as112.arpa, nom
garanti ne pas exister (RFC 7535), mais qui
n'est pas censé servir à cela (RFC 6305). On a
vu aussi des renommages vers des résolveurs DNS publics, ce qui est
également une horreur, la délégation doit être faite vers des serveurs faisant
autorité, pas des résolveurs.
Certains clients EPP maintiennent des serveurs de noms actifs qui
servent pour le renommage. Cela fait davantage de travail mais cela
protège contre le détournement. Le DNS va continuer à fonctionner
normalement. On pourrait aussi imaginer des serveurs de noms actifs, répondant
NXDOMAIN (« ce domaine n'existe pas »), qui soient
gérés collectivement (section 5.1.4.4) ; un tel service serait
certainement utile. On pourrait même créer un nom de domaine pour ce
service (sacrificial.arpa ?) mais personne ne
l'a encore fait. Pour l'instant, la solution des serveurs maintenus
par le client EPP (section 5.1.3.4) fait partie des trois solutions
recommandées. Le client prudent doit bien verrouiller le domaine dans
lequel ces serveurs sont nommés (enregistrement multi-années,
verrouillage par le registre, etc).
On peut aussi renommer les serveurs de noms vers un nom
non-existant dans un TLD existant. Ça s'est déjà vu mais il ne faut
surtout pas faire cela : un attaquant pourrait enregistrer le nom et
capter ainsi le trafic (.invalid n'a pas ce
problème). Idem si le nom n'est pas sous votre contrôle. Un exemple
est donné par le domaine lenvol.re : ses
serveurs de noms étaient ns.hostin.io,
ns.hostin.mu et
ns.hostin.re. Lors de la suppression de
hostin.re en octobre 2024, le dernier serveur
de noms a été renommé
host1.renamedbyregistry.com (et, en dépit du
nom, pas par le registre). Ce domaine
renamedbyregistry.com étant enregistré, et par
un autre BE, on voit le risque.
% whois lenvol.re domain: lenvol.re status: ACTIVE … nserver: host1.renamedbyregistry.com nserver: ns.hostin.io nserver: ns.hostin.mu
D'autres noms qui utilisaient ce même serveur ont également le problème :
% dig @d.nic.fr savanna.re. NS … ;; AUTHORITY SECTION: savanna.re. 3600 IN NS host1.renamedbyregistry.com. savanna.re. 3600 IN NS ns.hostin.mu. savanna.re. 3600 IN NS ns.hostin.io. ;; Query time: 8 msec ;; SERVER: 2001:678:c::1#53(d.nic.fr) (UDP) ;; WHEN: Tue Jun 24 14:33:32 CEST 2025
En lecture supplémentaire, notre RFC recommande le rapport « SSAC 125 "Report on Registrar Nameserver Management" », ainsi que l'article « Risky BIZness: Risks Derived from Registrar Name Management ».
Première rédaction de cet article le 4 octobre 2025
Le site Web « MonLycée.net » a été souvent mentionné dans la
presse française il y a deux semaines, suite à l'annonce que la région Île-de-France
allait remplacer certains logiciels Microsoft
par des logiciels français. Mais la communication était mal faite,
seul
marchait, pas http://monlycee.net/ (avec l'accent sur le
E). C'est désormais réparé.http://monlycée.net/
L'essentiel de la communication en français utilisait en effet le
nom de domaine
monlycée.net, ce qui est logique, le
lycée prend en effet un accent. Mais, depuis
très longtemps, le nom de domaine à utiliser était
monlycee.net, le nom correct n'étant pas
configuré.
Cette copie d'écran du site Web montre bien que l'orthographe
correcte est utilisée depuis longtemps : 
Mais, en septembre 2025, au moment où ce site était beaucoup
mentionné dans les médias, le domaine
monlycée.net existait bien mais n'était pas correctement configuré dans le
DNS, aucun des deux
serveurs de noms ne répondait. Voici ce qu'on voyait avec dig le 29 septembre
(si vous utilisez un dig compilé sans IDN, vous devrez utiliser
xn--monlyce-gya.net au lieu de
monlycée.net) :
% dig @l.gtld-servers.net. monlycée.net NS … ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 14466 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 4, ADDITIONAL: 1 … ;; AUTHORITY SECTION: monlycée.net. 172800 IN NS dns1.namebay.com. monlycée.net. 172800 IN NS dns2.namebay.com. ;; Query time: 41 msec ;; SERVER: 2001:500:d937::30#53(l.gtld-servers.net.) (UDP) ;; WHEN: Mon Sep 29 09:14:57 CEST 2025 ;; MSG SIZE rcvd: 244 % dig @dns1.namebay.com. monlycée.net. NS … ;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 32749 ;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 1 … ; COOKIE: e359c164d9f8ced10100000068da31fe1b539adeb5eee54e (good) ; EDE: 18 (Prohibited) ; EDE: 20 (Not Authoritative): (recursion disabled) … ;; Query time: 20 msec ;; SERVER: 2a01:c8:ff00:201::133#53(dns1.namebay.com.) (UDP) ;; WHEN: Mon Sep 29 09:15:09 CEST 2025 ;; MSG SIZE rcvd: 106
Le serveur faisant autorité pour
.net annonce que les
deux serveurs faisant autorité pour
monlycée.net sont
dns1.namebay.com et
dns2.namebay.com mais aucun des deux ne veut
répondre. (L'EDE - Extended DNS Error, cf. RFC 8914 indique que le serveur ne fait pas autorité pour ce
nom.) Le domaine ne marche donc pas du tout.
De telles erreurs de communication et/ou de configuration avec les noms de domaine internationalisés sont hélas fréquentes.
Voulant signaler le problème, j'ai utilisé whois :
% whois monlycée.net … Creation Date: 2016-06-27T11:07:12Z … Tech Name: Communication Direction Tech Organization: CONSEIL REGIONAL ILE DE FRANCE Tech Street: 2, rue Simone Veil Bon de commande 81236 Numero d'engagement Chorus 792650 Tech City: SAINT-OUEN … Tech Email: https://www.validname.com/whoiscontact/?id=4377E185-08DF-4351-91A9-D02B507D3806&c=T
Zut, on n'a pas l'adresse du contact technique, juste un
formulaire Web pénible. Bon, j'ai écrit. Le message de confirmation
disait « E-Mail routed to the contact. Thank you for using
our services. Best regards. A-Bot, in charge of routing anonymous
[sic] e-mails. ». Je n'ai évidemment jamais eu de réponse
mais peut-être que quelqu'un a fait quelque chose puisque, le 1
octobre, un certificat Let's Encrypt a été
obtenu pour ce nom, prouvant que le domaine remarchait. Bienvenue à
.https://monlycée.net
Dans son effort louable de progression vers l'autonomie
numérique, le Conseil régional a aussi
installé un PeerTube, ce qui est une
excellente idée. Mais il annonce « PeerTube de Monlycée.net » alors
que seul fonctionne, pas
https://peertube.monlycee.net/
(nom non existant). On retombe dans le problème de communication
cité plus haut.https://peertube.monlycée.net/
Et quelques petits détails techniques pour finir. D'abord,
X.509 ne permettant pas apparemment
l'Unicode (ou bien c'est
l'AC qui ne le fait pas ?), le certificat
utilise la forme Punycode,
xn--monlyce-gya.net :
% gnutls-cli monlycée.net … Resolving 'xn--monlyce-gya.net:443'... Connecting to '185.65.56.149:443'... - Certificate type: X.509 … - subject `CN=xn--monlyce-gya.net', issuer `CN=E7,O=Let's Encrypt,C=US', serial 0x06f5adb213041273640f32f866068a400090, EC/ECDSA key 256 bits, signed using ECDSA-SHA384, activated `2025-10-01 09:39:17 UTC', expires `2025-12-30 09:39:16 UTC', pin-sha256="iDBvNYX0tCBtaAEMSv5m2AknA+bFcLX3WCWtCA33S0U="
J'ai vu que ce certificat du 1 octobre est le plus ancien, par une recherche sur crt.sh. Ce service, par ailleurs très utile, ne permet pas de recherche sur le nom en Unicode, mais je crois que c'est une limite de X.509.
Enfin, monlycée.net est signé avec
DNSSEC mais n'a pas
(encore ?) publié de lien dans le domaine parent.
Première rédaction de cet article le 4 octobre 2025
Bon, c'est un article d'ultra-niche. Mais, comme j'ai eu du mal à faire fonctionner cette configuration, je me dis que je peux la décrire, et que peut-être quelqu'un·e trouvera cet article un jour via un moteur de recherche, et que cela lui servira. Donc, le problème était : soit un ensemble de machines Debian configurées via Ansible. Un paquetage dont j'avais besoin n'était que dans la version Debian unstable. Comment l'installer via le module Ansible apt ?
D'abord, je détaille le problème. Pour une formation DNSSEC, je gère N machines virtuelles tournant sous Debian. Le paquetage d'OpenDNSSEC, qui était dans les précédentes versions de Debian, a été retiré de l'actuelle version stable, la version 13, alias « trixie ». La configuration que j'utilisais d'habitude avec Ansible et son module apt n'est donc pas utilisable telle quelle pour installer OpenDNSSEC.
Il y a bien sûr plusieurs solutions à ce problème (mais pas celle des backports, OpenDNSSEC n'y est pas) :
dpkg -i. À la réflexion,
c'est sans doute ce que j'aurais dû faire, mais il n'est pas
garanti que cela aurait bien marché.
J'ai finalement choisi une autre voie : utiliser
stable mais permettre l'installation de
paquetages qui existent dans unstable. Bien que
déconseillée
par Debian, cette méthode est assez banale et on trouve en
ligne plein d'instructions sur comment la réaliser. Une
configuration simple est, dans
/etc/apt/sources.list.d/unstable.sources :
Types: deb deb-src URIs: mirror+file:///etc/apt/mirrors/debian.list Suites: sid Components: main Signed-By: /usr/share/keyrings/debian-archive-keyring.gpg
(sid est le petit nom de la version
unstable. Si vous avez vu Toy
Story, vous savez pourquoi.)
Avec cela, vous pouvez maintenant installer des paquetages
d'unstable mais attention, vous ne voulez pas que
ces paquetages remplacent vos paquetages stable
existants ! Il faut donc aussi donner une basse priorité à
unstable, avec un /etc/apt/preferences.d/99unstable :
Package: * Pin: release a=unstable Pin-Priority: 1
Désormais, après le prochain apt update, un apt install quelquechose
installera bien la version stable du paquetage
« quelquechose », qui a une priorité supérieure. Et apt
install opendnssec installera la version
d'unstable puisqu'il n'y en a pas dans stable.
Tout cela est classique et bien connu, et ça marche. Mais depuis Ansible, patatras. Le module apt d'Ansible prétend qu'il n'existe pas d'OpenDNSSEC, alors que ça marche en ligne de commande avec apt. Voici la configuration du module :
tasks:
…
- name: install packages
apt: # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html
name: [truc, machin, opendnssec]
state: present
C'est apparemment parce que Ansible ajoute à apt une option qui lui dit de n'utiliser que stable et je n'ai pas trouvé le moyen de couper cette option.
La solution est donc de faire deux tâches Ansible, une pour
stable et une pour unstable,
en utilisant le paramètre
default_release. Voici ma configuration
complète et qui marche :
tasks:
…
- name: "Copy apt sources"
copy:
src: apt-unstable.sources
dest: /etc/apt/sources.list.d/unstable.sources
- name: "Copy apt preferences"
copy:
src: apt-preferences.txt
dest: /etc/apt/preferences.d/99unstable
- name: "Update Repository cache"
apt: # https://docs.ansible.com/ansible/latest/collections/ansible/builtin/apt_module.html
update_cache: true
upgrade: dist
cache_valid_time: 3600
force_apt_get: true
- name: install packages
apt:
name: [truc, chose, machin]
state: present
- name: install unstable packages
apt:
name: [opendnssec, softhsm2]
default_release: sid
state: latest
Notez le state: latest. Si on laisse à la
valeur par défaut, present, Ansible produit un
message d'erreur incohérent prétendant que sid
n'est pas dans les sources.
Première rédaction de cet article le 9 septembre 2025
Le 9 septembre 2025 est sortie la version 17 d'Unicode. Une description officielle des principaux changements est disponible mais voici ceux qui m'ont intéressé particulièrement. (Il n'y a pas de changement radical.)
Pour explorer plus facilement la grande base Unicode, j'utilise un programme qui la convertit en SQL et permet ensuite de faire des analyses variées. Faisons quelques requêtes SQL :
ucd=> SELECT count(*) AS Total FROM Characters; total -------- 159866
Combien de caractères sont arrivés avec la version 17 ?
ucd=> SELECT version,count(version) FROM Characters GROUP BY version ORDER BY version::float; ... 14.0 | 838 15.0 | 4489 15.1 | 627 16.0 | 5185 17.0 | 4803
4 803 nouveaux caractères, c'est pas mal, mais moins que la précédente version. Quels sont ces nouveaux caractères ?
ucd=> SELECT To_U(codepoint) AS Code_point, name FROM Characters WHERE version='17.0' ORDER BY Codepoint; code_point | name -----------+---------------------------------------------------------------------------- U+FDCC | ARABIC LIGATURE SALLALLAHU ALAYHI WA-ALAA AALIHEE WA-SALLAM … U+10940 | SIDETIC LETTER N01 U+10941 | SIDETIC LETTER N02 U+10942 | SIDETIC LETTER N03 … U+11DB0 | TOLONG SIKI LETTER I U+11DB1 | TOLONG SIKI LETTER E U+11DB2 | TOLONG SIKI LETTER U U+11DB3 | TOLONG SIKI LETTER O … U+16EA0 | BERIA ERFE CAPITAL LETTER ARKAB U+16EA1 | BERIA ERFE CAPITAL LETTER BASIGNA U+16EA2 | BERIA ERFE CAPITAL LETTER DARBAI … U+1CEC8 | EUNOMIA U+1CEC9 | PSYCHE U+1CECA | THETIS U+1CECB | MELPOMENE … U+1F6D8 | LANDSLIDE … U+1FAC8 | HAIRY CREATURE …
Cette version amène en effet quatre nouvelles écritures. Si l'écriture sidétique n'est connue que par une douzaine d'inscriptions, le zaghawa (Beria Erfe), lui, est toujours en usage. On voit aussi arriver le Tolong Siki et le Lai Tay (Tai Yo). Vous noterez aussi les symboles astronomiques pour les plus gros astéroïdes, comme Victoria.
Il y a d'autres nouveautés que l'addition de caractères. Par exemple les propriétés pour la coupure de mots de la cédille, malgré l'ancienneté de ce caractère, ont été changées pour se conformer aux règles du saanich. Ou bien une nouvelle classe pour les tirets a été créée, Unambiguous_Hyphen, pour des caractères comme le ‐ (regardez bien, c'est le tiret Unicode, U+2010, pas le tiret ASCII, U+002D, qui peut être un tiret ou bien le signe moins).
En plus futile, il y a l'habituelle arrivée d'emojis comme le trombone ou le coffre au trésor. Par contre, le trognon de pomme a finalement été retiré et n'apparaitra pas avant Unicode 18.
Si vous avez les bonnes polices de caractères, vous allez pouvoir voir quelques exemples (sinon, le lien mène vers Uniview). Voici par exemple, la troisième lettre de l'alphabet sidétique , le caractère Zaghawa Kobo , l'astéroïde Psyche , le yéti et le glissement de terrain .
Vous voulez davantage de détails ? Vous en trouverez plein dans l'article d'Ysabeau.
Auteur(s) du livre : Fiona M. Alexander, Laura
DeNardis, Nanette
S. Levinson, Francesca Musiani
Éditeur : Palgrave Macmillan Cham
978-3-031-89477-0
Publié en 2025
Première rédaction de cet article le 29 août 2025
Ce livre parle de géopolitique de l'Internet. Banal, vous allez me dire, plein de livres ont déjà été écrits sur ce sujet. Mais celui-ci parle bien de l'Internet, pas des services qui tournent dessus, comme le font la très grande majorité des livres qui prétendent parler de l'Internet, mais ne connaissent que de ce qu'on voit sur l'écran. Ici, au contraire, c'est bien l'infrastructure, le cœur de l'Internet, qui est étudiée par les auteures.
Car l'infrastructure n'est pas neutre et les questions politiques liées à l'Internet ne concernent pas que les trois ou quatre réseaux sociaux centralisés étatsuniens que mentionnent la plupart des auteurs. Ce livre va donner de nombreux exemples, du choix et du déploiement d'IPv6 à la gestion des serveurs racine du DNS, en passant par la censure ou les demandes de blocage des réseaux russes suite à l'invasion de l'Ukraine, ainsi que des sujets très peu traités quand on parle de « gouvernance de l'Internet » comme la distribution d'adresses IP, avec l'exemple des attaques contre Afrinic. Mais pas d'Instagram (cité uniquement à propos d'un problème BGP) ou de Twitch.
De nombreux autres sujets d'infrastructure sont traités, montrant à chaque fois les conséquences politiques de choix que l'on pourrait croire plutôt techniques. Et ce sont aussi bien des sujets déjà largement décrits (la « guerre des protocoles » entre TCP/IP et OSI), que des sujets un peu plus rares comme la normalisation technique ou dont les conséquences politiques sont vraiment très peu discutées, comme la sécurisation de BGP ou les certificats (mais DANE n'est pas mentionné dans cette section, ce qui est dommage).
En prime, le livre est correct techniquement, ce qui n'est pas le cas de tous les textes parlant des aspects sociaux ou politiques de l'Internet, par exemple il ne reprend pas le cliché « les adresses IPv4 sont composées de quatre groupes de chiffres », expliquant même leur format binaire.
Par contre, je regrette que le livre ait un biais très étatsunien. Cela se voit dans des clichés comme parler de « constitutional protections of free expression » (alors que le fameux premier amendement ne s'applique qu'à l'État, et qu'il laisse les entreprises privées s'attaquer à la liberté d'expression tant qu'elles veulent), dans une présentation peu critique de l'ICANN (création du gouvernement étatsunien et qui ne représente pas du tout la mythique « communauté Internet »), ou lorsque des termes juridiques étatsuniens sont utilisés sans être expliqués (sans être lecteur de Grisham, savez-vous ce qu'est un amicus curiae ?). De même, l'Internet Society est présentée en termes vraiment acritiques. Un autre exemple est la place excessive donnée à New IP, projet qui a été beaucoup plus mis en avant par les Étatsuniens, en termes chauvins (« Mon Dieu, les Chinois veulent faire un nouveau protocole »), que par les Chinois eux-mêmes (qui se sont contentés de quelques PowerPoint).
La préface de Fiona Alexander pose un autre problème : elle a été un des acteurs principaux du feuilleton de la « privatisation » de l'ICANN. D'un côté, c'est bien d'avoir comme auteure quelqu'un qui connait le dossier de l'intérieur. De l'autre, elle se donne le beau rôle et fait une présentation très unilatérale (le politicien de droite extrême Ted Cruz étant bien pratique comme repoussoir).
Bref, sur un sujet aussi complexe, il y a plusieurs points de vue, ce qui est logique en politique, donc n'hésitez pas à demander à votre institution de se procurer ce livre (très cher à l'achat, malheureusement ; on peut avoir toutes les conneries qu'on veut en ligne gratuitement mais l'information sérieuse est souvent payante).
(Au passage : j'ai reçu un exemplaire gratuit de l'ouvrage.)
Première rédaction de cet article le 25 août 2025
Dernière mise à jour le 31 août 2025
Du 21 au 23 août 2025, c'était Pas Sage En Steïr. C'était très intéressant, très sympa, et je me suis permis d'y parler d'IA.
Pas Sage En Steïr est l'héritier des réunions Entrée Libre (qui se tenait à Quimper) et Pas Sage En Seine (qui se tenait à Choisy-le-Roi). Pas Sage En Seine a dû être annulé en raison d'une campagne de harcèlement contre les organisateur·rices, d'où Pas Sage En Steïr (car le Steïr coule à Quimper, où se tenait la conférence, au Centre des Abeilles).
Comme dit plus haut, j'ai parlé d'IA, sous le titre un peu sensationnaliste « L'IA n'existe pas ». Vous pouvez récupérer le support (et son source).
Mais il y avait plein d'autres choses intéressantes ! (Le programme est en ligne.) Tout a été enregistré et les vidéos sont disponibles (cf. la mienne). Et il y a aussi une transcription de mon intervention.
Par exemple, « Le testament numérique : que faire de mes données après moi », par Natouille, un « sujet d'après-repas » (non, en fait, c'est un sujet triste mais qu'il faut regarder en face). Il y a aussi bien le problème de données qui disparaissent involontairement que celui de données qui restent en ligne involontairement. Les gros machins (GAFA) ont tous des fonctions (« contact légataire » ou équivalent). Il faut juste leur faire confiance. Il existe des boites privées qui permettent de gérer ses données post-mortem. Faut juste leur faire confiance (honnêteté et pérennité). Dans les conférences, il y avait aussi la place des femmes dans l'informatique, par Isabella Vanni. Ou bien le concept de « Jardinage numérique », par Mindiell, qui présente son jardin, contre les « 5M » : Mondialisation / Monopolisation / Minitelisation / Monétisation / Merdification. Ou le classique mais toujours indispensable « Quelles alternatives pour se libérer des GAFAM », par Bookynette, qui note que la partie n'est pas égale « Les GAFAM peuvent avoir vingt rendez-vous par jour avec des ministres, et l'April zéro ».
Mais il y avait aussi des ateliers passionnants. Les Petits Débrouillards pratiquaient la science participative en guidant les participant·es dans le projet [Plantes] Sauvages de ma rue (on trouve désormais de nombreuses photos des plantes de Quimper). Une sortie bien agréable sous le beau soleil breton, et une découverte. Autre excellent atelier, celui sur l'installation et l'utilisation de Thunderbird (pour échapper aux gros webmails dominants). À noter qu'un des problèmes rencontrés était dû à Orange : IMAP n'est pas activé par défaut sur les comptes de courrier Orange, il faut le faire via son espace client sur le Web mais c'est refusé si on n'est pas sur sa connexion à la maison (cf. la documentation). Mais, bon, Proton Mail fait payer pour la même opération.
Et, comme d'habitude au Centre des Abeilles, mille mercis pour Brigitte et son rôle crucial.
Autres articles sur cet événement :
First publication of this article on 25 August 2025
July 2025? Among many other events in the world, this was the IETF meeting, the 123rd, in Madrid. Before the meeting, there was the now traditional hackathon. I worked on the future protocol RPP (RESTful Provisioning Protocol), which may be the successor of EPP.
EPP (RFC 5730) is the current
standard for provisioning, specially domain names. When you register
a domain name, the registrar you use
probably talks to the registry with EPP. EPP
has some limits and the IETF is working (in the rpp working group)
on another protocol, RPP
(for RESTful Provisioning Protocol). Such a REST API is already
done, for
instance at Afnic (the domain name registry of
.fr).
At the time of the IETF hackathon, RPP was far from done, there was not even a consensus on some basic aspects. So, unlike many hackathon projects, there was no attempt to test interoperability of different implementations of a protocol. The idea was instead to explore various things related to a REST provisioning protocol.
OK, so my specific work (the code is on GitHub):
Let's do a demo first:
% createdb registry % psql -f ./create.sql registry % ./test-server.py
Then let's get information about a domain with curl (one of the goals of a RESTful protocol like RPP is to be able to use regular HTTP clients):
% curl --header @headers.txt http://localhost:8080/domains/nic.example
{"result": "Domain nic.example exists", "holder": 1, "tech_contact": 1, "admin_contact": 1, "registrar": 1, "created": "2025-08-25T14:04:18.271586", "status_code": 200, "status_message": "OK"}
And create a domain (note we have to authentify this time):
% curl --header @headers.txt --request PUT --user 2:qwerty --data '{"holder": 2, "tech": 2, "admin": 2}' http://localhost:8080/domains/durand.example
{"result": "durand.example created", "status_code": 201, "status_message": "Created"}
OK, you get the idea, the README.md file will give you some examples.
The registry is a PostgreSQL database. The server is written in Python, uses the WSGI framework, depends on several modules from the Python standard library and on psycopg2 and jsonschema. We use JSONschema for the validation of the incoming JSON (remember that RPP is far from being standardized and no schema language have been chosen yet). The test client, as you saw, is regular curl.
I added a (very small) test suite written in Zig:
% cd tests % zig build test
(I took the opportunity of the hackathon to post a sample HTTP client written in Zig to the Ziggit forum.)
The work done at the RPP table of the hackathon is presented here. All the live presentations are on YouTube and ours is at 1:19:08. Now, the rpp working group continues its work… (See the development on Github.)
Auteur(s) du livre : William Blanc, Justine
Breton, Jonathan Fruoco
Éditeur : Libertalia
9-782377-293513
Publié en 2024
Première rédaction de cet article le 10 juillet 2025
Dans ce livre, William Blanc, avec Justine Breton et Jonathan Fruoco, poursuit son exploration des mythes nés de héros de fiction. Car, si Robin des Bois n'a probablement jamais existé, le mythe, lui, est bien réel, et a été repris par d'innombrables créateurs, bien différents.
Ce livre commence par creuser les racines du mythe. Au Moyen Âge, il était suffisamment connu pour que les premières mentions écrites du héros, au détour d'autres ouvrages, ne soient pas expliquées : tous les lecteurs connaissaient la référence, grâce à la tradition orale. Puis Robin des Bois a eu droit à ses propres livres et pièces de théâtre, après le Moyen Âge, et a commencé une carrière médiatique que le cinéma, au XXe siècle, a puissamment relancé. Je rappelle que ce livre ne porte pas sur le personnage historique (qui est sans doute imaginaire) mais sur sa reprise par les créateurs. C'est l'histoire du mythe, pas l'histoire de Robin.
Comme avec tous les mythes, Robin des Bois a été utilisé par de nombreux courants artistiques et politiques. Si le roi Arthur, sujet d'un précédent livre du même auteur, est plutôt « de droite » (après tout, c'est un roi), Robin des Bois, proscrit caché dans la forêt, qui affronte les autorités, serait plutôt « de gauche ». Mais attention, c'est bien plus compliqué que cela et les auteurices du livre montrent que Robin des Bois a été présenté de nombreuses façons différentes, et parfois en allié du roi, ou, au XXe siècle, en libertarien partisan des esclavagistes. De même, Robin a souvent changé de classe sociale, parfois yeoman (paysan propriétaire), parfois noble. Bien des bandits et des rebelles ont été qualifiés de « Robin des Bois moderne » (y compris les plus contestables).
Le livre analyse aussi un certain nombre de points spécifiques : le rôle des femmes dans la légende, Robin des Bois en France, les questions que se posent les médias pour enfants (présenter un voleur comme personnage positif…), l'importance du déguisement dans la légende (et, de manière plus générale, de l'inversion), bref, un livre d'une grande richesse où vous découvrirez plein de choses.
Première rédaction de cet article le 8 juillet 2025
Dernière mise à jour le 15 juillet 2025
J'ai reçu trois messages de menaces d'une société nommée Cloud Innovation, car j'avais relayé un article qui dénonçait, à juste titre, leur rôle dans les attaques contre Afrinic. Je crois utile de rendre publiques ces menaces.
Un peu de contexte : Afrinic est le RIR pour l'Afrique. Le plus petit et le moins riche des RIR, le moins doté en ressources humaines et en appuis politiques, il est aussi le seul à avoir encore beaucoup d'adresses IPv4, ce qui aiguise des convoitises. Une entreprise chinoise, Cloud Innovation (elle utilise d'autres noms comme Larus ou Number Resource Society) a ainsi obtenu des adresses IP via une société-écran aux Seychelles, adresses ensuite utilisées pour des activités sans lien avec l'Afrique. Afrinic a tenté de récupérer au moins une partie de ces adresses, ce à quoi Cloud Innovation a réagi par une série de procès. Afrinic, victime de décisions mal réfléchies de la justice du pays de son siège social, a été privé des moyens de se défendre et est entré, depuis plusieurs années, dans une crise dont on ne voit pas le bout. (Le registre n'a, actuellement, ni directeur, ni conseil d'administration, les dernières élections, en juin 2025, ont été annulées.)
Tout ceci est bien décrit dans un excellent article d'Emmanuel Vitus (en anglais, mais il y a aussi la version en français). Et c'est la cause immédiate des messages que j'ai reçus de Cloud Innovation. J'ai tweeté sur cet article. Je reçois donc, sur une adresse personnelle et une professionnelle, un message en anglais signé de Cloud Innovation, m'enjoignant de « IMMEDIATELY remove and cease and desist from further sharing, disseminating, or promoting the Article through your X (formerly known as Twitter) profile, which disparage Cloud Innovation Limited and its officers, and further invite and open the floodgate for defamatory commentary regarding the same ». (Vous avez le mesage complet ici ; notez qu'il mentionne mon employeur - qu'on trouve facilement en ligne - mais que celui-ci n'y est pou rien.) Et le message me donne 24 heures et se termine par d'autres menaces, citant par exemple un procès en cours au Ghana (pourquoi le Ghana ???). Quelques jours après, un autre message de menace me relance puis un troisième.
J'ai hésité à partager cette information car je craignais d'aider Cloud Innovation dans sa politique d'intimidation (certaines personnes ont cédé mais en relançant publiquement la diffusion de l'information). Mais il me semble qu'il serait pire de ne pas faire connaitre les méthodes de cette entreprise. Comme vous le voyez, je n'ai pas supprimé mon tweet, que j'assume totalement. (Dans des cas comme ceci, il est important d'archiver les articles cités, car il y a toujours un risque d'une action d'effacement. Voici les archives, faites par Seb Sauvage.) D'autres personnes ont fait le même choix de publication, comme Joe Hall (voir aussi son tweet).
(Notez que j'ai cité Wikipédia, comme je le fais souvent sur ce blog, mais ne vous fiez pas à la page Wikipédia sur Afrinic : sans doute modifié par Cloud Innovation, elle - aussi bien en version francophone qu'anglophone - reprend des accusations mensongères contre Afrinic. Le compte à l'origine de ces modifications a d'ailleurs été bloqué.)
Sur ce même sujet, vous pouvez lire :
Date de publication du RFC : Juin 2025
Auteur(s) du RFC : Q. Misell (AS207960)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF acme
Première rédaction de cet article le 27 juin 2025
Le protocole ACME,
normalisé dans le RFC 8555, permet
d'automatiser le processus de création et de renouvellement de
certificats utilisables, par exemple, pour
TLS. L'extension normalisée dans ce nouveau
RFC permet
d'obtenir et de renouveler des certificats pour un service utilisant
le .onion de
Tor. Si vous voulez passer à la télévision en
disant « j'ai obtenu un certificat pour le Dark Web », ce RFC est la
bonne lecture.
Ces services en
.onion sont décrits dans
la spécification de
Tor. Le nom .onion a été enregistré
comme « nom spécial » (RFC 6761) par le RFC 7686. Ces noms en .onion
ne sont pas résolvables via le DNS et on ne peut donc pas utiliser les défis
ACME (RFC 8555, section 8) classiques avec une
AC habituelle.
La norme ACME, le
RFC 8555, définit dans sa section 9.7.7 des
types d'identificateurs. Pour les
.onion, ce sera le type
dns, même si Tor
n'utilise pas le DNS. J'aurais préféré que ce type se nomme
domainname, ça aurait été plus cohérent (voir
l'annexe A du RFC, qui discute ce choix, qui prend place dans un
cadre très embrouillé, avec beaucoup de gens qui confondent noms de
domaine et DNS). L'identificateur est le nom dans
.onion, éventuellement avec des
sous-domaines. Par exemple, le JSON envoyé par ACME, pour la version en .onion de ce blog,
serait :
{
"type": "dns",
"value": "sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion"
}
Reste à permettre au serveur ACME (l'AC)
de le valider. Le forum AC/Navigateur permet les
.onion (annexe B.2 de ses « Baseline
Requirements for the Issuance and Management of Publicly-Trusted
Certificates »). ACME utilise le concept de
défi pour ces validations. Le client ACME
demande un certificat pour un certain identificateur, le serveur lui
renvoie un défi, une tâche à accomplir, du genre « prouve-moi que tu
peux mettre ces données que je t'envoie dans un endroit accessible
en HTTP
via le nom pour lequel tu veux un certificat ». Il existe plusieurs
défis utilisables avec les .onion :
http-01 (section 8.3 du RFC 8555), notre RFC dit qu'il faut suivre les redirections
HTTP, même si elles mènent à un serveur HTTP qui n'est pas en
.onion ; c'est ce défi que j'ai utilisé pour
mon blog.tls-alpn-01 (RFC 8737).onion-csr-01, qui, contrairement aux
deux précédents, est spécifique aux .onion et
normalisé dans ce RFC 9799. C'est aussi le seul des
trois défis utilisables qui permettre des jokers dans
l'identificateur
(*.bbcweb3hytmzhn5d532owbu6oqadra5z3ar726vq5kgwwn6aucdccrad.onion). Je
le décris en détail plus loin. Cette méthode de validation figure
désormais dans le registre
des types de défis.dns-01 (section 8.4 du RFC 8555), par contre, ne peut pas être
utilisé pour les .onion, ceux-ci ne se
servant pas du DNS.
Le défi onion-csr-01 consiste à demander au
client de générer une CSR (RFC 2986) signée avec la clé privée du
nom en .onion (je rappelle qu'un nom en
.onion est une clé publique, plus exactement
est dérivé d'une clé publique). Pour cela, le serveur ACME envoie un
numnique et sa clé publique
Ed25519, qu'il utilise pour Tor. Le client
doit répondre avec le CSR, signé avec sa clé privée, CSR contenant
parmi ses attributs le numnique choisi par le serveur, et un autre
choisi par le client. Le serveur vérifie alors ce CSR (le nom en
.onion, et bien sûr la signature).
Notez qu'on peut imaginer une AC qui ne soit elle-même accessible
que via un nom en .onion, ce qui serait
cohérent (section 5 du RFC). Par contre, en sortie, une AC doit
utiliser Tor pour se connecter aux .onion (je
veux dire l'utiliser directement, pas en passant par une passerelle,
cf. section 8.8) mais une AC ne doit
pas utiliser Tor pour se connecter à des
domaines non-onion (section 8.4) car il y a des
nœuds
de sortie méchants.
Et les enregistrements de type CAA, normalisés dans le RFC 8659, et qui augmentent la sécurité du système
en indiquant à quelles AC le client ACME a recours ? On ne peut pas
utiliser le CAA traditionnel puisqu'il dépend du DNS. La solution,
décrite dans la section 6 du RFC, est de mettre cette information
dans le descripteur de service Tor (celui que le serveur en
.onion envoie aux serveurs d'annuaire Tor).
Passons à la pratique. Il existe une AC
expérimentale (et qui ne prétend pas offrir les
garanties de sécurité d'une « vraie »), par l'auteure du RFC,
documentée sur le site Web du
projet. Je m'en suis servi pour obtenir un certificat pour
mon blog. Vous pouvez donc
désormais vous connecter en HTTPS, à
(votre navigateur va sans doute râler car il ne connait pas cette
AC). Attention, l'AC a enregistré ce certificat via
Certificate Transparency (RFC 9162) et est donc publiquement visible. Ne
demandez pas de certificat à une AC qui utilise Certificate
Transparency si ça vous dérange (section 8.9 du RFC).
https://sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion/
Quelle est la marche à suivre pour obtenir un tel certificat ? D'abord, configurer Tor pour accepter le port de HTTPS :
HiddenServiceDir /var/lib/tor/blogv2/ HiddenServicePort 80 127.0.0.1:80 HiddenServicePort 443 127.0.0.1:443
Ensuite, configurer son serveur HTTP, Apache dans mon cas :
# Pour le client ACME (qui écoutera sur le port 8080) : ProxyPass /.well-known/acme-challenge http://localhost:8080/.well-known/acme-challenge ProxyPassReverse /.well-known/acme-challenge http://localhost:8080/.well-known/acme-challenge ProxyPreserveHost on # Ne pas oublier d'activer mod_proxy *et* mod_proxy_http. # HTTPS : <VirtualHost _default_:443> ServerName sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion # Liens symboliques vers les certificats obtenus par ACME, dans mon # cas /etc/letsencrypt/live/sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion/… SSLCertificateFile /etc/apache2/server.pem SSLCertificateKeyFile /etc/apache2/server.key
On lance ensuite le client ACME. J'ai utilisé certbot :
% sudo certbot certonly -v --server https://acme.api.acmeforonions.org/directory \
--standalone --http-01-port 8080 --http-01-address 127.0.0.1 \
-d sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion
…
Performing the following challenges:
http-01 challenge for sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion
Waiting for verification...
Cleaning up challenges
Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion/fullchain.pem
Key is saved at: /etc/letsencrypt/live/sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion/privkey.pem
This certificate expires on 2025-05-28.
…
(Et pensez à configurer le renouvellement automatique du certificat, normalement, certbot l'a fait tout seul, mais vérifiez.)
Un tcpdump montre le trafic échangé entre Apache (qui a reçu le défi de l'AC, via Tor) et certbot (qui avait envoyé la demande et stocké le défi du serveur ACME), sur ma machine :
127.0.0.1.60978 > 127.0.0.1.8080: Flags [P.] … HTTP, length: 376
GET /.well-known/acme-challenge/G4JwaWsRDG42H_FAESGCQrY7ZYk3D3mY9Ob_j458z6M HTTP/1.1
Host: sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion
X-Forwarded-For: 127.0.0.1
X-Forwarded-Host: sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion
X-Forwarded-Server: sjnrk23rmcl4ie5atmz664v7o7k5nkk4jh7mm6lor2n4hxz2tos3eyid.onion
127.0.0.1.8080 > 127.0.0.1.60978: Flags [P.] … HTTP, length: 92
HTTP/1.0 200 OK
Server: BaseHTTP/0.6 Python/3.11.2
Date: Thu, 27 Feb 2025 17:04:53 GMT
Si vous voulez ajouter cette AC (mais rappelez-vous qu'elle est
expérimentale et sans garanties de sécurité) au Tor
Browser, vous devrez sans doute, dans
about:config, configurer,
avant d'importer l'AC :
security.nocertdb false browser.privatebrowsing.autostart false security.ssl.enable_ocsp_stapling false
Mais je répète mon avertissement : cela peut affecter votre vie privée. Ne le faites pas si vous ne comprenez pas en détail les conséquences.
J'ai utilisé le défi http-01, avec un
certbot ordinaire. Le défi onion-01 nécessite
un certbot modifié (du code est disponible).
Petite anecdote : l'auteure du RFC travaille pour l'AS 207960, dont l'objet RIPE annonce :
aut-num: AS207960 as-name: AS-TRANSRIGHTS descr: Trans Rights! Hell Yeah!
Et le site Web montre d'ailleurs le drapeau correspondant. Il est assez rare de voir des prises de position politiques dans la base des RIR.
Date de publication du RFC : Juin 2025
Auteur(s) du RFC : A. Gable (Internet Security Research
Group)
Chemin des normes
Réalisé dans le cadre du groupe de travail IETF acme
Première rédaction de cet article le 18 juin 2025
Si vous utilisez Let's Encrypt (et tout le monde l'utilise, de nos jours), vous avez sans doute reçu les messages « Let's Encrypt Expiration Emails Update » qui vous préviennent que cette AC n'enverra plus de rappels que vos certificats vont bientôt expirer. C'est parce qu'un meilleur système est maintenant disponible, ARI (ACME Renewal Information). ARI permet à une AC utilisant le protocole ACME d'indiquer à ses clients des suggestions sur le renouvellement des certificats. Il est décrit dans ce RFC.
ACME est normalisé dans le RFC 8555 et est surtout connu via le succès de Let's Encrypt. Les certificats sont de courte durée (aujourd'hui trois mois mais ça va diminuer) et il faut donc les renouveler souvent. On peut le faire automatiquement via cron, ou bien analyser le certificat et renouveler quand sa date d'expiration approche. L'un des problèmes est que cela peut mener à ce que plusieurs demandes de renouvellement arrivent en même temps sur l'AC. Le pic d'activité qui en résulterait pourrait charger l'AC inutilement. L'idée est donc que ce soit le serveur ACME, l'AC, qui planifie les renouvellements, et pas le client ACME. Cela permettrait aussi des changements de planning, comme une réduction des durées de validité, ou bien une révocation.
Donc, ARI ajoute un nouvel URL à la description d'une AC,
renewalInfo. Voici celui de Let's Encrypt (qui
met
en œuvre ARI depuis deux ans) :
% curl https://acme-v02.api.letsencrypt.org/directory
{
"keyChange": "https://acme-v02.api.letsencrypt.org/acme/key-change",
"meta": {
"caaIdentities": [
"letsencrypt.org"
],
"profiles": {
…
},
"termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.5-February-24-2025.pdf",
…
},
"newAccount": "https://acme-v02.api.letsencrypt.org/acme/new-acct",
"newNonce": "https://acme-v02.api.letsencrypt.org/acme/new-nonce",
"newOrder": "https://acme-v02.api.letsencrypt.org/acme/new-order",
"renewalInfo": "https://acme-v02.api.letsencrypt.org/draft-ietf-acme-ari-03/renewalInfo",
"revokeCert": "https://acme-v02.api.letsencrypt.org/acme/revoke-cert"
Ce nouveau membre a été ajouté au registre IANA.
Et comment on obtient quelque chose à cet URL
https://acme-v02.api.letsencrypt.org/draft-ietf-acme-ari-03/renewalInfo ?
On doit passer un identificateur du certificat. Il est formé par la
concaténation de l'identifiant de l'AC et du numéro de série du
certificat (pour garantir qu'il soit unique). Je passe sur les
détails de construction (lisez la section 4 du RFC pour cela) mais
ce petit script Python vous fait le
calcul. Utilisons-le sur l'actuel certificat de ce blog :
ari-make-path.py
% openssl x509 -text -in cert.pem
[Notez les valeurs]
% python ari-make-path.py 93:27:46:98:03:A9:51:68:8E:98:D6:C4:42:48:DB:23:BF:58:94:D2 00:06:7d:03:91:c6:49:b8:83:ba:e5:c6:da:8a:dc:be:4d:33:78
kydGmAOpUWiOmNbEQkjbI79YlNI.AAZ9A5HGSbiDuuXG2orcvk0zeA
% curl -i https://acme-v02.api.letsencrypt.org/draft-ietf-acme-ari-03/renewalInfo/kydGmAOpUWiOmNbEQkjbI79YlNI.AAZ9A5HGSbiDuuXG2orcvk0zeA
…
retry-after: 21600
…
{
"suggestedWindow": {
"start": "2025-06-23T03:50:00Z",
"end": "2025-06-24T23:00:50Z"
}
}
Voilà, on a fait un URL en concaténant la valeur de
renewalInfo avec celle obtenue à partir du
certificat et on sait désormais quand est-ce que Let's Encrypt
suggère de renouveler ce certificat. Le format de sortie est clair
mais vous avez les détails dans la section 4.2. Les dates sont
évidemment au format du RFC 3339. Au passage,
la date d'expiration est le 24 juillet 2025 donc Let's Encrypt
n'attend pas le dernier moment. (Comme le dit un commentaire dans le
code source du serveur, « calculate a point 2/3rds of the
way through the validity period (or halfway through, for short-lived
certs) ».)
Le RFC précise qu'un membre explanationURL
peut être ajouté mais Let's Encrypt ne le fait pas. Les membres
possibles figurent dans un
nouveau registre IANA, auquel on pourra ajouter de nouveaux
membres, en fournissant une spécification (« Spécification
nécessaire » du RFC 8126).
Et on utilise cet intervalle entre deux dates comment ? Le RFC
recommande de choisir une date au hasard dans l'intervalle. Le
client ACME ne doit pas dormir jusqu'à la date sélectionnée, il doit
réessayer de temps en temps car l'AC a pu changer la date (c'est
tout le principe d'ARI). Mais attention à respecter le
Retry-After: (six heures, ici), cf. RFC 9110, section 10.2.3.
Notre RFC ajoute également un membre à la description d'une
commande de certificat : replaces indique quel
certificat on est censé remplacer. (En utilisant le même
identificateur de certificat qu'indiqué plus haut.) Il a été ajouté
au
registre IANA.
ARI est mis en œuvre dans le serveur ACME de Let's Encrypt, Boulder. Regardez
core/objects.go et notamment la fonction
RenewalInfoSimple. Côté client, Lego, acmez et le CertMagic
de Caddy ont ARI mais
certbot ou dehydrated
ne gèrent pas ARI. Si vous voulez vous y mettre, Let's Encrypt a
écrit un guide
d'intégration d'ARI.
Articles des différentes années : 2025 2024 2023 2022 2021 2020 2019 Précédentes années
Syndication : Flux Atom avec seulement les résumés et Flux Atom avec tout le contenu.