Il y a un résolveur DNS qui tourne sur votre machine en ce moment même. Vous ne l’avez pas installé, vous ne l’avez probablement jamais configuré, et il répond à chaque recherche de nom que fait la machine. Sur Fedora, Ubuntu et la plupart des Linux de bureau, c’est systemd-resolved, et il est assis là tranquillement depuis le jour de l’installation du système.

Il sait faire trois choses qui valent le coup. Il met en cache, donc la même recherche ne traverse pas le réseau deux fois. Il valide DNSSEC, donc une réponse forgée est rejetée au lieu d’être crue. Et il parle DNS over TLS, donc le réseau local ne peut pas lire chaque nom que vous demandez.

Par défaut, il fait exactement une de ces trois choses. Les deux autres sont coupées, et sur certaines distributions elles sont coupées à la compilation, ce qui veut dire que le réglage que vous iriez changer n’est même pas le réglage qui a décidé. Un validateur qui ne valide jamais n’est ni utile ni décoratif.

Voici ce que cette chose fait réellement, mesuré sur une machine Fedora 44 en fonctionnement avec systemd 259, et ce qui change quand vous activez les deux autres. Y compris ce que votre distribution a décidé pour vous à la compilation, et quelle part de ça vous pouvez tout simplement outrepasser.

Qu’est-ce qui répond réellement à vos recherches

Commençons par le tableau honnête, parce que « ça utilise /etc/resolv.conf » n’est plus vrai sur ces systèmes depuis des années.

systemd-resolved s’expose de quatre façons différentes, et celle qu’un programme emprunte décide de ce qu’il reçoit en retour.1 Le chemin glibc, c’est nss-resolve, câblé par /etc/nsswitch.conf. Les chemins natifs sont D-Bus et Varlink, qui portent le verdict DNSSEC et la portée d’interface que getaddrinfo n’a aucun moyen d’exprimer. Puis le stub, un vrai serveur DNS sur le bouclage pour tout ce qui parle du DNS brut et ne sait rien de tout ce qui précède.

Quatre portes, un seul démon.

Sur cette machine, ce câblage ressemble à ça :

$ ls -l /etc/resolv.conf
lrwxrwxrwx. 1 root root 39 May 30 13:56 /etc/resolv.conf -> ../run/systemd/resolve/stub-resolv.conf

$ cat /etc/resolv.conf
nameserver 127.0.0.53
options edns0 trust-ad
search damiendye.uk

$ grep ^hosts: /etc/nsswitch.conf
hosts:      files myhostname mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns

resolve dans cette ligne, c’est nss-resolve. dns juste après, c’est le traditionnel nss-dns, posé là en secours qui ne se déclenche que si resolved ne tourne pas du tout.

Quatre portes d'entrée, un seul démon, et une décision de routage avant que quoi que ce soit ne quitte la machineCOMMENT UN PROGRAMME DEMANDECE QUE FAIT LE DÉMONOÙ ÇA VAnss-resolveglibc, sans verdictD-Bus, Varlinknatif, verdict complet127.0.0.53le stub complet127.0.0.54le stub proxysystemd-resolved1. hosts et synthétiques2. cache3. validateur DNSSEC4. routage5. transportle stub proxy saute 2 et 3LLMNR sur 5355DNS amont
Quatre portes d’entrée, un seul démon, et une décision de routage avant que quoi que ce soit ne quitte la machine

Il y a deux stubs, pas un

Tout le monde connaît 127.0.0.53. Moins de gens savent qu’il y en a un second.

$ ss -lntup | grep ':53 '
udp   UNCONN 0  0     127.0.0.54:53   0.0.0.0:*
udp   UNCONN 0  0  127.0.0.53%lo:53   0.0.0.0:*
tcp   LISTEN 0  4096  127.0.0.54:53   0.0.0.0:*
tcp   LISTEN 0  4096 127.0.0.53%lo:53 0.0.0.0:*

Ce ne sont pas deux adresses pour la même chose. La seconde en fait délibérément moins :2

127.0.0.53127.0.0.54
RôleLe résolveur local completMode proxy seulement
CacheOuiNon
Validation DNSSECOui, quand elle est activéeJamais
LLMNR et Multicast DNSOuiNon
Noms synthétiques (localhost, _gateway)OuiNon
Bascule vers DNS over TLSOuiOui
Nom synthétique associé_localdnsstub_localdnsproxy
Ce que vous récupérezCe que resolved a décidéCe que l’amont a dit

La documentation est brutale sur la seconde colonne. Il va « pass most DNS messages relatively unmodified to the current upstream DNS servers and back, but not try to process the messages locally, and hence does not validate DNSSEC, or offer up LLMNR/MulticastDNS » — faire passer la plupart des messages DNS à peu près intacts vers les serveurs amont et retour, sans essayer de les traiter localement, et donc sans valider DNSSEC ni offrir LLMNR/MulticastDNS.2

Ce qui fait de 127.0.0.54 la bonne cible pour un programme qui veut faire sa propre validation, ou pour celui qui a besoin de la réponse brute de l’amont plutôt que de l’interprétation qu’en fait resolved.

Les deux adresses ont des noms synthétiques, _localdnsstub et _localdnsproxy, qui résolvent sans la moindre configuration.

Quatre modes pour /etc/resolv.conf, pas trois

Le mode est détecté automatiquement d’après ce qu’est le fichier, et il y en a quatre.1

/etc/resolv.conf estCe que ça veut direLes clients qui contournent NSS
un lien vers run/systemd/resolve/stub-resolv.confLe mode recommandé. Liste 127.0.0.53 et les domaines de recherche vivantsPassent par resolved
un lien vers /usr/lib/systemd/resolv.confStatique, liste 127.0.0.53, ne porte aucun domaine de recherchePassent par resolved
un lien vers run/systemd/resolve/resolv.confListe les vrais serveurs amont, tenu à jourContournent resolved entièrement
un vrai fichier géré par autre choseresolved le lit en consommateur, pas en fournisseurContournent resolved entièrement

C’est la troisième ligne qui piège les gens. Elle a l’air de l’option propre, elle est tenue à jour, et elle veut discrètement dire que chaque programme qui lit resolv.conf directement parle au résolveur de votre FAI sans cache, sans validation et sans chiffrement, quoi que vous ayez configuré dans resolved.conf.

L’option trust-ad dans le fichier ci-dessus compte aussi. Sans elle, la glibc retire le bit AD de la réponse avant que votre programme ne la voie, au motif raisonnable que la prétention d’un résolveur quelconque à avoir validé quelque chose ne vaut rien. Avec 127.0.0.53 comme serveur de noms, la prétention vient de votre propre machine, donc trust-ad est correct ici et resolved vous l’écrit tout seul.

Sur les théories du complot autour de systemd

Avant d’entrer dans le détail, parce que c’est le sujet où ça finit toujours par arriver.

Si votre objection à ce qui suit est une théorie sur Lennart Poettering personnellement, ou sur les motivations de Red Hat, ou sur systemd comme complot pour vous prendre quelque chose, vous pouvez passer votre chemin, parce que je n’ai pas envie d’entendre votre fiction.

Tout ce qui est dans ce billet vient d’une machine vivante : les sources, les options de compilation, le binaire livré, les pages de manuel et le comportement mesuré. Chaque affirmation a une commande à côté d’elle que vous pouvez lancer vous-même pour vérifier. Les options de compilation sont publiques. Les sources sont publiques. Le seul défaut que je crois véritablement mauvais a été choisi par des gens qui ont écrit leur raisonnement là où n’importe qui peut le lire et le contester, ce qui est exactement ce que je fais plus bas.

C’est nettement plus de transparence que vous n’en obtenez de la plupart des logiciels que vous faites tourner sans un mot de protestation.

Apportez des preuves ou arrêtez.

Le cache est la partie qui marche toute seule

C’est la seule fonctionnalité activée par défaut partout, et c’est celle qui rapporte sans la moindre configuration.

La mesure, sur cette machine, sur 32 domaines. Vider le cache, interroger le lot, les interroger de nouveau, et lire le temps de requête rapporté par dig plutôt que de chronométrer le processus :

