Set-OPS-Public/roles/serveur_dns_public/templates/dnsdist.conf.j2
Daniel Allaire 53cfe71c7e DNS public : dnsdist devant PowerDNS, debit et refus eprouves
ANY UDP tronque, AXFR/NOTIFY/UPDATE refuses depuis dehors, debit par /24 (rafale 300 ->
70 reponses, 230 tronquees). PowerDNS garde le 53 pour les primaires ; le frontal ecoute
1053, ou la frontiere redirigera. Sonde par le frontal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 22:29:19 -04:00

46 lines
3.2 KiB
Django/Jinja

-- GENERE par Set-OPS (role serveur_dns_public). Ne pas editer a la main.
--
-- LE FRONTAL PUBLIC DU SERVEUR DNS DU SITE — ce que l'Internet frappe, avant PowerDNS.
--
-- PowerDNS repond ; dnsdist decide QUI a droit a une reponse, et combien. Un autoritatif
-- expose sans frontal est un amplificateur offert : une requete de 60 octets, adresse source
-- usurpee, rend 1 a 3 Ko signes DNSSEC a la victime.
--
-- Chaque regle ci-dessous a ete eprouvee le 2026-09-16 sur cette machine, dans l'unite
-- `dnsdist.service`, avec le vrai PowerDNS derriere.
-- AUCUN APPEL VERS L'EDITEUR (meme raison que `security-poll-suffix=` de PowerDNS).
setSecurityPollSuffix("")
setLocal("{{ serveur_dns_public_ecoute }}:{{ serveur_dns_public_frontal_port }}")
-- TOUT LE MONDE PEUT DEMANDER : c'est un serveur de noms public. Ce qui est borne, c'est
-- ce qu'on demande (regles) et a quel rythme (debit).
setACL({"0.0.0.0/0", "::/0"})
-- LE SEUL AMONT : le PowerDNS de cette machine. Sonde par le SOA d'une zone qu'il DOIT servir
-- — un PowerDNS vivant mais vide serait declare en panne, et c'est juste.
newServer({address="{{ serveur_dns_public_ecoute }}:{{ serveur_dns_public_port }}", name="pdns",
checkName="{{ (serveur_dns_public_relations | map(attribute='zones') | flatten | first) | default('.') }}.",
checkType="SOA", mustResolve={{ 'true' if (serveur_dns_public_relations | map(attribute='zones') | flatten | length) > 0 else 'false' }}})
setServerPolicy(firstAvailable)
-- NI TRANSFERT, NI NOTIFICATION, NI MISE A JOUR DEPUIS DEHORS. Les NOTIFY des primaires
-- arrivent directement a PowerDNS, par le reseau du site ; par ici, ils seraient ceux de
-- n'importe qui. (Eprouve : NOTIFY -> REFUSED, AXFR -> « Transfer failed ».)
addAction(NotRule(OpcodeRule(DNSOpcode.Query)), RCodeAction(DNSRCode.REFUSED))
addAction(OrRule({QTypeRule(DNSQType.AXFR), QTypeRule(DNSQType.IXFR)}), RCodeAction(DNSRCode.REFUSED))
-- ANY EN UDP : TRONQUE (RFC 8482). La reponse ANY est la plus grosse qu'une zone puisse
-- rendre ; tronquee, elle ne pese que l'en-tete, et un vrai client repasse en TCP — ce qu'un
-- attaquant a adresse usurpee ne peut pas faire.
--
-- PIEGE DE MESURE (2026-09-16) : `dig` envoie ANY en TCP par defaut. Le premier essai a donc
-- « montre » une regle inoperante ; `dig +notcp` montre la troncature, et le compteur de la
-- regle le confirme.
addAction(AndRule({QTypeRule(DNSQType.ANY), NotRule(TCPRule(true))}), TCAction())
-- LE DEBIT, PAR /{{ serveur_dns_public_debit_masque_v4 }} (et /{{ serveur_dns_public_debit_masque_v6 }} en IPv6). En UDP, au-dela : reponse tronquee — le
-- client legitime passe en TCP, la victime d'une reflexion ne recoit qu'un en-tete. En TCP,
-- au-dela : abandon ; la poignee de main prouve deja l'adresse, le debit borne l'abus.
addAction(AndRule({MaxQPSIPRule({{ serveur_dns_public_debit_udp }}, {{ serveur_dns_public_debit_masque_v4 }}, {{ serveur_dns_public_debit_masque_v6 }}, {{ serveur_dns_public_debit_udp * 2 }}), NotRule(TCPRule(true))}), TCAction())
addAction(AndRule({MaxQPSIPRule({{ serveur_dns_public_debit_tcp }}, {{ serveur_dns_public_debit_masque_v4 }}, {{ serveur_dns_public_debit_masque_v6 }}, {{ serveur_dns_public_debit_tcp * 2 }}), TCPRule(true)}), DropAction())