# GÉNÉRÉ par Set-OPS (rôle serveur_resolveur). NE PAS éditer à la main. server: {% for a in serveur_resolveur_ecoutes %} interface: {{ a }} {% endfor %} {% for r in serveur_resolveur_reseaux_autorises %} access-control: {{ r }} allow {% endfor %} access-control: 127.0.0.0/8 allow hide-identity: yes hide-version: yes prefetch: yes do-ip6: no # LA RACINE PROUVE QUE NOTRE TLD N'EXISTE PAS, ET UNBOUND ETEND CE « NON » # A TOUT CE QUI EST DESSOUS (mesure du 2026-09-02). # # `internal.` n'est pas delegue dans la racine, qui est SIGNEE : elle rend donc une # preuve NXDOMAIN validee pour ce TLD. `harden-below-nxdomain` (actif par defaut) # tient ce « non » pour prouve et repond NXDOMAIN pour TOUT nom sous `internal.` # DEPUIS SON CACHE — sans jamais interroger la `stub-zone` ni la `forward-zone` # declarees plus bas. La delegation etait correcte, l'autoritatif repondait juste, et # pas une requete ne lui parvenait. # # CE QUI DECLENCHE L'EMPOISONNEMENT : n'importe quelle question sur un nom inexistant # sous `internal.` — y compris la zone d'UN AUTRE ecosysteme, que ce resolveur ne # sert pas et va donc chercher a la racine. Sur un resolveur partage par plusieurs # locataires, ca arrive en permanence. # # POURQUOI LE DIAGNOSTIC A DEMANDE DEUX JOURS. La panne parait INTERMITTENTE : au # redemarrage le cache est vide, tout fonctionne, on conclut que c'est regle. Puis # une seule requete l'eteint pour des heures. Pire, le cache contenait EN MEME TEMPS # la bonne reponse (`A 10.0.35.11`) et un message negatif pour le meme nom — c'est le # message qui etait servi. Il a fallu lire le cache pour le voir. # # MESURE QUI TRANCHE (trois essais, cache vide puis une requete empoisonnante) : # harden-below-nxdomain: no seul -> repond 10.0.35.11 # aggressive-nsec: no seul -> ne repond pas # Le second avait ete essaye d'abord et avait semble marcher : le REDEMARRAGE qu'il # imposait vidait le cache. C'est le vidage qui soignait, pas le reglage. # # Ce qu'on perd : une protection anti-usurpation qui suppose que la racine dit vrai # sur nos noms. Elle ne le peut pas — nos zones ne sont pas dans la racine. Ce qu'on # garde : la validation DNSSEC complete de tout ce qui, lui, y est delegue. harden-below-nxdomain: no # La zone souveraine n'est pas signée : on ne la soumet pas à la validation DNSSEC. domain-insecure: "{{ serveur_resolveur_zone_interne }}" # LA RACINE NIE NOTRE TLD, ET UNBOUND ÉTEND CE NON À TOUT CE QUI EST DESSOUS # (mesuré le 2026-09-01). # # `internal.` n'existe pas dans la racine, qui rend donc un NXDOMAIN **signé** — # `dig internal. SOA` le montre avec le drapeau `ad`. `harden-below-nxdomain` # (actif par défaut) tient ce non pour prouvé et fabrique lui-même un NXDOMAIN pour # tout nom sous `internal.`, SANS JAMAIS interroger la `stub-zone` déclarée plus # bas. La délégation était correcte, l'autoritatif répondait juste, et pas une seule # requête ne lui parvenait. # # CE QUE ÇA COÛTAIT : aucun locataire ne pouvait nommer un service du site. Chacun # gravait donc des ADRESSES dans sa configuration — le cache, le résolveur, la # forge — et le site devenait indéplaçable un intrant à la fois. Le défaut ne s'est # pas vu parce que les machines du site, elles, ont le plancher `/etc/hosts` : le # nom marchait partout où on le testait, et nulle part où on en avait besoin. # # `transparent` sur NOTRE zone seulement : la question reprend son cours normal — # donc atteint la `stub-zone` — sans rien changer pour le reste de l'Internet. # Même remède que pour les zones inverses ci-dessous, même cause : un « non » # d'Unbound qu'on ne distingue pas d'une absence. local-zone: "{{ serveur_resolveur_zone_interne }}." transparent {% for zone_inverse in serveur_resolveur_zones_inverses | default([]) %} # Les zones inverses ne le sont pas davantage — et `in-addr.arpa`, LUI, est signé # publiquement. Sans cette ligne, la validation chercherait une chaîne de confiance # vers une délégation qui n'existe pas, et tous les PTR rendraient SERVFAIL. domain-insecure: "{{ zone_inverse }}" # UNBOUND BLOQUE L'INVERSE PRIVÉ PAR DÉFAUT (2026-08-25). # # Il embarque des `local-zone` pour tout l'espace RFC1918 inversé, et y répond # lui-même. Avec la `stub-zone` pourtant bien déclarée, `dig -x` rendait NXDOMAIN # avec le drapeau `aa` — une réponse AUTORITAIRE d'Unbound, qui n'avait jamais # interrogé l'autoritatif. Le même NXDOMAIN qu'un nom qui n'existe pas : rien ne # distinguait le blocage de l'absence. # # `transparent` laisse la requête suivre son cours — donc atteindre la `stub-zone` # ci-dessous — pour CETTE zone seulement. # # Pas `nodefault` : ce mode ne retire un blocage que si le nom correspond EXACTEMENT # à une zone par défaut d'Unbound. La sienne est `10.in-addr.arpa`, le /8 entier ; nos # `/24` n'y correspondent pas, et la ligne restait sans effet — mesuré, le NXDOMAIN # autoritaire persistait après rechargement. # # Pas `unblock-lan-zones: yes` non plus : il aurait débloqué tout l'espace privé # inversé, y compris ce que cet écosystème ne sert pas. local-zone: "{{ zone_inverse }}" transparent {% endfor %} # UNBOUND REFUSE D'INTERROGER UNE LOOPBACK PAR DÉFAUT (2026-08-24). # # `do-not-query-localhost` vaut `yes` d'origine — une protection contre les boucles. # Or l'autoritatif de l'écosystème vit désormais SUR 127.0.0.1:5300, derrière ce # résolveur. Sans cette ligne, toute la zone souveraine rend SERVFAIL. # # Et la panne était MASQUÉE : le plancher /etc/hosts répondait pour les noms de la # flotte, si bien qu'un `getent hosts forge.genese.internal` semblait prouver que la # résolution marchait. Seul un `dig` explicite l'a montrée. do-not-query-localhost: no {% for d in serveur_resolveur_zones_deleguees | default([]) %} # LA ZONE D'UN AUTRE — CELLE DE L'HÉBERGEUR (2026-09-02). # # Un locataire dépend de services du SITE par leur NOM : son cache d'artefacts, sa # forge, son dépôt de sauvegarde, son autorité. Ces noms vivent dans une zone que ce # résolveur ne sert pas et n'a pas à servir — il doit savoir À QUI les demander. # # CE QUE SON ABSENCE A COÛTÉ : tant que les machines du locataire pointaient # directement sur le résolveur du site, ça marchait — par accident. Reconstruites # proprement, elles utilisent LEUR résolveur, qui ignorait la zone de l'hébergeur : # la recette de validation a échoué sur la RESTITUTION d'une sauvegarde, avec # « Could not resolve hostname sauvegarde.genese.internal ». La sauvegarde était # intacte ; c'est le chemin pour la nommer qui manquait. # # Mêmes deux lignes que pour notre propre zone, même cause : la racine rend un # NXDOMAIN signé pour le TLD, et `harden-below-nxdomain` l'étendrait à toute la zone # sans jamais interroger le transitaire déclaré plus bas. domain-insecure: "{{ d.nom }}" local-zone: "{{ d.nom }}." transparent {% endfor %} stub-zone: name: "{{ serveur_resolveur_zone_interne }}" stub-addr: {{ serveur_resolveur_autoritatif }}@{{ serveur_resolveur_autoritatif_port }} {# LES ZONES INVERSES SE DELEGUENT COMME LA DIRECTE (2026-08-25). Sans ces stubs, l'autoritatif servait les PTR et personne ne les lui demandait : `dig -x` rendait vide depuis les cinq machines, alors que la meme requete posee directement a l'autoritatif repondait juste. Un service correct derriere un chemin que rien n'emprunte. #} {% for zone_inverse in serveur_resolveur_zones_inverses | default([]) %} stub-zone: name: "{{ zone_inverse }}" stub-addr: {{ serveur_resolveur_autoritatif }}@{{ serveur_resolveur_autoritatif_port }} {% endfor %} {% for d in serveur_resolveur_zones_deleguees | default([]) %} {# `forward-zone` ET NON `stub-zone` : on s'adresse au RÉSOLVEUR de l'hébergeur, qui recursera pour nous, et non à son autoritatif — que rien ne nous autorise à joindre et dont l'adresse ne nous regarde pas. Le locataire connaît une seule adresse du site en matière de noms : celle par laquelle il a été amorcé. #} forward-zone: name: "{{ d.nom }}" {% for a in d.adresses %} forward-addr: {{ a }} {% endfor %} {% endfor %} {% if serveur_resolveur_transitaires %} forward-zone: name: "." {% for f in serveur_resolveur_transitaires %} forward-addr: {{ f }} {% endfor %} {% endif %}