$ resolvectl flush-caches
$ for n in $NAMES; do dig +tries=1 @127.0.0.53 "$n" A | grep 'Query time'; done
médianemoyennep90maxtotal pour 32 noms
cache froid103,5 ms106,7 ms184 ms238 ms3 414 ms
cache chaud0,0 ms0,5 ms1 ms3 ms16 ms

3 414 millisecondes contre 16. C’est tout l’argument en faveur d’un cache local, et c’est pourquoi c’est le défaut.

Il vaut la peine d’être honnête sur ce qu’est ce chiffre, cela dit. C’est l’économie sur une série de trente-deux noms jamais recherchés auparavant, comparée à la même série répétée, et aucune charge réelle ne ressemble ni à l’une ni à l’autre. Le chiffre utile, c’est la queue plutôt que la médiane : la pire recherche de cet ensemble a coûté 238 ms à froid et 3 ms à chaud. Une page qui tire huit noms d’hôte se moque de votre médiane, elle attend votre plus lent.

Les molettes

[Resolve]
Cache=yes                 # yes | no-negative | no
CacheFromLocalhost=no     # default: do not cache answers from 127.0.0.1
StaleRetentionSec=0       # serve expired records when upstream is down
RéglageDéfautCe qu’il fait
Cache=yesactivéMet en cache les réponses positives et négatives
Cache=no-negativeRéponses positives seulement, pour quand vous en avez assez d’attendre la fin d’un TTL négatif
CacheFromLocalhost=noactivéNe met rien du tout en cache quand l’amont est sur 127.0.0.1
StaleRetentionSec=0coupéSert les enregistrements expirés quand l’amont cesse de répondre
DNSCacheSize=40964096Enregistrements gardés par portée. systemd 261 et au-delà seulement

C’est la troisième ligne qui mord. Si votre amont est un dnsmasq ou un unbound sur le bouclage, resolved ne mettra pas du tout ses réponses en cache, au motif que la chose à laquelle il parle est déjà un cache. Pointez resolved vers un résolveur filtrant sur un autre hôte et vous avez deux couches de cache. Pointez-le vers un résolveur sur le bouclage et vous n’en avez qu’une.

StaleRetentionSec= est le réglage intéressant, et il est coupé par défaut. Mettez-le, et quand l’amont cesse de répondre, resolved continue de servir les enregistrements au-delà de leur TTL plutôt que d’échouer.2 Il essaie toujours l’amont d’abord. Ça ne s’applique pas à NXDOMAIN, parce qu’un nom qui n’existe pas est une réponse parfaitement valide et qu’il n’y a rien de périmé là-dedans. Pour un portable qui entre et sort de couverture, ou une machine qui doit continuer de fonctionner pendant une panne DNS, ça vaut le coup :

[Resolve]
StaleRetentionSec=1d

systemd 261 a ajouté par-dessus le dimensionnement du cache par protocole, avec DNSCacheSize=, MulticastDNSCacheSize= et LLMNRCacheSize=, chacun réglé par défaut à 4096 enregistrements et plafonné à 2^24.3 Pas sur cette machine, qui tourne en 259, et sur aucune version stable actuelle de distribution. Bon à savoir que ça arrive, parce que jusqu’ici la taille du cache n’était pas réglable du tout.

Le démon vide aussi tout sous pression mémoire, ce qui est sensé et occasionnellement surprenant quand vous cherchez pourquoi un taux de succès du cache a l’air médiocre.

Le split DNS est la raison de le garder

Si vous ne retenez qu’une chose, retenez cette section, parce que c’est la fonctionnalité qui fait véritablement ce que l’ancien résolveur stub ne pouvait pas faire.

Le resolv.conf traditionnel a une liste de serveurs de noms pour toute la machine. Une seule liste. Montez un VPN et il faut bien que quelque chose l’écrase, ce qui veut dire soit vos noms internes marchent et le reste du DNS passe par le résolveur de l’entreprise, soit l’inverse. Il n’y a pas de troisième option. Le fichier ne sait pas l’exprimer.

resolved route par requête et par interface.1 Chaque lien a ses propres serveurs et ses propres domaines, et une recherche part vers le lien dont le domaine correspond le mieux au nom, au nombre de labels.

ConfigurationÉcriteEffet
Domaine de recherchedamiendye.ukSuffixe pour les noms à un seul label, et route les requêtes correspondantes vers ce lien
Domaine de routage seul~internal.exampleRoute les requêtes correspondantes vers ce lien, jamais utilisé comme suffixe
Route fourre-tout~.Envoie vers ce lien tout ce qui ne correspond à rien d’autre
Route par défautDNSDefaultRoute=yesPrend les requêtes sans correspondance, sans revendiquer ~.

Alors un portable avec un VPN d’entreprise monté obtient ceci :

resolvectl domain tun0 '~corp.example' '~10.in-addr.arpa'
resolvectl dns    tun0 10.0.0.53

Les noms sous corp.example et les recherches inverses pour 10.0.0.0/8 descendent dans le tunnel. Tout le reste continue de sortir par le lien local comme avant. Le resolv.conf de personne n’a été réécrit, et quand le tunnel tombe les routes s’en vont avec lui.

Une requête, deux liens, et une décision de routage prise au nombre de labelsLA REQUÊTELA DÉCISIONLE LIENdb01.corp.example14.0.10.in-addr.arpablogs.damiendye.ukLe plus de labels gagneChaque lien a ses propresserveurs et domaines.tun0~corp.examplewlp4s0DefaultRouteUn tilde route seulement. Sans tilde, ça suffixe aussi les noms à un seul label.
Une requête, deux liens, et une décision de routage prise au nombre de labels

Une règle mérite d’être énoncée clairement, parce que c’est celle vers laquelle les gens se précipitent en se trompant. ~. sur un lien veut dire préférer ce lien pour tout. Ça empêche aussi implicitement tout autre lien d’être une route par défaut. Vous voulez qu’un lien prenne les restes sans revendiquer tout l’espace de noms ? Mettez DNSDefaultRoute=yes et laissez ~. tranquille.

Vérifiez ce qu’il a décidé plutôt que de le supposer :

$ resolvectl status
Link 3 (wlp4s0)
    Current Scopes: DNS LLMNR/IPv4 LLMNR/IPv6
         Protocols: +DefaultRoute LLMNR=resolve -mDNS -DNSOverTLS
                    DNSSEC=no/unsupported
Current DNS Server: 192.0.2.53
       DNS Servers: 192.0.2.53 2001:db8:1::53
        DNS Domain: damiendye.uk
     Default Route: yes

Chaque adresse de ce billet vient des plages de documentation, 192.0.2.0/24 et 2001:db8::/32, alors ne les collez pas dans une configuration en espérant une réponse.4 5 Les chiffres et le comportement sont réels, pris sur une machine vivante. Les adresses sont des doublures, parce qu’une adresse IPv6 globale construite de la façon habituelle porte la MAC de l’interface dans sa moitié basse, et en publier une distribue un morceau d’inventaire matériel avec.

Ce -DNSOverTLS et ce DNSSEC=no font les deux sections suivantes.

Il sait valider DNSSEC

Le démon est décrit en amont comme « a caching and validating DNS/DNSSEC stub resolver » — un résolveur stub DNS/DNSSEC avec cache et validation.1 La moitié validation est réelle, ce n’est pas une surcouche par-dessus autre chose, et elle marche. Elle n’est simplement pas activée.

L’activer par lien ne demande pas de sudo, parce que resolvectl passe par polkit et qu’une session locale active y a droit :

$ resolvectl dnssec wlp4s0 yes
$ resolvectl status wlp4s0 | grep DNSSEC
                    DNSSEC=yes/supported

Ce suffixe /supported est resolved qui rapporte ce qu’il a trouvé en sondant l’amont, séparément de ce que vous avez demandé. yes/supported veut dire que vous avez demandé la validation et que le serveur sait la porter. no/unsupported sur une installation par défaut veut dire que personne n’a demandé, donc personne n’a sondé.

Une fois que c’est activé, chaque recherche revient avec un verdict, et il y en a trois.

