Cet article documente le service DNS : le système de vues split-horizon, les zones, les forwarders, et l'intégration avec les défis ACME. L'image durcie qui exécute le résolveur a le sien.
Split-horizon : une vue par sous-réseau
Le résolveur génère une vue par sous-réseau de l'inventaire — près d'une vingtaine en production — plus une vue autoritative publique. Chaque sous-réseau ajouté ou retiré de l'inventaire fait apparaître ou disparaître la sienne.
flowchart TB
subgraph views["Vues du resolveur"]
direction TB
v1["Vue par sous-reseau<br/>recursion autorisee<br/>passerelle locale"]
v2["Vue de confiance<br/>zones internes completes"]
v3["Vue publique<br/>recursion desactivee<br/>autoritative"]
end
client["Client interne"] -->|"adresse source"| v1
trusted["Client de confiance"] -->|"liste d acces"| v2
external["Requete externe"] -->|"redirection WAN"| v3
acme["Mise a jour signee"] -->|"exclue des vues par sous-reseau"| v3
Chaque vue sélectionne ses clients par adresse source. Une directive supplémentaire exclut les requêtes signées des vues par sous-réseau, pour qu'elles tombent dans la vue publique — la seule qui porte la politique de mise à jour ACME.
La même question, deux réponses légitimes
Chaque vue a sa propre zone interne, où le nom de la passerelle pointe vers la dernière adresse du sous-réseau du client. Un objet du VLAN domotique résout donc la passerelle vers sa passerelle, pas vers celle du réseau bureautique.
C'est le comportement recherché, et c'est aussi ce qui rend le piège suivant si difficile à voir : dans ce résolveur, deux réponses différentes à la même question sont normales.
Toutes les requêtes DNS sont interceptées
Une règle de redirection capture tout le DNS interne : aucun client ne sort en DNS directement vers Internet, quelle que soit sa configuration.
nat destination rule 534 :
inbound-interface : group dnat-dns
protocol : tcp_udp
destination port : 53
translation : adresse du container du resolveur
Cette interception garantit que tous les clients interrogent le même serveur. Et c'est exactement le raccourci qui coûte cher : même serveur ne veut pas dire même cache.
Un cache de résolution par vue
Le résolveur alloue un cache par vue. Une vue par sous-réseau plus la vue publique, c'est donc une vingtaine de résolveurs indépendants derrière une seule instance. Chacun interroge les forwarders pour son propre compte.
Pour tout ce qui tourne — CDN, anycast, répartition tournante — deux vues reçoivent alors deux réponses différentes, au même instant, du même serveur :
| Nom | Vue du client | Vue du proxy |
|---|---|---|
accounts.google.com |
une adresse | une autre |
ghcr.io |
une adresse | une adresse voisine |
| une API domotique | huit adresses | huit adresses, une seule commune |
Pourquoi le déchiffrement rend cette divergence fatale
Sans proxy, deux réponses différentes d'un CDN sont sans conséquence : le client se connecte à l'une ou à l'autre, les deux fonctionnent.
Dès qu'un proxy s'interpose en interception, la même divergence devient une erreur dure. Le client ouvre sa connexion vers l'adresse que sa vue lui a rendue. Le proxy récupère cette destination, lit le nom de serveur annoncé dans la négociation TLS, puis résout ce nom dans sa vue à lui. Les deux jeux d'adresses ne se recoupent pas — le proxy conclut à une usurpation d'en-tête et répond une erreur, servie en clair sur une connexion que le client croit chiffrée.
flowchart LR
client["Client"] -->|"resout dans sa vue"| ipa["Une adresse"]
squid["Proxy en interception"] -->|"resout dans la sienne"| ipb["Une autre adresse"]
ipa --> cmp["Le proxy compare la destination<br/>a sa propre resolution"]
ipb --> cmp
cmp -->|"aucun recouvrement"| err["Usurpation supposee<br/>connexion refusee"]
Le diagnostic tient dans la corrélation : des dizaines d'alertes en une demi-journée, toutes venant d'un seul client, et exactement sur les domaines dont les jeux de réponses divergent. C'est cette coïncidence qui désigne le DNS plutôt que le proxy.
Un seul cache pour toutes les vues récursives
// portee globale
attach-cache "shared";
Toutes les vues récursives résolvent désormais dans le même cache : la réponse servie au client et celle servie au proxy sont la même entrée, pas deux résolutions concurrentes.
Une vue ne peut pas rejoindre le lot. La vue publique, autoritative, tourne sans récursion : ses paramètres de résolution diffèrent, et le résolveur refuse de mélanger — l'erreur est fatale au démarrage, pas silencieuse. Elle garde donc un cache privé, qui reste vide puisqu'elle ne résout rien.
view "internet" {
match-clients { any; };
recursion no;
attach-cache "internet";
};
Le partage impose une condition à retenir : les vues ne doivent différer que par leur sélection de clients, leurs listes d'accès et leurs zones locales. Tout ce qui touche à la résolution — forwarders, validation, dimensionnement du cache — doit rester global.
Le split-horizon n'est pas affecté
Contre-intuitif, mais net : les zones autoritatives sont des données, elles ne transitent jamais par le cache. Un cache partagé ne partage que les réponses récursives.
La passerelle continue donc de répondre différemment selon la vue du client, pendant qu'un nom externe devient identique partout. Les deux propriétés qu'on croyait opposées coexistent.
La vérification se fait depuis deux clients de vues distinctes : sur un nom externe, les deux listes d'adresses doivent être identiques ; sur le nom de la passerelle, elles doivent rester différentes. Un seul des deux contrôles ne prouve rien.
Effet de bord bienvenu : un seul cache au lieu d'une vingtaine — moins de mémoire, plus de succès de cache, moins de requêtes sortantes.
Zones et fichiers
| Type | Partage entre vues |
|---|---|
| zone interne | non, une par vue |
| zone publique | copies réelles, pas des liens |
| zones inverses | partagées par lien |
Les copies réelles pour la zone publique ne sont pas un détail : les mises à jour ACME modifient cette zone dans la vue publique, et un lien propagerait la modification à toutes les autres vues.
Génération automatique
Les zones sont générées à partir de l'inventaire. Déclarer un hôte avec son adresse et son masque suffit à faire apparaître ou disparaître la vue correspondante — il n'y a pas de fichier de zone tenu à la main.
Le nettoyage en début d'exécution supprime les fichiers orphelins quand un sous-réseau disparaît, sans quoi le résolveur continuerait de servir des zones que plus rien ne déclare.
Forwarders et récursion
forwarders { ... };
forward only;
allow-recursion { trusted; };
allow-query-cache { trusted; };
La récursion est restreinte aux clients de confiance ; la vue publique est purement autoritative et ne récursive pas. Un résolveur ouvert est un amplificateur d'attaque, pas seulement une fuite d'information.
Durcissement du service
- la version, le nom d'hôte et l'identifiant de serveur ne sont pas divulgués ;
- les réponses sont réduites au minimum nécessaire ;
- un plafond de débit s'applique aux clients hors du périmètre de confiance ;
- IPv6 est désactivé côté sortie, faute d'usage.