Sécurisé, non sécurisé et falsifié sont trois réponses différentesLE PARENT PUBLIE-T-IL UN DS, ET LES SIGNATURES VÉRIFIENT-ELLES ?SÉCURISÉdamiendye.ukSignée, et lachaîne tient.Données renvoyées.NON SÉCURISÉsystemd.ioPas de DS. Non signée,donc rien à vérifier.Données renvoyées.FALSIFIÉdnssec-failed.orgPrétend être signée.La preuve échoue.Recherche refusée.Deux des trois renvoient des données. Activer la validation n'empêche pas les sites non signés de marcher.
Sécurisé, non sécurisé et falsifié sont trois réponses différentes, et une seule est un échec

Voici les trois sur cette machine, contre de vrais noms :

$ resolvectl query damiendye.uk
-- Data is authenticated: yes; Data was acquired via local or encrypted transport: no

$ resolvectl query systemd.io
-- Data is authenticated: no; Data was acquired via local or encrypted transport: no

$ resolvectl query dnssec-failed.org
dnssec-failed.org: resolve call failed: DNSSEC validation failed: missing-key
  (DNSKEY Missing: no SEP matching the DS found for dnssec-failed.org.)

damiendye.uk est signé, donc la chaîne depuis la racine valide et les données sont authentifiées. systemd.io n’est pas signé, donc il n’y a rien à vérifier et resolved le dit honnêtement plutôt que de faire semblant. C’est le domaine du projet systemd lui-même, un point sur lequel je reviendrai. dnssec-failed.org est publié avec des signatures délibérément cassées. Celui-là échoue net, avec un diagnostic qui nomme le problème exact.

Voici la distinction qui se perd. Non sécurisé n’est pas un échec. Une zone non signée renvoie les données et vous dit qu’elle n’a pas pu les vérifier. Seule une zone qui prétend être signée et ne sait ensuite pas le prouver est rejetée.

La chaîne elle-même est visible si vous voulez la voir :

$ dig +short @127.0.0.53 uk DS
43876 8 2 A107ED2AC1BD14D924173BC7E827A1...

$ dig +short @127.0.0.53 damiendye.uk DS
2371 13 2 A5B2825C57899A5A15EE9703832C8358E0D19EF29DC72DD83C691ED77C33BD7F

$ dig +short @127.0.0.53 damiendye.uk DNSKEY
256 3 13 oJMRESz5E4gYzS/q6XDrvU1qMPYIjCWz...
257 3 13 mdsswUyr3DPW132mOi8V9xESWE8jTo0d...

La racine se porte garante de uk, uk se porte garant de damiendye.uk, et la clé 257 signe la clé 256 qui signe les enregistrements. L’algorithme 13 là-dedans, c’est ECDSA P-256, ce que vous voulez sur une zone neuve plutôt que le RSA que le DS de uk ci-dessus utilise encore.

Par le stub ordinaire, un programme qui n’a jamais entendu parler de resolved obtient le même verdict sous forme de drapeau AD :

$ dig @127.0.0.53 ietf.org A | grep flags
;; flags: qr rd ra ad;

$ dig @127.0.0.53 dnssec-failed.org A | grep status
;; ->>HEADER<<- status: SERVFAIL

Et le démon tient un décompte courant, qui est le moyen le plus rapide de voir si la validation fait quelque chose :

$ resolvectl statistics
DNSSEC Verdicts
                    Secure:  134
                  Insecure:   62
                     Bogus:    0
             Indeterminate:    2

Ce que coûte la validation

Ici je vais vous décevoir, parce que j’ai essayé de le mesurer correctement et je n’ai pas pu.

À chaud, c’est propre et c’est nul. Les mêmes 32 noms contre un cache plein ont coûté 16 ms sans validation et 23 ms avec. Une erreur d’arrondi.

C’est à froid que le coût devrait se voir, et un seul client ne peut pas l’isoler. J’ai lancé la série dans les deux sens et j’ai obtenu des réponses contradictoires : dans un ordre la validation avait l’air 74 % plus lente, dans l’autre elle avait l’air plus rapide, ce qui est impossible et vous dit ce qui est réellement mesuré. Quelle que soit la série qui passe en second, elle bénéficie du fait que le résolveur amont a déjà récupéré tout ce que la première a demandé. La variable qui domine n’est pas la validation, c’est le cache de qui était chaud.

Je ne vais donc pas vous donner un chiffre que je ne peux pas assumer. Ce qui est vrai, c’est le mécanisme, et la documentation en énonce la forme clairement : la validation « requires retrieval of additional DNS data, and thus results in a small DNS lookup time penalty » — exige la récupération de données DNS supplémentaires, et entraîne donc une petite pénalité sur le temps de recherche.2 Une recherche validée à froid descend la chaîne de délégation en récupérant DS et DNSKEY à chaque niveau avant de pouvoir répondre, donc elle coûte des allers-retours supplémentaires sur les noms que personne n’a encore demandés, et rien du tout sur les noms que quelqu’un a demandés.

C’est aussi pourquoi la même page avertit que couper le cache « comes at a performance penalty, which is particularly high when DNSSEC is used » — s’accompagne d’une pénalité de performance, particulièrement forte quand DNSSEC est utilisé.2 Les deux fonctionnalités ne sont pas indépendantes. La validation est abordable précisément parce que le cache fait que vous ne la payez qu’une fois.

Si vous voulez le vrai chiffre pour votre propre réseau, mesurez-le là-bas. Un client sur une connexion ne peut pas vous le dire.

Alors pourquoi la validation est-elle coupée

Parce que votre distribution l’a coupée quand elle a construit le paquet, et elle l’a fait pour une raison qu’elle a écrite quelque part.

Le systemd amont livre la validation activée. L’option de compilation le dit :

option('default-dnssec', type : 'combo',
       choices : ['yes', 'allow-downgrade', 'no'],
       value : 'allow-downgrade')

et le manuel amont est d’accord : DNSSEC= « Defaults to allow-downgrade » — vaut allow-downgrade par défaut.6

Maintenant regardez ce que Fedora passe à cette compilation :7

-Ddefault-dnssec=no
-Ddefault-dns-over-tls=no
-Ddefault-mdns=no
-Ddefault-llmnr=resolve

Et ce que passent Debian et Ubuntu :8

-Ddefault-dnssec=no
-Ddefault-llmnr=no
-Ddefault-mdns=no
-Ddns-over-tls=openssl

Du coup le fichier /usr/lib/systemd/resolved.conf sur une machine Fedora, celui qui est titré « Entries in this file show the compile time defaults », porte #DNSSEC=no. Relisez ça, parce que c’est sournois : la ligne a l’air d’être le logiciel qui vous annonce son propre défaut, et ce qu’elle vous renvoie en réalité, c’est l’option de compilation de Fedora, le vrai défaut amont n’apparaissant nulle part sur la page.

RéglageDéfaut amontCompilation FedoraCompilation Debian/UbuntuRésultat sur cette machine
DNSSEC=allow-downgradenonoDNSSEC=no
DNSOverTLS=nonono (compilé avec OpenSSL)-DNSOverTLS
MulticastDNS=yesnono-mDNS
LLMNR=yesresolvenoLLMNR=resolve

Chacune de ces lignes correspond à ce que resolvectl status affiche sur cette machine. Les sources, l’option de compilation, le système en fonctionnement : les trois concordent.

Le raisonnement de Fedora est dans la proposition de changement et il est d’une franchise rafraîchissante. La fonctionnalité « is known to cause compatibility problems with certain network access points » — est connue pour poser des problèmes de compatibilité avec certains points d’accès réseau — et Fedora « is not prepared to handle an influx of DNSSEC-related bug reports » — n’est pas prête à encaisser un afflux de rapports de bugs liés à DNSSEC, donc ça part coupé.9

Puis-je demander pourquoi la réponse à une fonctionnalité qui casse sur les mauvais réseaux est de la désactiver pour tout le monde, plutôt que de livrer allow-downgrade comme le fait l’amont et de la laisser se couper toute seule là où il le faut ? Pas qui a décidé. Ce qui, dans le processus, y a mené. Parce que allow-downgrade existe précisément pour le cas du portail captif, c’est le défaut amont exactement pour cette raison, et livrer no à la place veut dire qu’une machine sur un réseau parfaitement sain n’a pas de validation non plus.

Pour être juste envers eux, allow-downgrade a son propre problème, et il est réel. Ce mode détecte un résolveur incapable de faire du DNSSEC et arrête discrètement de valider. Un attaquant capable de façonner vos réponses DNS peut faire se déclencher cette détection exprès, et la documentation le dit clairement : ça « makes DNSSEC validation vulnerable to ‘downgrade’ attacks » — rend la validation DNSSEC vulnérable aux attaques par déclassement.2 Une fonctionnalité de sécurité que n’importe quel attaquant peut désactiver fait moins que ce qu’elle en a l’air.

Aucun des deux défauts n’est donc bon. no ne vous donne rien. allow-downgrade vous donne quelque chose qu’un attaquant peut vous reprendre, et yes vous donne la vraie chose plus tout ce qui est dans la section suivante.

Quelle part du web est signée, au fait

Avant de dépenser beaucoup d’efforts sur la validation, il vaut la peine de savoir quelle fraction de vos recherches elle peut éventuellement protéger. J’ai donc demandé à resolved le verdict sur l’apex de trente-deux domaines que ce sujet met réellement en cause : les organismes qui ont écrit la norme, les maisons qui livrent le résolveur, et l’infrastructure que tout le monde résout qu’il le veuille ou non.

Seize sur trente-deux. La moitié.

SignésNon signés
Normes et registresietf.org, iana.org, icann.org, rfc-editor.org, ripe.net, isc.org, nlnetlabs.nl, nic.cz, afnic.fr, verisign.comaucun
Distributions et éditeursdebian.org, fedoraproject.org, opensuse.org, almalinux.orgredhat.com, ubuntu.com, canonical.com, suse.com, rockylinux.org, archlinux.org
systemd lui-mêmeaucunsystemd.io, freedesktop.org
Infrastructurecloudflare.com, gov.ukgithub.com, kernel.org, google.com, wikipedia.org, mozilla.org, apache.org, gnu.org, quad9.net

Lisez la première colonne de haut en bas. Tous les organismes de normalisation et tous les registres ont signé. Dix sur dix, sans exception : les gens qui ont écrit DNSSEC, et les gens qui tiennent les registres qui publient les enregistrements DS de tous les autres. Ils ont fait le travail sur leurs propres zones.

Maintenant lisez la troisième ligne en travers. systemd.io n’a pas de DS. Le projet qui a écrit le résolveur validant dont parle tout ce billet n’a pas signé son propre domaine, et freedesktop.org, où vit sa documentation, non plus. En dessous, quad9.net n’est pas signé non plus, ce qui mérite qu’on s’y arrête une seconde : un résolveur public dont tout l’argumentaire est qu’il valide DNSSEC pour vous, sur une zone que personne ne peut valider.

Et les éditeurs se séparent nettement sur une ligne. Les distributions communautaires ont signé. debian.org, fedoraproject.org, opensuse.org, almalinux.org. Les entreprises, non. redhat.com, ubuntu.com, canonical.com, suse.com. Ce sont les quatre organisations qui empaquettent et livrent ce résolveur à la majorité du parc Linux.

Aucune lacune technologique là-dedans. Le protocole est déployable depuis plus de quinze ans, l’outillage est gratuit, et les registres prennent l’enregistrement DS sans le facturer. Je vous le dis gratuitement : les gens qui ont écrit la norme ont signé, et la plupart des gens qui livrent le logiciel, non.

Il y a une version plus tranchante du même argument dans gov.uk, qui est signé, et qui fait ensuite ceci :

$ dig +short @127.0.0.53 www.gov.uk CNAME
www-cdn.production.govuk.service.gov.uk.

$ dig +short @127.0.0.53 service.gov.uk DS
                                          (nothing)

uk a un DS. gov.uk a un DS. service.gov.uk n’en a aucun, donc la chaîne s’arrête net à cet endroit, et www.gov.uk résout en non sécurisé alors même que l’apex au-dessus est signé correctement. Quelqu’un a fait le travail sur gov.uk puis a pointé le site web réel vers une délégation non signée. Un travail à moitié fait.

Du coup, si vous signez une zone, vérifiez les noms que les gens tapent réellement. Un apex signé qui pointe par CNAME dans une zone de CDN non signée ne vous apporte rien.

DNS over TLS marche, sous conditions

DNS over TLS est implémenté, il est compilé dans toutes les constructions grand public, et il marche. Il est coupé par défaut partout, y compris en amont, où DNSOverTLS= « Defaults to no » — vaut no par défaut.6

La configuration tient en deux lignes, et c’est la seconde que les gens ratent :

[Resolve]
DNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=yes

Ce #dns.quad9.net n’est pas de la décoration. Il fixe le nom utilisé pour le SNI et pour valider le certificat. Laissez-le de côté et le certificat est « checked against the server’s IP » — vérifié contre l’IP du serveur.2 Ça marche avec les gros fournisseurs parce qu’ils mettent des adresses IP dans les SAN de leur certificat, mais c’est le contrôle le plus faible, il casse à la seconde où un fournisseur arrête de le faire, et il ne vous protège en rien d’une redirection vers une autre adresse qui se trouve détenir un certificat valide pour elle-même. L’article du magazine de Fedora sur le sujet omet le nom d’hôte,10 ce qui est dommage, parce que la syntaxe est là, dans les commentaires du fichier de configuration livré.

Strict et opportuniste sont deux réglages très différents

ModeSur un serveur qui gère DoTSur un serveur qui ne le gère pasAuthentifie le serveur
yesChiffréToutes les recherches échouentOui
opportunisticChiffréEn clair, silencieusementNon
noEn clairEn clairsans objet

opportunistic se lit comme le juste milieu raisonnable et ne l’est le plus souvent pas. La documentation le dit franchement : dans ce mode « the resolver is not capable of authenticating the server, so it is vulnerable to ‘man-in-the-middle’ attacks » — le résolveur n’est pas capable d’authentifier le serveur, il est donc vulnérable aux attaques de l’homme du milieu,2 et quiconque peut faire tomber votre trafic sur le port 853 peut forcer le déclassement. Ça vous protège de l’observation passive sur un réseau où personne n’essaie. Face à quelqu’un qui essaie, ça ne fait rien.

yes est le réglage honnête. Il veut aussi dire que quand il n’arrive pas à se connecter, vous n’avez plus de DNS du tout, ce qui vaut la peine d’être su avant de le mettre sur une machine que vous ne pouvez pas atteindre à pied.

J’ai essayé le DoT strict vers Quad9 depuis cette machine et la requête est restée suspendue. Pas d’erreur, pas de délai dépassé, rien dans le journal entre le vidage et mon retour en arrière deux minutes plus tard :

Sep 27 17:12:11 systemd-resolved[40105]: wlp4s0: Bus client set DNS server list to: 9.9.9.9#dns.quad9.net, ...
Sep 27 17:12:14 systemd-resolved[40105]: wlp4s0: Bus client set DNSOverTLS setting: yes
Sep 27 17:12:15 systemd-resolved[40105]: Flushed all caches.
Sep 27 17:14:39 systemd-resolved[40105]: wlp4s0: Bus client set DNS server list to: 192.0.2.53, ...

Je ne peux pas vous dire d’ici si le port 853 est joignable sur cette connexion, parce que le shell depuis lequel j’ai fait le test n’atteignait pas non plus le port 443 et était clairement filtré lui-même. Ce que le journal montre, en revanche, c’est le mode de défaillance : un DoT strict qui n’arrive pas à se connecter ne rapporte rien d’utile. Il attend. Si vous activez ça et que votre DNS devient silencieux, vérifiez ss -tn dport = :853 avant d’aller regarder du côté de resolved.

Le tester correctement

# does the transport actually come up
resolvectl flush-caches
resolvectl query ietf.org        # expect: acquired via ... encrypted transport: yes

# is anything still going out in the clear
sudo tcpdump -ni any 'port 53 and not host 127.0.0.53'

La seconde commande est celle qui dit la vérité. Avec le DoT qui marche, rien ne devrait quitter la machine sur le port 53, et tout ce qui le fait est un programme qui a trouvé le moyen de contourner le stub.

DNS over HTTPS n’existe pas ici

Réponse directe à la question : systemd-resolved ne gère pas DNS over HTTPS. Ni partiellement, ni derrière un drapeau, ni avec une option de compilation que personne n’active. Il n’y a pas de DoH là-dedans, point.

Et ce n’est pas lu dans la documentation, qui pourrait simplement être périmée. C’est ce que contient le binaire :

$ strings /usr/lib/systemd/systemd-resolved | grep -icE 'application/dns-message|dns-query|:443'
0

$ grep -oE 'name="DNSOver[A-Za-z]*"' /usr/share/dbus-1/interfaces/org.freedesktop.resolve1.Manager.xml
name="DNSOverTLS"

Pas de format de fil DoH, pas de HTTP/2, une seule propriété de chiffrement sur l’interface D-Bus et c’est TLS. La liste complète des directives de resolved.conf en amont compte dix-sept réglages et aucun ne mentionne HTTPS.6

Ça a été demandé. Le ticket #8639, « Add support for DNS-over-HTTPS to systemd-resolved », a été ouvert le 2 avril 2018 et il est toujours ouvert, avec deux pull requests attachées et rien de fusionné.11 Huit ans et ça continue.

Même charge utile, même chiffrement, port différentLES DEUX PORTENT LA MÊME REQUÊTE DNS, DANS LE MÊME TLSDNS over TLSDNS over HTTPStcp/853tcp/443Dans systemd-resolvedoui, depuis la v239non, pas du toutUn observateur voit les nomsnonnonUn réseau peut le bloqueroui, son propre portpas sans malVous pouvez auditer le vôtreouinonDemandé dans le ticket systemd 8639, avril 2018. Toujours ouvert.
Même charge utile, même chiffrement. La différence, c’est le port qu’il emprunte, et qui peut le voir

Est-ce le mauvais choix ? Pas de façon évidente, et l’argument en faveur du DoT tient la route. Les deux transportent du DNS à l’intérieur de TLS et les deux arrêtent le même observateur passif. L’avantage du DoH est qu’il se cache sur le port 443 avec tout le reste, si bien qu’un réseau qui veut bloquer le DNS chiffré doit travailler beaucoup plus dur. C’est véritablement utile quand l’opérateur du réseau est l’adversaire.

Ça coupe aussi dans l’autre sens. Là où l’opérateur, c’est vous, cette indistinguabilité veut dire que vous ne pouvez pas non plus auditer votre propre DNS, et chaque application qui embarque son propre client DoH cesse d’utiliser le résolveur du système, ce qui est la façon dont on se retrouve avec un navigateur qui ignore votre split DNS, votre cache et vos zones internes. Sur votre propre matériel, un résolveur visible sur un port connu est une fonctionnalité.

Donc : si le DoT atteint votre résolveur, utilisez-le et vous ne perdez rien. Si le port 853 est bloqué, resolved n’a pas de réponse et vous voulez un proxy DoH devant lui, dnscrypt-proxy ou cloudflared sur le bouclage avec resolved pointé dessus. Le cache et la validation restent le travail de resolved. Seul le transport bouge.

Ce que votre distribution livre réellement

Le même démon se comporte très différemment selon qui l’a empaqueté. Activer le service est la moitié facile. La moitié qui saute, c’est de le brancher aux outils réseau que la distribution utilise réellement, et sur l’une de ces quatre il n’est véritablement pas branché tant que vous ne le faites pas vous-même.

InstalléActivénss-resolve câbléLa configuration arrive parSupporté
Fedora (33+)ouiouioui, dans systemd-libsNetworkManageroui
Ubuntuouiouiouinetplan, puis NM ou networkdoui
Debian (12+)paquet séparénonnon, paquet séparévous choisissez et vous câblezoui
RHEL / Rocky / Alma (9, 10)ouinonouiNetworkManager, une fois prévenuTechnology Preview

Validation et cache plein : les réglages eux-mêmes

Ce sont les quatre mêmes lignes partout, parce que resolved.conf est resolved.conf sur toutes les distributions. Ce qui diffère, c’est seulement la façon dont le reste de la configuration parvient au démon, ce qui fait les quatre sections suivantes.

# /etc/systemd/resolved.conf.d/60-local.conf
[Resolve]
DNSSEC=allow-downgrade
Cache=yes
CacheFromLocalhost=no
StaleRetentionSec=1d

Utilisez un drop-in plutôt que d’éditer /etc/systemd/resolved.conf, parce que le fichier principal a une priorité plus faible que n’importe quel drop-in et qu’une mise à jour de paquet peut vous le disputer. La numérotation est une convention documentée : les fournisseurs prennent 10 à 40 sous /usr/, vous prenez 60 à 90 sous /etc/, donc c’est vous qui gagnez.6

Sur systemd 261 et au-delà il y a une cinquième ligne qui vaut la peine d’être ajoutée, parce que jusque-là la taille du cache n’était pas réglable du tout :

DNSCacheSize=16384    # systemd 261+; default 4096, max 2^24

Appliquez-la et confirmez que le démon est d’accord avec le fichier, plutôt que de supposer qu’il l’a lu :

systemd-analyze cat-config systemd/resolved.conf   # every fragment, in precedence order
systemctl restart systemd-resolved
resolvectl status | grep -E 'DNSSEC|Protocols'
resolvectl statistics                              # verdicts should start moving

DNSSEC=allow-downgrade est le réglage que je mettrais sur une machine que je ne vais pas surveiller, pour les raisons de la section précédente. DNSSEC=yes est le réglage honnête et il échoue fermé. Choisissez délibérément.

Le brancher à la pile réseau

Activer le service tient en une commande. Amener les outils réseau de votre distribution à lui remettre leur configuration DNS est la partie qui varie, et c’est là que « activé » et « qui marche correctement » se séparent.

D’où vient la configuration DNSPour activer DNSSECPour dimensionner le cacheCâblage supplémentaire
FedoraNetworkManager, automatiquementdrop-indrop-inaucun
Ubuntunetplan, puis NM ou networkddrop-in, ou par lien dans .networkdrop-inaucun
Debianla pile que vous avez choisiedrop-in, ou par lien dans .networkdrop-inlibnss-resolve, plus la pile ci-dessous
Famille RHELNetworkManager, une fois prévenudrop-indrop-indns=systemd-resolved

Fedora

L’intégration la plus complète des quatre, et la seule où tout est déjà branché.

NetworkManager possède le réseau et remet le DNS à resolved sans qu’on le lui demande. Sur cette machine il n’y a aucune ligne dns= nulle part dans /etc/NetworkManager/ et ça marche quand même, parce que NetworkManager détecte le démon en fonctionnement et l’utilise. /etc/resolv.conf est le lien vers le stub, et la glibc est câblée parce que libnss_resolve.so.2 est livré dans systemd-libs, qui n’est pas optionnel :

$ rpm -qf /usr/lib64/libnss_resolve.so.2
systemd-libs-259.9-1.fc44.x86_64

$ grep ^hosts: /etc/nsswitch.conf
hosts: files myhostname mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns

Donc sur Fedora le drop-in ci-dessus est tout le travail. Rien d’autre à connecter.

Ubuntu

Activé par défaut, et nss-resolve est câblé de la même façon. La différence, c’est que la configuration arrive d’habitude par netplan :

network:
  version: 2
  ethernets:
    enp1s0:
      dhcp4: true
      nameservers:
        addresses: [9.9.9.9, 149.112.112.112]
        search: [example.com]

Netplan remet ça à son moteur de rendu, NetworkManager sur le bureau ou systemd-networkd sur le serveur, et le moteur le remet à resolved. Notez ce que netplan ne sait pas exprimer. Il n’y a pas de clé netplan pour DNSSEC=, ni pour DNSOverTLS=. Celles-là vont dans le drop-in ci-dessus, qui est de toute façon leur place.

Si vous êtes sur systemd-networkd, les réglages par lien sont aussi disponibles nativement dans le fichier .network, et un réglage par lien l’emporte sur le réglage global :

# /etc/systemd/network/10-lan.network
[Network]
DNS=9.9.9.9#dns.quad9.net
DNSSEC=yes
DNSOverTLS=yes
Domains=~.

Debian demande d’être câblé à la main

C’est celle qui n’est véritablement pas branchée, et la raison d’être de la section précédente.

Depuis Debian 12, systemd-resolved est un paquet séparé, et les notes de version sont explicites sur la mise à niveau : « The new systemd-resolved package will not be installed automatically on upgrades » — le nouveau paquet systemd-resolved ne sera pas installé automatiquement lors des mises à niveau — et « until it has been installed, DNS resolution might no longer work since the service will not be present on the system. » — tant qu’il n’a pas été installé, la résolution DNS peut ne plus fonctionner puisque le service ne sera pas présent sur le système.12 Les mêmes notes tranchent la question plus large : « systemd-resolved was not, and still is not, the default DNS resolver in Debian. » — systemd-resolved n’était pas, et n’est toujours pas, le résolveur DNS par défaut dans Debian.

L’installer, c’est trois paquets, pas un, et le second est celui que tout le monde rate :

apt install systemd-resolved libnss-resolve
systemctl enable --now systemd-resolved

libnss-resolve est seulement en Suggests:, pas une dépendance,13 et Suggests est la seule relation sur laquelle apt n’agit pas. Donc personne ne l’installe.

La raison pour laquelle personne ne le remarque est plus intéressante que la raison pour laquelle personne ne l’installe. Laissez-le de côté et la glibc atteint quand même resolved, parce que /etc/resolv.conf pointe vers le stub et que le simple nss-dns lui parle. L’essentiel du démon continue de fonctionner :

Sans libnss-resolveAvec
CacheOui, via le stubOui
Validation DNSSECOui, ça se passe dans le démonOui
DNS over TLSOuiOui
Le bit AD atteint la glibcOui, resolv.conf porte trust-adOui
Sécurisé vs non sécurisé vs falsifiéNon, un seul bitOui
Adresses à portée de lienNonOui
/etc/hosts lu par le démonNonOui

Rien dans la colonne de gauche n’a l’air cassé, donc rien n’est réparé. Ce que vous perdez, c’est ce que le format de fil du DNS ne sait pas porter, et le cas le plus clair est une adresse qui a une portée :

$ getent hosts _gateway            # via nss-resolve
fe80::5054:ff:fe12:3456 _gateway

$ dig +short @127.0.0.53 _gateway  # via the stub, as nss-dns would
192.0.2.1

Même nom, même démon, deux réponses différentes. Une adresse IPv6 lien-local n’a aucun sens sans l’interface à laquelle elle est rattachée, et il n’y a nulle part dans une réponse DNS où mettre ça, donc le stub ne peut rendre que l’IPv4. L’API native a un endroit où la mettre et vous donne l’adresse que vous vouliez réellement.

La ligne du verdict, c’est le même problème. AD est un seul bit : signé ou non. L’API native sépare sécurisé de non sécurisé de falsifié, ce qui est la différence entre « personne n’a signé ça » et « quelqu’un a signé ça et quelqu’un d’autre y a touché ». Une application à qui ça importe ne peut pas distinguer les deux sur le fil.

C’est ça, l’écart entre le service qui tourne et le service qui est branché, et il reste invisible jusqu’à ce que vous alliez le chercher.

Vérifiez que c’est bien en place :

grep ^hosts: /etc/nsswitch.conf     # wants 'resolve [!UNAVAIL=return] dns'
Quatre portes d'entrée sur Debian, et celle qu'on ne vous installe pasD'OÙ VIENT LA CONFIGURATIONCOMMENT ÇA ENTRELE DÉMONifupdown, statiqueifupdown, DHCPsystemd-networkdNetworkManagercouche resolvconf= resolvectlresolved127.0.0.53ET LE MORCEAU QUE PERSONNE N'INSTALLEglibc getaddrinfo()libnss-resolveEn Suggests: seulement. Sans lui la glibc marche quand même, et le verdict ne l'atteint jamais.
Quatre portes d’entrée sur Debian, et celle qu’on ne vous installe pas

Comment le reste de votre réseau l’atteint dépend de laquelle des piles de Debian vous utilisez.

Le paquet déclare Provides: resolvconf et Conflicts: resolvconf, openresolv,13 donc il reprend l’interface resolvconf à son compte. /usr/sbin/resolvconf devient un lien vers resolvectl, qui est un binaire multi-appel : invoqué sous ce nom, il parle le protocole resolvconf(8) et pousse tout ce qu’on lui remet directement dans resolved.14 Tout ce qui appelle déjà resolvconf -a continue donc de fonctionner sans modification, avec systemd-resolved comme seul backend supporté.

L’interface ne survit pas entièrement, et les manques échouent bruyamment plutôt que silencieusement :

Option resolvconfSous systemd-resolved
-a <iface>Enregistre le DNS par lien lu sur l’entrée standard. Celle qui compte
-d <iface>Désenregistre, comme resolvectl revert
-xTraduit en domaine de routage ~.
-pMarque le lien comme n’étant pas une route par défaut (systemd 257+)
-fRend -a et -d silencieux sur une interface absente
-mAccepté et ignoré silencieusement
-u, -i, -I, -l, -r, -R, -v, -VNon supportées. La commande échoue

Un piège à connaître avant de le déboguer : la couche de compatibilité n’écrit /etc/resolv.conf que lorsque ce fichier est un lien vers /run/systemd/resolve/resolv.conf, et pas quand c’est un fichier statique.14

  • ifupdown, la pile par défaut d’une installation serveur Debian. La strophe dns-nameservers dans /etc/network/interfaces a toujours été implémentée par les hooks de l’ancien paquet resolvconf, et systemd-resolved est en conflit avec ce paquet et ne livre aucun hook /etc/network/if-up.d/ à lui. Pour une interface statique, ne comptez pas sur la strophe. Posez les serveurs explicitement sur le lien et laissez resolved les posséder :

    # /etc/network/interfaces
    auto enp1s0
    iface enp1s0 inet static
        address 192.0.2.10/24
        gateway 192.0.2.1
        up   /usr/sbin/resolvconf -a $IFACE <<< 'nameserver 192.0.2.53'
        down /usr/sbin/resolvconf -d $IFACE
    
  • Le DHCP sur ifupdown est déjà géré. isc-dhcp-client livre des hooks nommés d’après le démon, /etc/dhcp/dhclient-enter-hooks.d/resolved-enter et /etc/dhcp/dhclient-exit-hooks.d/resolved,15 si bien que les serveurs DNS d’un bail atterrissent sur le bon lien sans rien à configurer.

  • systemd-networkd est l’option la plus propre et ne demande aucune couche de compatibilité, parce que les deux moitiés sont le même projet. Le DNS par lien, DNSSEC et le DoT sont des clés natives du fichier .network, exactement comme dans l’exemple Ubuntu ci-dessus.

  • NetworkManager, sur un bureau Debian, demande à être prévenu une fois :

    # /etc/NetworkManager/conf.d/10-resolved.conf
    [main]
    dns=systemd-resolved
    

Choisissez-en une et sachez laquelle vous avez choisie. Le mode de défaillance ici n’est pas un démon qui refuse de démarrer, c’est deux piles qui croient toutes les deux posséder /etc/resolv.conf, ce qui se lit comme un DNS intermittent et vous gâche un après-midi. resolvectl status dit sur quel lien les serveurs ont atterri. Si la réponse est aucun, rien n’alimente le démon, et c’est ça le bug.

RHEL, Rocky et Alma

Et voici celle qui vaut d’être lue deux fois. Le paquet est là, nss-resolve est disponible, NetworkManager sait le piloter, et la documentation de Red Hat dit ceci :

systemd-resolved is an unsupported Technology Preview.16

Cette formulation est dans les notes de version de RHEL 9 depuis la 9.0 et elle y est toujours en 9.8. Technology Preview veut dire aucun SLA de production, et Red Hat ne le recommande explicitement pas pour un usage en production.

Il n’est pas activé non plus. NetworkManager possède resolv.conf sur ces systèmes, donc l’activer est un réglage NetworkManager plutôt que simplement activer l’unité :

# /etc/NetworkManager/conf.d/10-resolved.conf
[main]
dns=systemd-resolved
systemctl enable --now systemd-resolved
systemctl reload NetworkManager
resolvectl status          # confirm NM actually handed the servers over

Après ça le drop-in ci-dessus s’applique sans changement, et la validation et le cache se comportent exactement comme sur Fedora.

Donc sur RHEL vous avez une décision qui n’existe pas sur les autres. Vous voulez un résolveur local validant, avec cache et capable de split DNS sur une machine RHEL supportée ? Alors la réponse supportée n’est pas celle-ci. C’est unbound, ou dnsmasq via le dns=dnsmasq de NetworkManager, deux choses derrière lesquelles Red Hat se rangera effectivement.

L’étiquette n’est pas un commentaire sur le code. C’est un commentaire sur ce derrière quoi Red Hat mettra un SLA, et un résolveur validant avec cache est une chose sur laquelle les clients ouvrent des tickets. C’est de bonne guerre. Mais si vous achetez RHEL pour le support, faire tourner la résolution de noms sur un composant non supporté est une décision à prendre délibérément et à écrire quelque part, pas une chose dans laquelle dériver parce que c’était dans le dépôt.

Rien n’est réellement mutilé, et voici comment le prouver

La prémisse mérite d’être testée, parce que « ma distribution l’a désactivé, je vais devoir recompiler » est le réflexe et qu’ici il est faux.

systemd a deux sortes d’options de compilation entièrement différentes, et on en parle comme si c’était la même :17

Option de compilationSorteAmontFedoraDebian et UbuntuModifiable à l’exécution
resolvecapacitéactivéecompiléetruenon, et les deux la compilent
nss-resolvecapacitéactivéecompilée, livrée dans systemd-libsenabled, livrée comme libnss-resolvenon, et les deux la compilent
dns-over-tlscapacitéautoauto, résolu vers OpenSSLopensslnon, et les deux la compilent
opensslcapacitéactivéeenabledenablednon, et les deux l’activent
default-dnssecdéfaut seulementallow-downgradenonooui, dans un drop-in
default-dns-over-tlsdéfaut seulementnonodéfaut amontoui, dans un drop-in
default-mdnsdéfaut seulementyesnonooui, dans un drop-in
default-llmnrdéfaut seulementyesresolvenooui, dans un drop-in

Lisez la dernière colonne. Chaque réglage dont ce billet s’est plaint se trouve dans la moitié basse, et chacun est un défaut, pas une capacité. Le code est compilé dans les deux. Personne n’a rien retiré.

La preuve est plus haut dans ce billet et n’a demandé aucun compilateur. Sur le paquet Fedora d’origine, avec DNSSEC=no gravé dedans, une seule commande a activé la validation et elle a marché complètement : une zone signée authentifiée, une non signée rapportée honnêtement, une cassée refusée avec un diagnostic précis. Si ça avait été retiré à la compilation, resolvectl status n’aurait jamais dit yes/supported.

Alors vérifiez votre propre construction avant d’aller chercher une chaîne de compilation :

# is the crypto there at all
systemctl --version | tr ' ' '\n' | grep -E '^[+-](OPENSSL|GNUTLS|GCRYPT)$'

# ask for the feature and read back what the daemon says it can do
resolvectl dnssec <link> yes
resolvectl status <link> | grep DNSSEC          # yes/supported means the code is there

Sur cette machine Fedora, c’est -GCRYPT +GNUTLS +OPENSSL, ce qui suffit largement : DNSSEC a besoin de l’un d’eux et le DoT est compilé contre OpenSSL.

Ce que les distributions retirent vraiment

Il y a une chose que toutes les deux enlèvent, et elle n’est pas dans la liste dont on se plaint.

L’amont compile dans le binaire une liste de serveurs DNS de secours : Cloudflare, Google et Quad9.17 Fedora et Debian construisent toutes les deux avec -Ddns-servers= réglé à rien, et vous pouvez le confirmer sur le binaire livré plutôt que de me croire sur parole :

$ strings /usr/lib/systemd/systemd-resolved | grep -ciE 'quad9|one\.one\.one|dns\.google'
0

Rien. Aucun hyperscaler gravé dans l’exécutable.

C’est le seul endroit où les deux distributions ont amélioré l’amont, et ça mérite d’être dit clairement vu la place que ce billet a donnée à des défauts que je crois mauvais. Une machine qui perd sa configuration DNS devrait échouer bruyamment et vous attendre. Elle ne devrait pas router discrètement chaque nom que vous cherchez vers un résolveur dans une autre juridiction que vous n’avez jamais choisi. Vous voulez un secours ? Mettez FallbackDNS= et choisissez qui c’est.

Si vous avez véritablement besoin de recompiler

Un cas réel existe : une construction minimale ou embarquée où quelqu’un a passé -Ddns-over-tls=false et où le transport est véritablement absent. Vérifiez d’abord avec les commandes ci-dessus, parce que c’est rare et que ça ressemble exactement au réglage simplement coupé.

Si vous en avez besoin, recompilez le paquet, jamais make install :

# Fedora
dnf download --source systemd
rpmbuild --rebuild --define '_with_upstream 1' systemd-*.src.rpm

# Debian and Ubuntu
apt source systemd
cd systemd-*/ && editor debian/rules       # change the -Ddefault-* flags
dpkg-buildpackage -us -uc -b

Je n’ai lancé ni l’un ni l’autre sur cette machine, alors traitez-les comme la forme du travail, pas comme une recette testée. C’est le principe qui compte. systemd est le PID 1, et un make install compilé à la main par-dessus la copie de votre distribution le sort du gestionnaire de paquets : plus de mises à jour de sécurité, et la prochaine montée de version se bat avec vous pour les fichiers. Construire le paquet garde les deux. Pour presque tout le monde, la réponse honnête est qu’un drop-in de quatre lignes fait le même travail en vingt secondes, et c’est pourquoi cette section existe surtout pour vous en dissuader.

Trois configurations qui valent le coup

Pas un menu de toutes les options. Trois positions, dont chacune je défendrais réellement.

Portable, réseaux hostilesServeur, votre propre réseauStrict
Ce contre quoi vous vous défendezLe café et le portail de l’hôtelRien de local ; l’amont valide déjàUn chemin de résolution auquel vous ne faites pas confiance
DNSSEC=allow-downgradenoyes
DNSOverTLS=opportunisticnoyes
StaleRetentionSec=1d1dnon défini
Survit à un portail captifOuisans objetNon
Survit à un .arpa filtréOuiOuiNon
Survit au port 853 bloquéOuisans objetNon
Un attaquant peut le déclasserOui, les deux réglagessans objetNon

Un portable sur des réseaux que vous ne contrôlez pas. Chiffrez le transport, et prenez le risque de déclassement en échange du fait que ça continue de marcher derrière un portail captif.

# /etc/systemd/resolved.conf.d/60-local.conf
[Resolve]
DNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=opportunistic
DNSSEC=allow-downgrade
Cache=yes
StaleRetentionSec=1d

Un serveur sur un réseau que vous administrez, avec un amont validant. Ça s’est déjà passé un saut plus loin. Ne le faites pas deux fois, et ne prenez pas de dépendance sur un résolveur public.

[Resolve]
DNSSEC=no
DNSOverTLS=no
Cache=yes
CacheFromLocalhost=no
StaleRetentionSec=1d

Une machine où vous voulez la vraie chose. Strict des deux côtés, rien qu’un attaquant puisse déclasser, et vous avez vérifié que l’amont sait le porter.

[Resolve]
DNS=9.9.9.9#dns.quad9.net 149.112.112.112#dns.quad9.net
DNSOverTLS=yes
DNSSEC=yes
Cache=yes

Cette troisième cassera le DNS inverse si quoi que ce soit sur votre chemin filtre .arpa, elle cassera entièrement si le port 853 est bloqué, et elle échoue fermé plutôt que discrètement. Ce sont les conditions. Connaissez-les avant de la déployer, pas après, parce qu’une machine qui ne peut rien résoudre représente un long trajet si elle n’est pas dans la pièce d’à côté.

Quelle que soit celle que vous choisissez, appliquez-la puis vérifiez ce que le démon a réellement décidé plutôt que ce que vous avez écrit :

systemd-analyze cat-config systemd/resolved.conf   # every file, in order
systemctl restart systemd-resolved
resolvectl status                                  # the state it is really in

resolvectl status est l’oracle ici, de la même façon que la construction est l’oracle pour un site Hugo. Un réglage dans un fichier est une intention. Ce que status affiche est ce qui se passe.

Le reste des verbes vaut d’être connu, parce qu’à eux tous ils répondent à presque toutes les questions que vous vous poserez sur ce démon sans lire un journal :

CommandeCe qu’elle vous dit
resolvectl statusLes serveurs par lien, les domaines, les protocoles, et l’état vivant de DNSSEC et du DoT
resolvectl query NOMLa réponse, le protocole utilisé, le verdict DNSSEC, et si ça venait du cache
resolvectl statisticsLes succès de cache contre les échecs, et le décompte courant sécurisé/non sécurisé/falsifié
resolvectl show-cacheTout ce qui est actuellement en cache, par portée
resolvectl flush-cachesVider le cache sans redémarrer le démon
resolvectl dns LIEN ...Poser les serveurs sur un lien à l’exécution, sans fichier de configuration
resolvectl dnssec LIEN yesActiver la validation sur un lien, pour tester avant de s’engager
resolvectl domain LIEN ~xPoser les domaines de routage et de recherche sur un lien
resolvectl revert LIENJeter tout changement fait à l’exécution sur ce lien
resolvectl show-server-stateLe sondage des fonctionnalités par serveur : ce que chaque amont s’est révélé supporter
resolvectl monitorRegarder les requêtes et les réponses en direct, ce qui vaut mieux que deviner

Tout ce qui est dans le bloc du milieu ne vaut qu’à l’exécution et ne survit pas au retour d’un lien, ce qui en fait la bonne façon d’essayer un réglage avant de l’écrire dans un drop-in. Ça veut dire aussi que nmcli device reapply défera discrètement vos tests, alors vérifiez status après, pas avant.

Les défauts sont une position, pas un accident

Trois fonctionnalités dans la boîte. Une seule activée.

Tentant de lire ça comme de la paresse. Ça ne l’est pas. Chacun de ces défauts a été discuté par des gens qui voyaient la file de bugs de l’autre côté de la décision, et Fedora au moins a écrit honnêtement qu’elle ne pouvait pas faire face à la charge de support. C’est une vraie contrainte et je ne prétendrai pas le contraire.

Mais un défaut est une position, et celui-ci dit qu’une recherche que personne ne peut vérifier est une chose acceptable sur laquelle bâtir. Nous avons la norme depuis 2005. Les registres publient les enregistrements gratuitement, le résolveur sur votre machine implémente le tout, et la raison pour laquelle il reste inactif est que trop d’internet n’a jamais rien signé, si bien que l’activer fait de votre machine celle qui a l’air cassée. C’est la forme de toute norme que personne n’impose. Le faire correctement est un coût que vous portez tout seul, et le bénéfice n’arrive que quand assez d’autres l’ont porté aussi.

C’est pourquoi le recensement est la partie de tout ça sur laquelle j’irais réellement agir. Pas les réglages. Les réglages, c’est vingt minutes. Le recensement dit que les organisations qui publient les recommandations, livrent le résolveur et hébergent le code source du monde n’ont pour la plupart pas signé leurs propres zones, et que gov.uk a signé l’apex puis pointé le seul site web que personne ne peut éviter d’utiliser vers une délégation non signée. Quelqu’un a fait la partie difficile puis n’a jamais vérifié le nom que les gens tapent.

Vous ne pouvez arranger que la vôtre. Si vous tenez une zone, signez-la, déposez le DS, puis allez chercher le nom en www comme le ferait un visiteur et confirmez que la chaîne survit jusqu’au bout. C’est un après-midi. Faites ça et l’argument en faveur de la validation cesse d’être théorique pour tous ceux qui résolvent votre nom, ce qui est la seule partie de tout ça qu’aucun de nous ne peut réellement réparer autrement.


  1. systemd-resolved.service(8) — « implements a caching and validating DNS/DNSSEC stub resolver » ; les quatre interfaces client, les deux stubs, les enregistrements synthétiques, les règles de routage et les quatre modes de /etc/resolv.conf. ↩︎ ↩︎ ↩︎ ↩︎

  2. resolved.conf(5), tel que livré dans systemd 259.9 sur Fedora 44 — DNSSEC=, DNSOverTLS=, Cache=, CacheFromLocalhost=, StaleRetentionSec= et la description du stub proxy 127.0.0.54. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. resolved.conf(5) — DNSCacheSize=, MulticastDNSCacheSize= et LLMNRCacheSize=, « Each defaults to 4096 », ajoutés dans systemd 261. ↩︎

  4. RFC 5737 — « IPv4 Address Blocks Reserved for Documentation » : 192.0.2.0/24, 198.51.100.0/24 et 203.0.113.0/24. ↩︎

  5. RFC 3849 — 2001:DB8::/32 réservé à la documentation, « to reduce the likelihood of conflict and confusion when relating documented examples to deployed systems ». ↩︎

  6. resolved.conf(5), version amont la plus récente — DNSSEC= « Defaults to allow-downgrade », DNSOverTLS= « Defaults to no », la convention de numérotation des drop-ins, et la liste complète des directives, sans aucune option DNS over HTTPS. ↩︎ ↩︎ ↩︎ ↩︎

  7. Fedora systemd.spec, rawhide — les options de compilation meson -Ddefault-dnssec=no, -Ddefault-dns-over-tls=no, -Ddefault-mdns=no, -Ddefault-llmnr=resolve. ↩︎

  8. Debian systemd packaging, debian/rules — -Ddefault-dnssec=no, -Ddefault-llmnr=no, -Ddefault-mdns=no, -Ddns-over-tls=openssl. Le systemd d’Ubuntu dérive de cet empaquetage. ↩︎

  9. Fedora Change : systemd-resolved — Fedora 33 en a fait le résolveur par défaut ; change le défaut « from the upstream default DNSSEC=allow-downgrade to DNSSEC=no » parce que la fonctionnalité « is known to cause compatibility problems with certain network access points » et que Fedora « is not prepared to handle an influx of DNSSEC-related bug reports ». ↩︎

  10. Fedora Magazine — Use DNS over TLS — la configuration DNSOverTLS=yes recommandée, donnée avec des adresses IP nues et sans la forme SNI #hostname. ↩︎

  11. systemd issue #8639 — « Add support for DNS-over-HTTPS to systemd-resolved », ouvert le 2 avril 2018, toujours ouvert. ↩︎

  12. Debian 12 release notes, 5.2.3 — « The new systemd-resolved package will not be installed automatically on upgrades » ; « until it has been installed, DNS resolution might no longer work » ; « systemd-resolved was not, and still is not, the default DNS resolver in Debian. » ↩︎

  13. Debian systemd packaging, debian/control — la strophe systemd-resolved : Provides: resolvconf, Conflicts: resolvconf, openresolv, Replaces: resolvconf, et libnss-resolve listé seulement sous Suggests:. ↩︎ ↩︎

  14. resolvconf(1), le mode de compatibilité de resolvectl — resolvectl « is a multi-call binary. When invoked as ‘resolvconf’ … it is run in a limited resolvconf(8) compatibility mode » ; systemd-resolved « is the only supported backend » ; quelles options sont supportées, ignorées ou rejetées ; et la règle selon laquelle /etc/resolv.conf n’est écrit que lorsqu’il est un lien vers /run/systemd/resolve/resolv.conf. ↩︎ ↩︎

  15. Debian isc-dhcp-client file list — livre /etc/dhcp/dhclient-enter-hooks.d/resolved-enter et /etc/dhcp/dhclient-exit-hooks.d/resolved. ↩︎

  16. RHEL 9.4 release notes, Technology Previews — « Note that systemd-resolved is an unsupported Technology Preview. » Repris sans changement de RHEL 9.0 jusqu’à 9.8. ↩︎

  17. systemd meson_options.txt — la séparation entre les options de capacité (resolve, nss-resolve, dns-over-tls, openssl) et les options de valeur par défaut (default-dnssec, default-dns-over-tls, default-mdns, default-llmnr), et la liste de secours dns-servers compilée dans le binaire. ↩︎ ↩︎