Elke machine in een Active Directory-domein vindt zijn domeincontroller door DNS te vragen. Niet uit een ingestelde lijst. Hij vraagt om een SRV-record, en hij gaat waar het antwoord hem stuurt. Daarmee wordt een handvol records onder _msdcs het meest veiligheidskritische in de zone, en dringen zich twee vragen op die het waard zijn netjes te beantwoorden: hoe onderteken je ze, en wie mag ze schrijven. Geen van beide wordt vaak gesteld, en standaard worden ze allebei slecht beantwoord.
Dit bericht antwoordt op beide voor een Samba4-domeincontroller. BIND met dlz_bind9 dat de directory aanbiedt, inline ondertekenen, een verborgen primaire server waar geen enkele client ooit bij komt, een delegatie binnen een publieke zone zodat de vertrouwensketen tot de root loopt, en dynamische updates van clients ver weg van de locators.
Maar ondertekenen is alleen iets waard als je weet wat het beschermt, en daarvoor moet je een niveau lager beginnen — bij hoe een naam überhaupt wordt opgezocht. Het eerste derde deel hiervan is dus de wandeling omlaag vanaf de root. Heb je dat al onder de knie, spring dan naar de twee DNS-backends van Samba.
Een resolver begint met bijna niets
Het is de moeite om duidelijk te zijn over hoe weinig een resolver meekrijgt, want al het andere in dit bericht volgt daaruit.
Een net geïnstalleerde recursieve resolver weet twee dingen. Hij kent de adressen van de rootservers — een hints-bestand, dertien namen van a.root-servers.net tot m.root-servers.net, aangeboden via anycast vanaf veel meer instanties dan dertien. En als hij valideert, kent hij één publieke sleutel: die van de root.
Dat is de hele ingebouwde configuratie. Elk ander feit dat hij ooit zal aanbieden — welke servers autoritatief zijn voor uk, waar jouw domein woont, welk adres je mailserver heeft — leert hij tijdens het draaien door te vragen, en houdt hij daarna in cache tot de TTL verloopt.
Dat is een goed ontwerp. Niemand hoeft een lijst met naamservers van het internet mee te leveren, en geen centrale partij hoeft een wijziging aan jouw zone goed te keuren. Maar het heeft een gevolg, en daar gaat de rest van dit bericht over: een resolver gelooft wat hem wordt verteld, door welke server het vorige antwoord hem ook aanwees. Haal de signaturen weg en het hele bouwwerk is een keten van beweringen, elk alleen bekrachtigd door het feit dat hij van het adres kwam dat het vorige antwoord noemde. Dat is veel gewicht om aan een retouradres te hangen.
De wandeling omlaag door de boom
Een lookup is niet één vraag. Het is een reeks doorverwijzingen omlaag door de naamboom, en elke stap is een aparte query naar een andere server.
Stel dat een client dc01.ad.example.co.uk wil. Een resolver met een koude cache doet dit:
- Vraag een rootserver. De root kent het antwoord niet en doet ook niet alsof. Hij geeft een doorverwijzing terug: een lege antwoordsectie, en in de autoriteitssectie de
NS-records vooruk, met de adressen van die naamservers als glue in de aanvullende sectie. - Vraag een
uk-server. Weer een doorverwijzing, dit keer naar de naamservers voorexample.co.uk. - Vraag een
example.co.uk-server. Isad.example.co.ukgedelegeerd — en in het ontwerp verderop in dit bericht is dat zo — dan nog één doorverwijzing. - Vraag een
ad.example.co.uk-server. Die is autoritatief voor de naam, dus hij antwoordt met hetA-record en zet de AA-bit (authoritative answer).
Drie dingen over die wandeling doen later mee.
De doorverwijzing ís de delegatie. De ouder van een zone bevat de inhoud niet. Hij bevat NS-records die zeggen “vraag het daar”, en — zodra ondertekenen in beeld komt — een DS-record dat zegt “en dit is de vingerafdruk van de sleutel die je dan mag verwachten”. Delegatie is het enige structurele mechanisme dat DNS heeft, en het is waarmee je een interne zone intern houdt.
Bijna dit alles zit in de cache. De doorverwijzingen van de root en de TLD hebben lange TTL’s, dus een warme resolver springt direct naar stap drie of vier. Daarom is de cache van een resolver het aanvallen waard: vergiftig één ingang en je hebt alles daaronder omgeleid, zo lang als de TTL die jij hebt gekozen.
De volledige naam gaat niet naar elke server. Onder QNAME-minimalisatie vraagt een resolver de root alleen naar uk, niet naar dc01.ad.example.co.uk. Goed om te weten als je ooit je interne namen gaat zoeken in een querylog stroomopwaarts. Daar horen ze niet te staan.
Stub, recursief, autoritatief
Drie woorden die door elkaar worden gebruikt en heel verschillende taken betekenen. Het onderscheid draagt gewicht in de Active Directory-helft van dit bericht, dus het is de moeite om het vast te leggen.
| Wat het doet | Loopt de boom af? | Bevat zonedata? | |
|---|---|---|---|
| Stubresolver | De bibliotheek of lokale dienst die je applicaties aanroepen. Vraagt één ingestelde server en neemt het antwoord aan. | Nee | Nee |
| Forwarder | Geeft queries door aan een andere resolver, bewaart de antwoorden. | Nee | Nee |
| Recursieve resolver | Doet de wandeling hierboven, bewaart elke stap, valideert eventueel signaturen. | Ja | Nee |
| Autoritatieve server | Antwoordt voor de zones die hij heeft gekregen, en alleen die. Zegt “ik weet het niet” over al het andere. | Nee | Ja |
De belangrijke regel is de laatste. Een autoritatieve server heeft niets te zoeken bij recursie, en een recursieve resolver heeft niets te zoeken in autoritatief zijn. Ze combineren is hoe je een machine krijgt die zowel willekeurige vragen van clients aanneemt als data bevat waar die clients van afhankelijk zijn — en dat is precies de machine die je domeincontroller niet moet zijn.
Op een moderne Linux-desktop zit er nog een laag waar je van moet weten. systemd-resolved draait een stublistener op het loopbackadres en schrijft een resolv.conf die naar zichzelf wijst, met options edns0 trust-ad. Die trust-ad is degene om op te merken: hij zegt de stub dat hij de AD-bit (authenticated data) in antwoorden van die server moet geloven. De AD-bit is op zichzelf geen bewijs van iets — het is de bewering van de resolver stroomopwaarts dat hij heeft gevalideerd. Dat vertrouwen is redelijk als die resolver van jou is en de hop ernaartoe betrouwbaar, en anders betekent het niets.
Hoe diensten echt worden gevonden
Een A-record antwoordt op één vraag: welk adres heeft deze naam. Het zegt niets over op welke poort de dienst zit, welke van meerdere servers de voorkeur heeft, of wat je moet doen als de eerste eruit ligt.
SRV-records antwoorden op alle drie. Uit RFC 2782 is de vorm:
_service._proto.name. TTL IN SRV priority weight port target
Elk veld verdient zijn plek:
- De onderstrepingsvoorvoegsels houden de dienstlabels in een eigen naamruimte.
_tcpkan nooit botsen met een host die echttcpheet, want een hostnaam mag niet met een onderstreping beginnen. - Priority is de failovervolgorde — het laagste wordt eerst geprobeerd. Servers met een hoger prioriteitsnummer worden pas gebruikt als alles daaronder onbereikbaar is.
- Weight verdeelt de last binnen één prioriteit. Een paar met gewicht 100 en gewicht 300 krijgt ruwweg een kwart en driekwart van de clients. Het is een verhoudingsgewijze trekking, geen round-robin.
- Port maakt de dienst los van een bekend nummer, en zo vindt een client een LDAP-server op 3268 zonder dat iemand dat vastspijkert.
- Target moet een naam met
A- ofAAAA-records zijn. RFC 2782 is er duidelijk over dat het geen CNAME mag zijn, en dat één doel van.betekent “deze dienst wordt hier met opzet niet aangeboden” — iets wat het waard kan zijn met opzet te publiceren.
Dit is wat een domein vindbaar maakt in plaats van ingesteld. Een machine in het domein heeft geen lijst met jouw domeincontrollers. Hij heeft een domeinnaam, en hij vraagt.
De namen die Active Directory publiceert
Een AD-domein is, van de client bekeken, vooral een set SRV-records. De boom onder _msdcs is het interessante deel, want zo onderscheiden clients “een LDAP-server” van “een domeincontroller voor dit domein” van “een global catalogue voor dit forest”:
| Naam | Wat ernaar vraagt |
|---|---|
_ldap._tcp.<domain> | Alles dat LDAP in het domein wil |
_ldap._tcp.dc._msdcs.<domain> | Domeincontroller vinden — de belangrijkste |
_ldap._tcp.pdc._msdcs.<domain> | Specifiek de PDC-emulator |
_ldap._tcp.gc._msdcs.<forest> | Global catalogue |
_kerberos._tcp.<domain>, _kerberos._udp.<domain> | KDC vinden, voordat er een ticket bestaat |
_kpasswd._tcp.<domain>, _kpasswd._udp.<domain> | Wachtwoordwijzigingen |
_ldap._tcp.<site>._sites.dc._msdcs.<domain> | Site-bewust vinden — een DC die dichtbij is |
Kijk waar die lijst voor is. Kerberos-authenticatie kan niet beginnen voordat de client een KDC heeft gevonden, en die vindt hij via DNS. Site-bewust vinden betekent dat het antwoord ook bepaalt tegen welk datacenter een client zich authenticeert.
Hetzelfde idee, gemoderniseerd
SRV heeft een opvolger waar je van moet weten. SVCB- en HTTPS-records (RFC 9460) veralgemenen het patroon: dienstparameters in DNS, inclusief ALPN, poort en adreshints, in één record. Zo leert een browser direct naar HTTP/3 te gaan zonder omleiding. Het HTTPS-record is al breed uitgerold. Het mechanisme is in de geest hetzelfde als SRV: de client krijgt van DNS te horen hoe hij de dienst moet bereiken. En dus geldt het argument in de volgende sectie er net zo goed voor.
Alles na de lookup vertrouwt de lookup
Hier is het kantelpunt, en het is de reden dat een DNS-bericht een veiligheidssectie heeft en niet andersom.
Een machine in het domein start op en vraagt waar een domeincontroller is. Hij krijgt een naam en een poort. Hij verbindt daar, en dan begint hij aan alle dingen die we normaal als de veiligheidslaag zien: Kerberos, LDAP-signing, channel binding, certificaatvalidatie.
Wat gebeurt er dus als het antwoord een leugen was?
Eerlijk zijn hierover doet ertoe, want het antwoord is niet “onmiddellijke catastrofe”. Kerberos is juist met wederzijdse authenticatie ontworpen zodat een client niet aan de genade van zijn naamlookup is: een host die geen serviceticket kan overleggen voor de naam die de client vroeg, kan de uitwisseling niet afronden. Krijg een SRV-antwoord dat naar een machine zonder sleutel in het domein wijst en, voor een streng ingestelde client, mislukt het.
De realistische schade is subtieler, en zit helemaal in de kieren daarrond:
- Downgrade. Een client die terugvalt op NTLM als Kerberos niet werkt, is net overgedragen aan wie er ook antwoordde.
- Relay en dwang. De aanvaller hoeft de DC niet te zijn. De naam zijn waarmee een client verbindt is genoeg om die authenticatie te gaan doorsluizen naar ergens waar hij nuttig is.
- Uitval die op een storing lijkt. Laat
_ldap._tcp.dc._msdcsnaar iets wijzen dat niet antwoordt en het domein is met horten en stoten kapot, op een manier die niemand een hele tijd als DNS herkent. - Alles zonder enige wederzijdse authenticatie. Tijdsynchronisatie, syslog, monitoring, back-upagents, dat interne HTTP-dingetje met
verify=False. Genoeg omgevingen hebben daar meer van dan ze willen toegeven.
Het algemene beginsel is degene om mee te nemen: DNS is een mechanisme om te vinden, niet om te machtigen. Een dienst op naam vinden is volstrekt redelijk. Iets toekennen op de kracht van een naam is niet redelijk, en het aantal systemen dat dat stilletjes wel doet — toegangslijsten op basis van PTR, ACL’s op hostnaam, “het staat op het interne netwerk dus het zal wel van ons zijn” — is het echte aanvalsoppervlak.
Wat DNSSEC oplost, en wat niet
DNSSEC bestaat om één vraag te antwoorden: kwam dit antwoord echt van de zone die de naam bezit, ongewijzigd?
Het werkt (RFC 4033 en de twee die erop volgen) door te ondertekenen, en door de signaturen te ketenen aan iets dat je al vertrouwt:
- Elke RRset in een ondertekende zone heeft een
RRSIG— een signatuur over die set. - De publieke sleutels van de zone worden gepubliceerd als
DNSKEY. - De ouderzone publiceert een
DS-record: een hash van de sleutel van het kind. - Die
DSis zelf ondertekend door de ouder, wiens sleutel gehasht is in deDSvan zijn ouder, helemaal omhoog tot de root — en de sleutel van de root is het enige dat je resolver van geboorte af kende.
Ontkenning wordt ook ondertekend, wat makkelijk te vergeten is en meer uitmaakt dan het klinkt. Zonder dat is “die naam bestaat niet” een onbekrachtigd antwoord dat een aanvaller kan verzinnen om iets te laten verdwijnen. NSEC en NSEC3 geven een bekrachtigd “er bestaat niets tussen deze twee namen”.
Dat is een echte oplossing voor een echt probleem. Cachevergiftiging is niet theoretisch: de Kaminsky-aanval maakte spoofing van buiten het pad goedkoop genoeg om over het hele internet noodpatches af te dwingen, en de maatregelen die volgden — willekeurige bronpoorten, hoofdlettermenging met 0x20 — zijn allemaal pogingen om raden moeilijker te maken, niet om antwoorden verifieerbaar te maken. Werk aan zijkanalen heeft er sindsdien telkens weer stukjes van afgeknabbeld. Signaturen zijn het enige in dat lijstje dat het spel verandert in plaats van de prijs opdrijft.
Nu de eerlijke grenzen, want DNSSEC wordt in beide richtingen overdreven.
Het is geen vertrouwelijkheid. Ondertekenen is authenticatie met publieke sleutels; elke query en elk antwoord staat nog steeds in leesbare tekst op de draad. Privacy op de hop naar de client is DoT, DoH of DoQ, en dat is een ander mechanisme dat een ander probleem oplost. De hop naar een resolver versleutelen die niet valideert, koopt je een privégesprek met iets waartegen nog steeds gelogen kan worden.
Het valideert geen betekenis, alleen herkomst. DNSSEC bewijst dat de eigenaar van de zone dit heeft gepubliceerd. Is het record verkeerd, of kwaadaardig, of geschreven door iemand die de zone onverstandig heeft laten schrijven, dan wordt de signatuur daar net zo trouw op gezet. Een zone ondertekenen die onbetrouwde machines mogen schrijven maakt de inhoud niet betrouwbaar — het bekrachtigt de leugen. Houd die zin vast; het laatste derde deel van dit bericht is in wezen het gevolg ervan.
Het helpt alleen als iemand valideert. Valideert je resolver niet, dan zijn signaturen op de zones die je opvraagt versiering. En gebeurt de validatie op een resolver aan de andere kant van het netwerk, dan vertrouwt de client op de AD-bit en op het pad — zie trust-ad hierboven.
En het moet beheerd worden. Een verlopen signatuur is geen slechter antwoord, het is SERVFAIL — de naam gaat op zwart. Een DS bij de ouder die niet meer bij de sleutel van het kind past doet hetzelfde. Dat is de eerlijke prijs, en daarmee doet de automatisering in de volgende secties meer ter zake dan het eerste ondertekenen.
Waarom een verzonnen interne TLD niet ondertekend kan worden
Hier komen naamkeuzes van jaren terug met een rekening, en het is de moeite dat uit te spellen, want het is de reden dat het ontwerp in dit bericht een echt, eigen domein gebruikt voor interne namen.
Kijk nog eens hoe de keten wordt gebouwd: voor de sleutel van een zone staat een DS-record bij de ouder in. Een validerende resolver kan een interne zone dus alleen bekrachtigen als die zone een ouder heeft die een DS ervoor wil en kan publiceren.
ad.corp.local heeft zo’n ouder niet. .lan, .home of wat er ook verzonnen is op de dag dat het domein werd ingericht, evenmin:
.localis voorbehouden aan mDNS. Het voor unicast-DNS gebruiken is niet alleen onondertekend, het is een gedocumenteerde botsing met hoe elk besturingssysteem in het gebouw zich mág gedragen. Het krijgt hieronder zijn eigen sectie, want het speelt in een andere klasse dan de andere drie..lan,.corp,.homezijn niet-geregistreerde tekenreeksen. Er is geen ouder om eenDSte bevatten, en de aangevraagde versies zijn niet gedelegeerd vanwege de rommel met naambotsingen in private omgevingen.home.arpais netjes voorbehouden aan thuisnetwerken, wat ideaal klinkt — maar de delegatie is met opzet onbeveiligd. Er is geen ondertekend pad naartoe, en dat is zo bedoeld..internalis door ICANN precies voor dit gebruik apart gezet, en dat lost het botsingsprobleem op. Dit probleem lost het niet op: geen delegatie betekent geenDS, dus geen keten.
Je opties bij een interne zone zonder keten zijn: onondertekend laten, of op elke validerende resolver in de omgeving een lokaal vertrouwensanker instellen en de root van je eigen private eiland worden — een sleutel die je nu met de hand moet uitdelen, bewaken en rollen, op elke resolver, voor altijd, met SERVFAIL over het hele domein als het faalgedrag.
Er is een veel makkelijker antwoord, en het is gratis: maak van de interne zone een delegatie binnen een publieke zone die je al bezit en al ondertekent. ad.example.com, gedelegeerd vanuit example.com. De DS gaat bij de publieke ouder. Elke validerende resolver in de omgeving bekrachtigt interne namen dan met het vertrouwensanker dat hij al heeft, en jij deelt niets uit.
De inhoud blijft binnen. Alleen het vertrouwen komt van buiten. Dat onderscheid is het ontwerp in de rest van dit bericht.
.local is geen stijlkeuze
Een van die vier verdient zijn eigen sectie, want het is degene die mensen verdedigen, en omdat de verdediging altijd dezelfde is: het werkt, we gebruiken het al jaren, wat is het probleem.
Het probleem is dat .local geen niet-geregistreerde tekenreeks is die iemand ooit misschien verkoopt. Het is een naamruimte die al een eigenaar heeft en een vastgelegd gedrag, en dat gedrag is niet “vraag het de DNS-server”. RFC 6762 §3 is er niet zachtzinnig over:
Any DNS query for a name ending with “.local.” MUST be sent to the mDNS IPv4 link-local multicast address 224.0.0.251 (or its IPv6 equivalent FF02::FB).
Lees waar die MUST echt over gaat. Het is geen bewering over wie de tekenreeks bezit. Het is een instructie over waar de query naartoe gaat — en het antwoord is een multicastgroep op de lokale link, niet je domeincontroller. Dezelfde sectie zegt dat namen onder .local “meaningful only on the link where they originate” zijn, het DNS-equivalent van een 169.254-adres.
Een client die de specificatie correct volgt zal je DNS-server dus nooit om een .local-naam vragen. Hij roept over de draad en neemt wat er antwoordt.
Dat levert een set storingen op met een heel eigen smaak:
- Het opzoeken hangt van de client af, niet van je DNS. macOS lost
.localvia Bonjour op en heeft dat altijd gedaan;systemd-resolvedstuurt het domeinlocalstandaard naar mDNS; Windows doet mDNS sinds Windows 10. Drie stacks, drie sets regels, en geen ervan raadpleegt jouw zonebestand. - Het stopt bij de eerste router. mDNS is link-lokaal van opzet. Een naam die het aan een bureau op hetzelfde VLAN doet, doet het niet van een andere verdieping, een andere locatie of de VPN — en dat is het ticket “werkt op kantoor, kapot thuis” dat vier keer als “netwerkprobleem” wordt afgesloten voordat iemand de specificatie leest.
- Het gedrag verandert onder je voeten. Of een machine eerst multicast probeert, eerst unicast, of beide tegelijk, hangt af van de resolverstack en de versie ervan. Omgevingen die “al jaren prima” op
.localliepen, zijn meestal omgevingen waar een distro-update de volgorde nog niet heeft veranderd. - Het bewijs ontbreekt precies waar je het zoekt. Het querylog van de DC laat niets zien, want er kwam niets aan. Mensen zijn dagen op de server bezig, en de query heeft de client nooit verlaten.
- En het kan niet ondertekend worden, en dat is het punt van deze sectie. Geen ouder, geen
DS, geen keten, geen manier om er een te publiceren.
Zet dat nu onder een Active Directory-domein. De realm wordt afgeleid van de domeinnaam. De serviceprincipals worden afgeleid van de realm. De _msdcs-locators — de records waar dit hele bericht over gaat — zitten onder een achtervoegsel dat een client volgens de specificatie moet oplossen door op het lokale segment te roepen. Je bouwt Kerberos boven op een naamruimte die de helft van je omgeving oplost met een protocol dat is ontworpen om printers te vinden.
En de uitgang is duur, en dat maakt de oorspronkelijke keuze het waard om niet sentimenteel over te doen. Op Samba bestaat geen hernoemen op de plek. Het gedocumenteerde pad is samba-tool domain backup rename gevolgd door een restore: je neemt een hernoemde kopie van de database, zaait daaruit een nieuwe DC, en voegt elke andere DC vanaf nul opnieuw toe, waarbij DC’s met de oude en de nieuwe naam niet naast elkaar mogen bestaan. Het is niet rendom van Windows, dat de DC’s tenminste één voor één afwerkt. Het is een herbouw met een nettere naam.
Dus de eerlijke samenvatting. .local voor een unicast-domein is geen voorkeur, geen conventie, en geen onschuldig stukje erfenis. Het is een gedocumenteerd conflict met een protocol dat aanstaat op elk besturingssysteem in het gebouw, en de rekening komt jaren later als storingen in het opzoeken die met horten en stoten komen en niet te reproduceren zijn — en, als je de zone eindelijk wilt ondertekenen, als een herbouw van het domein.
Dezelfde fout, met een andere pet op
Nu we er toch zijn: poort 5353.
mDNS luistert op UDP 5353, en dat is geen losse poort die toevallig vrij is. Een gewone unicast-DNS-dienst erop zetten, of resolvers naar :5353 laten wijzen omdat 53 bezet was of root nodig had, doet dezelfde schade van de andere kant — elke machine op dat segment die mDNS kent praat nu met je naamdienst, en je naamdienst zit nu multicast service discovery af te handelen die hij nooit had moeten antwoorden. De naam en de poort zijn twee helften van één naamruimte, en beide zijn al van iemand anders.
.local op 53, of unicast-DNS op 5353. Hetzelfde misverstand, dezelfde klasse van storingen met horten en stoten, dezelfde weken van iemand anders zijn tijd.
En wees eerlijk over wat dat je vertelt
Microsoft raadde .local aan in het tijdperk van Small Business Server, en daarom zitten zoveel omgevingen er nog mee, en is er al heel lang mee gestopt. Een .local-domein erven is pech. De meesten die dit lezen en er een hebben, hebben het niet gekozen, en de sectie hierboven is een migratieplan en geen aanklacht.
Er een uitrollen is een heel andere zaak, en daar ga ik bot over zijn, want dit verzachten heeft nog nooit iemand geholpen.
Wie .local uitrolt voor Active Directory of voor gewone unicast-DNS, of een DNS-dienst op poort 5353 zet, is geen IT-professional. Ze hebben misschien de functietitel. Ze doen het werk niet. RFC 6762 is sinds 2013 gepubliceerd, in twintig minuten te lezen, en de zin die de hele kwestie beslecht staat in sectie 3. De naamdienst van een bedrijf bouwen op een naamruimte die de specificatie voorbehoudt aan link-lokale multicast is geen verdedigbare technische keuze. Het is iemand die gokt in juist dat deel van de stack waar gokken storingen oplevert die niemand kan reproduceren en iedereen op het netwerk schuift.
Ze horen uit je IT-afdeling verwijderd te worden. Niet zijwaarts geschoven, niet DNS onder toezicht laten beheren. Uit de functie verwijderd. De rol bestaat om te weten welk pakket waarheen gaat en op wiens gezag. Wie het document niet heeft gelezen dat de naamruimte regelt die ze voor elke machine in het gebouw hebben gekozen, vervult die rol niet, en ze erin houden betekent dat de volgende keuze van deze omvang op dezelfde manier wordt gemaakt.
En elk systeem dat ze hebben aangeraakt hoort doorgelicht. Dit is het deel dat mensen overslaan, en het deel dat het meest uitmaakt. Zo’n keuze staat nooit op zichzelf. Wie RFC 6762 niet nakeek voordat het domein een naam kreeg, heeft ook niets anders nagekeken — ga dus kijken naar wat ze verder hebben gebouwd. Verwacht dit te vinden:
- DNS-servers open naar de wereld, forwarders die iedereen antwoorden die vraagt, en nergens ACL’s.
- Dynamische updates wagenwijd open, en zonecontainers met rechten die niemand meer heeft bekeken sinds het domein werd aangemaakt.
- Certificaten en Kerberos gebouwd op namen die nooit consistent zouden oplossen, met de storingen weggewerkt met hosts-bestanden.
- Hosts-bestanden. Overal. Hele omgevingen zijn er precies door bijeengehouden, omdat
.localnooit goed werkte en iemand een omweg vond in plaats van een oorzaak. - Firewallregels en serviceaccounts aangemaakt om de symptomen te laten verdwijnen, nog steeds actief, en nog steeds meer toestaand dan iemand zich nu herinnert.
Dat is geen wraakzucht. Dat is waar een signaal over vakbekwaamheid voor is. Vind je één keuze die is gemaakt zonder de specificatie te lezen, dan is het juiste antwoord aannemen dat de rest op dezelfde manier is gemaakt en gaan kijken — want dezelfde persoon heeft je authenticatie, je certificaten en je toegangsbeheer ingericht, en je hebt nu direct bewijs van hoe ze een probleem aanpakken dat ze niet helemaal begrijpen.
Deze rommel erven kost je een migratie. De persoon in dienst houden die hem nog steeds maakt, kost je aanzienlijk meer.
De twee DNS-backends van Samba
Een Samba-AD-domeincontroller bewaart zijn DNS-data in de directory zelf, en er zijn twee manieren om die aan te bieden.
SAMBA_INTERNAL is Samba’s eigen DNS-server, ingebouwd in de AD-DC. Hij handelt de AD-zones en de dynamische updates met Kerberos-authenticatie af, en geeft al het andere aan een forwarder. Samba beschrijft hem als ondersteuning voor “the basic feature required in an AD” en raadt hem aan “for simple DNS setups”, wat eerlijk is en het waard is letterlijk te nemen. Hij krijgt hieronder zijn eigen sectie, want wat hij niet doet is langer en interessanter dan wat hij doet.
BIND9_DLZ draait BIND als DNS-server, met Samba’s module dlz_bind9 erin geladen. DLZ — Dynamically Loadable Zones — is een interface van BIND om een zone door iets anders dan een zonebestand te laten dekken. De module antwoordt de queries van BIND rechtstreeks uit sam.ldb, dus er is geen kopie, geen exportstap, en geen synchronisatie die verkeerd kan gaan: BIND leest de directory terwijl hij aanbiedt.
De interne DNS-server doet geen recursie — dat kan hij niet
Begin met het ding dat vrijwel altijd verkeerd wordt beschreven, ook door mensen die het draaien.
“De DC is onze DNS-server, hij doet de recursie voor de clients” is niet wat er gebeurt, want de interne DNS-server kan helemaal geen recursie doen. Samba’s eigen functielijst zegt het onomwonden. De interne DNS ondersteunt niet:
- optreden als cachende resolver
- recursieve queries (maar hij kan doorsturen naar een andere recursieve DNS-naamserver)
- transactiesignaturen met gedeelde sleutel (TSIG)
- stubzones
- zonetransfers
- lastverdeling met round robin over DC’s
met scavenging en conditionele forwarders ook als niet-geïmplementeerd vermeld.
Lees die eerste twee samen, want dat is het hele verhaal. Hij kan niet opzoeken, en hij kan niet cachen. Wat dns forwarder je werkelijk levert is een doorgeefluik: een client vraagt de DC om windowsupdate.com, de DC vraagt een echte resolver, het antwoord komt via de DC terug, en dan vergeet de DC het volledig. De volgende client stelt dezelfde vraag en het hele ding gebeurt opnieuw.
Kijk terug naar de tabel eerder in dit bericht en zie wat dat is: een forwarder zonder de cache van een forwarder. Het heeft de kosten van de rol — een extra hop, een afhankelijkheid, iets dat eruit kan liggen — en geen van de baten.
Een DC met SAMBA_INTERNAL die de omgeving bedient is dus geen DNS-server in de betekenis die mensen bedoelen. Het is een proxy zonder cache voor een echte resolver, en je hebt hem op de machine gezet die je directory bevat.
Waarom dat een risico is en niet alleen inefficiënt
De inefficiëntie is makkelijk te zien: elke externe lookup in het gebouw wordt een rondje via de AD-DC, permanent, zonder cache om de scherpte eraf te halen. Op een stil domein merkt niemand het. En precies daarom blijft het bestaan.
Het veiligheidsargument is degene die het waard is te maken, en het heeft drie delen.
De listener zit in het verkeerde proces. De interne DNS is een servicetaak van de AD-DC zelf, draaiend met de rechten van de directory — niet een aparte daemon onder een eigen account zoals named. Het ding dat onbekrachtigde UDP ontleedt van alles dat poort 53 kan bereiken, draait dus binnen het proces dat LDAP en Kerberos aanbiedt en sam.ldb bezit. BIND heeft dertig jaar vijandige aandacht achter zich, een eigen gebruiker, en de gewoonte in een kooi te draaien, juist omdat een DNS-listener een ruwe buurt is. Samba’s interne server is een gemaksvoorziening die nu eenmaal in de kroonjuwelen woont.
Clients bedienen betekent bereikbaar zijn voor clients. Om de DNS-server van de omgeving te zijn, moet hij queries aannemen van elk werkstation, elke printer, elke laptop van een externe op het gast-VLAN dat iemand per ongeluk heeft doorgelust. Dat is een groot, permanent open, onbekrachtigd aanvalsoppervlak op de waardevolste host die je hebt, en je draait het om de kosten van een resolver te sparen die een Raspberry Pi zou kunnen huisvesten.
En een forwarder die open staat naar de wereld is het wapen van iemand anders. Een DC die doorstuurt voor alles dat vraagt, is een open forwarder. Zodra hij van buiten je netwerk bereikbaar is, wordt hij deelnemer aan reflectie en versterking, en dan komen het verkeer én de klachtmeldingen bij je domeincontroller aan. Er is geen ratelimiet om naar te grijpen, want de interne server heeft er geen.
Niets daarvan heeft een kwetsbaarheid in Samba nodig om een slecht idee te zijn. Het is een slecht idee op vorm alleen: het zet een onbekrachtigde, per-ongeluk-aan-internet-hangende, ontleedzware dienst in hetzelfde proces als je directory, om een taak te doen waarvan gedocumenteerd is dat hij die niet goed kan.
Hij valt eerder om, en neemt meer mee
Het is de moeite duidelijk te zijn over wat dit argument niet is. Het is niet de bewering dat Samba’s DNS-code meer fouten heeft dan die van BIND. BIND heeft een lange CVE-lijst, vooral omdat het de meest onderzochte DNS-implementatie is die er bestaat, en adviezen tellen zou een slechte manier zijn om ertussen te kiezen.
De vergelijking die uitmaakt is structureel, en komt neer op drie vragen met drie ongemakkelijke antwoorden.
Wie kan hem een misvormd pakket sturen? Met SAMBA_INTERNAL die de omgeving bedient: elk werkstation, elke telefoon op de wifi, alles dat naar poort 53 op die bak kan routeren. Met het ontwerp in dit bericht: de ondertekenaar. Één host, één TSIG-sleutel, allow-query daartoe beperkt. Dat is geen klein verschil in gradatie. Het is het verschil tussen een blootgestelde dienst en een die in de praktijk onbereikbaar is, en het overschaduwt elk verschil in codekwaliteit tussen de twee implementaties.
Wat valt er om als hij omvalt? Dit is degene die de ernst bepaalt. named is een aparte daemon onder een eigen account; gaat hij dood, dan stopt DNS en gaat de domeincontroller door met authenticeren. Samba’s interne DNS is een servicetaak binnen de AD-DC, dus alles dat hem vastzet, uitput of laat crashen gebeurt binnen het proces dat LDAP en Kerberos aanbiedt. Een DNS-probleem wordt een storing van de directory. En systemctl restart named kost een seconde, terwijl een DC herstarten een ander soort ochtend is.
Wat kun je eraan doen terwijl het gebeurt? BIND heeft ratelimieten op antwoorden, allow-query, allow-recursion, blackhole, beleid per view, en de mogelijkheid om clients helemaal niet te antwoorden. De interne server heeft dns forwarder en een logbestand. Begint er iets op te rammen, dan is er geen knop om aan te draaien.
Tel daar de ontbrekende cache bij op. Elke clientquery is een vers rondje naar buiten, dus een vloedgolf queries kost de DC een lookup stroomopwaarts per pakket in plaats van een cachetreffer — en het kost hem dat in hetzelfde proces dat Kerberos-tickets probeert uit te geven. Daarvoor heb je geen exploit nodig; je hebt een drukke ochtend nodig, een applicatie die zich misdraagt, of iemand die een scanner op het verkeerde VLAN richt. Houdt het aan, dan komt het naar boven als authenticatie die langzaam is en niemand die aan DNS denkt.
Dus ja — zelfs met DLZ in beeld is BIND de veiligere plek hiervoor. De DLZ-module geeft named inderdaad toegang tot Samba’s data, en dat is een echte overweging waar dit bericht al een punt van heeft gemaakt. Maar een crash van named is een DNS-storing en geen storing van de directory, en in dit ontwerp neemt die named überhaupt geen queries van de omgeving aan. Fouten zijn een feit van elke codebase. Straal van de explosie en bereikbaarheid zijn dingen die je kiest.
En hij kan het ontwerp in dit bericht niet bouwen
Er is een eenvoudiger, meer definitieve reden dat hij hier niet de backend is.
Geen zonetransfers. De pijplijn in de volgende sectie — verborgen primaire server, ondertekenaar, losstaande autoritatieve servers — begint met een AXFR uit de DC. SAMBA_INTERNAL heeft niets om mee te transferen. Ook geen TSIG, dus zelfs de authenticatie die zo’n transfer nodig zou hebben ontbreekt. En geen ondertekenen, en geen validatie van wat het ook doorstuurt.
Dus de eerlijke samenvatting van SAMBA_INTERNAL: het is de backend waarmee je op een zondagmiddag een AD-domein in een lab kunt neerzetten zonder BIND in te richten, en daar is hij heel goed in. Samba zegt “simple DNS setups” en meent dat. Het is geen resolver, het is nooit gebouwd om de DNS-dienst voor een omgeving te zijn, en op het moment dat je ondertekenen, transfers, views, ACL’s, caching of ratelimieten wilt, is het antwoord niet om hem bij te stellen. Die knoppen heeft hij niet. Het antwoord is BIND.
Draai je hem vandaag met elke client naar de DC gericht, dan is de oplossing niet urgent maar ook niet optioneel: geef de clients een echte validerende resolver, en zet dns forwarder op de DC naar die resolver, zodat de DC voor zijn eigen zones antwoordt en verder niets.
En dit is het deel waar ik duidelijk over wil zijn, want “gebruik geen DLZ” wordt herhaald alsof het een hardeningsregel is: DLZ is niet de blootstelling. Het is het onttrekkingsmechanisme. Wat uitmaakt is niet welke module in BIND is geladen — het is wie met die BIND mag praten, en wat er daarna met de zone gebeurt. Een BIND met DLZ erachter die alleen een transferverzoek van je ondertekenaar antwoordt, is in geen enkele interessante zin een aanvalsoppervlak. Een SAMBA_INTERNAL-DC die elke naamlookup van vierhonderd laptops afhandelt, is dat zeer zeker. Slechts een van die twee komt op hardeningslijsten voor, en het is niet degene die uitmaakt.
Twee dingen over DLZ zijn waar en het waard om rond te plannen in plaats van te vrezen:
- De module hangt aan de versie van BIND. Samba levert een aparte
.soper BIND-versie, ennamed.confnoemt er specifiek één. Een grote BIND-upgrade betekent dat de bijpassende module er moet staan, ofnamedstart niet. Het is een pakketafhankelijkheid om vooraf te testen, geen veiligheidseigenschap. namedheeft toegang nodig tot Samba’s data, en daarom houdt Samba een aparte map voor de stukken die BIND nodig heeft in plaats van hem de hele private map te geven. Die toekenning is bedoeld smal te zijn — het is de moeite na te kijken of dat op jouw DC’s nog zo is, want het is de ene plek waar DLZ wel verbreedt wat een compromittering vannamedzou bereiken.
De reden dat DLZ hier de juiste keuze is, is wat het mogelijk maakt: de DLZ-interface van BIND ondersteunt het opsommen van een hele zone, en dat is wat een zonetransfer uit een zone met DLZ erachter überhaupt mogelijk maakt. Die transfer is de eerste hop van de pijplijn, en het is BIND die hem doet, wat betekent dat de rest van de pijplijn gewone BIND-configuratie is in plaats van iets exotisch.
Er is één beperking die alles stroomafwaarts vormgeeft, en het is de moeite dat onomwonden te zeggen, want het is makkelijk het tegendeel aan te nemen. Een DLZ-zone kan zelf niet ondertekend worden. ISC is daar duidelijk over in de BIND ARM: DLZ “is unable to handle DNSSEC-signed data due to its limited API”. Je kunt geen dnssec-policy aan de dlz-verklaring hangen en klaar zijn.
Wat je wel kunt doen — en wat ISC in dezelfde adem voor DLZ voorstelt — is hem als verborgen primaire server draaien, met het ondertekenen gedaan door een gewone BIND-zone die de data binnenhaalt. Dat is de volgende sectie, en de beperking is de reden dat die de vorm heeft die hij heeft.
Het is de moeite te weten dat dit een beperking van Samba is en geen wet van Active Directory. De DNS-server van Microsoft doet sinds Windows Server 2012 online ondertekenen van dynamische, AD-geïntegreerde zones. De zone wordt op de plek ondertekend, de private sleutels repliceren via AD-replicatie zelf naar de Key Masters, en dynamische updates blijven werken. Op Windows is “sign the AD partitions” een echte optie en zit het antwoord op de bezwaren over veranderlijkheid ingebouwd. (Op Server 2008 R2 was dat niet zo: je kon een AD-geïntegreerde zone ondertekenen, maar niet een die dynamische updates aannam, en elke wijziging betekende met de hand opnieuw ondertekenen — en daar komt de folklore vandaan dat AD-zones niet te ondertekenen zijn.)
Samba heeft geen equivalent. Geen van beide backends ondertekent: de interne server heeft helemaal geen DNSSEC, en DLZ kan geen ondertekende data dragen. Op Samba is de pijplijn van transferen-en-ondertekenen dus niet één ontwerp uit meerdere. Het is de manier.
De publicatiepijplijn
Nu de vorm ervan. Vier rollen, en de DC staat achteraan waar niets bij hem kan.
named op de DC is of een aparte host. De losstaande autoritatieve servers zijn het enige dat clients ooit zien, en zij zijn secundair op de ondertekende zone.1. De domeincontroller — verborgen primaire server. BIND met dlz_bind9, autoritatief voor de AD-zones uit de directory. Recursie uit. Geen dienst naar clients. allow-transfer beperkt tot alleen de ondertekenaar, met TSIG. Van het netwerk bekeken biedt de DC helemaal geen DNS aan, en het enige dat hem ooit iets vraagt is de bak ernaast.
2. De ondertekenende instantie — een gewone BIND-zone die de data binnenhaalt. Hier wonen inline-signing en de dnssec-policy, op een zone met dezelfde naam die de getransfereerde kopie bevat. BIND houdt de onondertekende kopie die hij ontving en de ondertekende kopie die hij publiceert als aparte dingen, en ondertekent opnieuw zodra er nieuwe transfers aankomen. Omdat het een transfer is en geen gedeeld bestand, kan deze instantie op de DC zelf staan — een tweede named op zijn eigen adres — of op een aparte host, en de configuratie is in beide gevallen nagenoeg gelijk.
De afweging is precies wat hij lijkt: op de DC is één machine minder om te draaien, terwijl een aparte host de private sleutels weg houdt van de bak met de directory. Beide zijn verdedigbaar, en de keuze verandert niets anders in de pijplijn. Wat uitmaakt is dat ondertekenen één keer gebeurt, op een vastgelegd punt, onder een sleutelbeleid — het verschil tussen DNSSEC dat je beheert en DNSSEC dat om drie uur ’s nachts verloopt.
3. De autoritatieve servers — het enige dat clients zien. Gewone secundaire servers van de ondertekende zone. Ze bevatten geen sleutels, ondertekenen niets, en hebben geen pad naar de directory. Wordt er een gecompromitteerd, dan heeft de aanvaller een kopie van een zone en geen enkele mogelijkheid een nieuw record te verzinnen dat valideert.
4. De resolvers. Validerende recursieve resolvers voor de omgeving, die de interne zones conditioneel doorsturen naar die autoritatieve servers en voor al het andere de publieke boom aflopen. Dit is waar de resolv.conf van clients naar wijst.
Uit deze opstelling vallen twee eigenschappen die het waard zijn apart te noemen, want ze zijn het hele punt:
- De machine met de directory is niet bereikbaar voor de machines die hem gebruiken. Een domeincontroller is de host met de hoogste waarde in de omgeving. Hem een netwerkdienst naar clients geven — een die onbekrachtigde UDP van elk werkstation antwoordt — is een slechte ruil voor een dienst die andere machines kunnen doen.
- Ondertekenen gebeurt één keer, op een vastgelegd punt. Het gebruikelijke bezwaar tegen een AD-zone ondertekenen is de veranderlijkheid — dat de inhoud te vaak wijzigt om signaturen bij te laten blijven. Dat bezwaar is eigenlijk een bezwaar tegen de standaardindeling, niet tegen ondertekenen. Met de registratie van clients eruit gehaald, zoals de volgende sectie betoogt dat moet, wijzigt de AD-zone als een domeincontroller wordt gepromoveerd of gedegradeerd en ruwweg nooit anders. Een zone die alleen door DC’s wordt geschreven is een stabiele zone, en een stabiele zone is een onopvallend iets om te ondertekenen. Clients eruit houden is niet alleen een veiligheidsmaatregel; het is wat de zone stil genoeg maakt om netjes te ondertekenen.
Hoe het ondertekenen echt is aangesloten
De vorm hierboven is het belangrijke deel, maar “een gewone BIND-zone die de data binnenhaalt” verdient het getoond te worden in plaats van beschreven, want de eerste poging hiertoe loopt meestal vast op het proberen de DLZ direct te ondertekenen.
Op de DC blijft de DLZ-kant met opzet saai. Hij biedt de directory aan, hij geeft de zone aan precies één tegenpartij, en verder doet hij niets:
key "transfer-to-signer" {
algorithm hmac-sha256;
secret "...";
};
options {
recursion no;
allow-query { key transfer-to-signer; localhost; };
allow-transfer { key transfer-to-signer; };
notify no;
};
dlz "AD DNS Zone" {
database "dlopen /usr/lib64/samba/bind9/dlz_bind9_18.so";
};
Merk op dat de .so de grote BIND-versie in zijn naam draagt. Dat is de versiekoppeling uit de vorige sectie, concreet gemaakt — een BIND-upgrade heeft de bijpassende Samba-module nodig voordat named wil starten.
Aan de ondertekenende kant is de zone een gewone secundaire met de ondertekenopties eraan. Dit is het deel dat niet op de DLZ kan wonen:
dnssec-policy "ad-internal" {
keys {
ksk lifetime P365D algorithm ecdsa256;
zsk lifetime P90D algorithm ecdsa256;
};
};
zone "ad.example.com" {
type secondary;
primaries { 192.0.2.10 key transfer-to-signer; };
file "ad.example.com.axfr";
inline-signing yes;
dnssec-policy "ad-internal";
allow-transfer { key transfer-to-public; };
also-notify { 192.0.2.20; 192.0.2.21; };
};
Dat dit op een secundaire server werkt is het dragende detail, en de ARM zegt het rechtstreeks:
If
yes, BIND 9 maintains a separate signed version of the zone. An unsigned zone is transferred in or loaded from disk and the signed version of the zone is served with, possibly, a different serial number.
BIND houdt dus twee kopieën — de onondertekende die hij ontving, geschreven naar file, en de ondertekende die hij aanbiedt, ernaast geschreven met de extensie .signed. Er komt een transfer aan, de ondertekende versie wordt opnieuw opgebouwd, en de servers stroomafwaarts krijgen een notify, want op dit punt is het een volstrekt gewone zone. inline-signing yes is trouwens de standaard zodra er een dnssec-policy aan hangt; het staat hierboven uitgeschreven omdat een configuratie die zegt wat hij doet die regel waard is.
Sleutelrotatie komt met het beleid en niet met een cronjob. ZSK-rotaties hebben helemaal geen input nodig; KSK-rotaties hebben nodig dat de nieuwe DS bij de ouder aankomt, en dat is de CDS/CDNSKEY-automatisering van later in dit bericht. rndc dnssec -status ad.example.com vertelt je waar elke sleutel in zijn levensduur staat.
Of dat blok op de DC of op een eigen host draait, is een kwestie van naar welk adres primaries wijst. Op de DC is het een tweede named-instantie op een tweede adres, die van de eerste transfereert over de loopback of een beheerinterface. Dat is wat “BIND met DLZ kan de ondertekenaar zijn” in de praktijk betekent: dezelfde software, desgewenst dezelfde bak, maar het ondertekenen gebeurt op de getransfereerde kopie in plaats van op de DLZ-zone.
De ene bedrijfsmatige haak is notify — en die zit aan de DLZ-kant. De handleiding van ISC is er bot over: DLZ “has no built-in support for DNS notify”, dus secundaire servers worden niet automatisch op de hoogte gebracht van wijzigingen aan de zones in de database. Samba kan een record in de directory wijzigen en de DLZ-instantie heeft geen idee dat hij het iemand moet vertellen.
De eerste hop is dus een poll, geen push. De ondertekenaar verst op de SOA-timer van de zone die hij transfereert, en dat betekent:
- De doorlooptijd van een wijziging op de DC naar een ondertekend, gepubliceerd record wordt begrensd door dat verversingsinterval, niet door seconden. Promoveer een DC en zijn nieuwe
_msdcs-records verschijnen stroomafwaarts tot één verversing later. - Dat interval is de knop om aan te draaien als de vertraging uitmaakt. Het is een afweging tegen hoe vaak je de DLZ ondervraagd wil hebben, en dat brengt het andere ding dat ISC over DLZ zegt naar boven: hij doet database-lookups in real time zonder caching en is “not recommended for use on high-volume servers”.
- Die twee zijn allebei argumenten voor deze topologie en niet ertegen. De enige client die de DLZ-instantie ooit heeft is de ondertekenaar, die één keer per verversing vraagt. De werkelijke querylast van de omgeving landt op de losstaande autoritatieve servers, die op volle snelheid een gewoon ondertekend zonebestand aanbieden.
Alles stroomafwaarts van de ondertekenaar is conventioneel: de autoritatieve servers zijn secundair op de ondertekende zone, ze krijgen een echte notify, en ze raken de directory of een sleutel nooit aan.
Clients mogen de zone met de locators niet schrijven
Dit is de belangrijke, en het is een ontwerpregel en geen instelling.
Active Directory registreert records dynamisch. Een machine treedt toe en registreert zichzelf; een DC start en registreert de SRV-records die zijn diensten aankondigen. “Secure” dynamische update betekent dat die updates bekrachtigd zijn — de machine bewijst met zijn eigen inloggegevens dat hij is wie hij zegt, en er is een ACL per record zodat een machine over het algemeen alleen een record kan wijzigen dat hij zelf heeft aangemaakt.
Lees dat zorgvuldig, want de garantie is smaller dan hij eerst lijkt. Beveiligde dynamische update bekrachtigt wie er schrijft. Het beoordeelt niet wat het record betekent. En de set accounts die mag schrijven is veel breder dan mensen aannemen: in een standaard AD-geïntegreerde zone heeft de groep Authenticated Users Create All Child Objects op de zonecontainer in de directory, omdat ADIDNS elk record als een AD-object onder CN=MicrosoftDNS,DC=DomainDnsZones bewaart. Niet alleen machineaccounts — elk bekrachtigd account in het domein, inclusief dat van degene die vanmorgen de factuurbijlage heeft opengeklikt.
Dat levert twee faalvormen op in een zone met zowel clientrecords als dienstlocators:
Namen die nog niet bestaan zijn van niemand. ACL’s per record beschermen een record dat al een eigenaar heeft. Een naam die nooit is geregistreerd heeft geen object om een ACL op te handhaven, dus het eerste account dat hem aanmaakt krijgt hem. Dat is het mechanisme achter de hele familie ADIDNS-aanvallen, waarvan de scherpste een wildcard is: maak * aan en elke naam in de zone die niemand uitdrukkelijk heeft opgeëist — typefouten, uitgefaseerde hosts, wpad — komt bij de aanvaller uit. Bestaande records blijven onaangeroerd, en precies daarom valt het niet op.
De straal van de explosie omvat de locators. De records onder _msdcs zijn hoe elke client in het domein een domeincontroller en een KDC vindt. Het zijn de meest veiligheidskritische records die je hebt, en in een standaarduitrol zitten ze in dezelfde zone waar vierhonderd laptops naar schrijven zodra ze een DHCP-lease krijgen.
Dus de regel: de zone met de locators wordt geschreven door domeincontrollers, en door niets anders. Dynamische registratie van clients gaat ergens anders naartoe.
Ergens anders kan twee dingen zijn, en beide zijn goed:
- Een gedelegeerde subzone die elders wordt aangeboden. De AD-zone bevat een
NS-delegatie voor, zeg,dyn.ad.example.com, en de records landen op een aparte server die de updates aanneemt. De partities van Samba nemen dan überhaupt geen schrijfactie van een client aan. - Een aparte zone in Samba met zijn eigen update-ACL. Nog steeds in de directory, maar een eigen zone, dus een schrijfactie van een client heeft geen pad naar
_msdcsof naar de records van een DC zelf.
De eerste geeft de hardere scheiding; de tweede is minder om te draaien. Wat niet aan de regel voldoet is geen van die twee. Het is de standaard, waar de twee samenwonen. En zo lopen de meeste domeinen nog, omdat niemand het heeft gekozen en niemand er nog eens naar heeft gekeken.
En er is een tweede opbrengst, degene die dit terugbindt aan de pijplijn. Een zone die alleen domeincontrollers schrijven is een zone die bijna nooit wijzigt: een DC-promotie, een DC-degradatie, en verder stilte. Alle veranderlijkheid in een standaard AD-zone is clientregistratie. Haal die eruit en het bezwaar tegen het ondertekenen van de AD-partities gaat mee — er is geen stroom updates waar de signaturen achteraan moeten, dus ondertekenen is routine in plaats van een gevecht. De schrijfdiscipline en het ondertekenen zijn dezelfde keuze, twee keer bekeken.
Naast een van die twee is er een recht om naar te gaan kijken — en op een Microsoft-DC is het de directe oplossing. Omdat ADIDNS-records objecten in de directory zijn, komt het recht van een AD-ACL en niet van iets in het DNS-protocol: Authenticated Users met Create All Child Objects op de zonecontainer. Dat aanhalen is de schoonste maatregel tegen het probleem van niet-opgeëiste namen en wildcards, en in veel omgevingen kan het recht helemaal weg zodra je weet wat zich echt moet registreren. Samba’s AD-DNS bewaart zijn records op dezelfde manier in de directory, dus dezelfde vraag geldt — ga kijken wat jouw zonecontainer werkelijk toestaat.
Maar wacht even — zouden clients zich in 2026 nog wel moeten registreren?
Alles hierboven gaat ervan uit dat dynamische update door clients iets is dat je nodig hebt en veilig probeert te maken. Voordat je dat aanneemt, is het de moeite de vraag te stellen die niemand stelt, want het antwoord is veranderd sinds dit gedrag werd ontworpen.
Dynamische DNS-registratie is gebouwd voor een desktop. Een bak onder een bureau, één netwerkkabel, één adres dat hij jaren hield. In die wereld was een machine die zijn eigen naam registreerde netjes en in wezen waar.
Kijk nu naar wat een client in 2026 is. Hij wordt wakker op de wifi thuis. Hij komt naar kantoor en gaat op het bedrijfsnetwerk. Hij gaat in een dockingstation en pikt daarnaast een bedraad adres op. Iemand start de VPN en er verschijnt een tunneladapter met een derde adres. Ze gaan naar een koffiezaak, tetheren aan een telefoon, en de VPN komt terug op een vierde. Dat is één machine, één naam, en een half dozijn adressen op één werkdag — en standaard zal hij een flink aantal daarvan proberen te registreren.
En dus vult de zone zich met beweringen die eens waar waren.
- Windows registreert elke adapter die hij heeft, tenzij iemand langs is gegaan en per interface Register this connection’s addresses in DNS heeft uitgevinkt. Een gedockte laptop op de VPN is een machine met drie actieve adapters en een mening over alle drie.
- Meerdere A-records voor één naam is geen fouttoestand, het is het normale resultaat. Een lookup geeft ze allemaal terug, clients proberen ze in de volgorde die hen bevalt, en verbindingen naar die naam mislukken in verhouding tot hoeveel van die adressen dood zijn. Dit is het mechanisme achter “de helpdesk ziet de machine het ene moment en het volgende niet”.
- VPN-adressen zijn de ergste, want een tunneladres is een uur geldig en het record leeft langer. De tunnel valt weg, het adres uit de pool gaat naar iemand anders, en de naam wijst nu naar een collega.
- Dockingstations vertroebelen de identiteit zelf. Zonder MAC-doorgifte hoort de lease bij het dock en niet bij de laptop, dus in een omgeving met flexplekken drijven namen, leases en machines dagelijks van elkaar weg.
- En eigendom maakt het permanent. Een record kan alleen worden bijgewerkt door het account dat het heeft aangemaakt. Is een record door DHCP onder de ene inloggegevens gemaakt en probeert de machine het later onder zijn eigen bij te werken, dan mislukt de update, stil, en blijft het verouderde adres precies staan waar het stond.
Het opruimverhaal is ook niet de redding die het lijkt. Samba heeft scavenging sinds 4.9, maar het staat standaard uit (dns zone scavenging = yes, met samba-tool dns zoneoptions --aging=1), en Samba zegt zelf dat het “should only be enabled on new zones or new installations”, omdat oudere versies dynamische records als statisch en statische als dynamisch markeerden. In juist de omgevingen die het vaakst vol rommel zitten — die al jaren draaien — is het gereedschap om het op te ruimen datgene waarvan je wordt aangeraden het niet aan te zetten. Het heeft ook een eigen CVE gehad.
Stel de vraag dus rechtstreeks: wat gebruikt het A-record van een laptop eigenlijk?
In de meeste bedrijven bijzonder weinig. Gebruikers verbinden met servers; servers verbinden niet met laptops. De echte gebruikers zijn hulpmiddelen voor helpdesk op afstand, RDP naar een werkstation op naam, en inventarisatie of monitoring — en bijna al dat gereedschap houdt zijn eigen inventaris bij en werkt vanaf een agent die zich meldt, omdat het voor mobiele clients nooit op DNS kon leunen.
Wat suggereert de standaard om te draaien:
- Servers en infrastructuur krijgen records uit het inrichten. Met NetBox en Ansible al in beeld wordt het record gemaakt door hetzelfde dat de machine heeft gemaakt, is het van constructie juist, en verdwijnt het als de machine verdwijnt.
- Het stabiele bedrade deel kan records uit DHCP krijgen als iets ze echt nodig heeft, met één inloggegeven als eigenaar zodat updates niet mislukken.
- Mobiele clients registreren helemaal niets. Zij zijn gebruikers van DNS, geen publiceerders. Moet iets een laptop bereiken, dan heeft het een agent nodig en geen A-record.
Je komt op dezelfde plek uit als het veiligheidsargument je bracht, langs een volstrekt andere weg. Minder schrijvers betekent een kleiner ADIDNS-oppervlak, een zone die niet vol verlopen beweringen zit, en — terug naar de pijplijn — een zone die stil genoeg is om zonder nadenken te ondertekenen.
Het veiligheidsargument zegt dat clients de zone met de locators niet mogen schrijven. Het bedrijfsmatige argument vraagt waarom ze überhaupt DNS schrijven. In 2026, voor een vloot die vijf keer per dag van adres wisselt, is “dat doen ze niet” een volstrekt goed antwoord, en aanzienlijk minder werk dan hun rommel veilig maken.
Split views, en waar het vertrouwen vandaan komt
Het laatste stuk knoopt de twee helften van het bericht aan elkaar.
Er zijn twee views op de naamruimte. Een publieke zone, gepubliceerd naar het internet, met het handvol namen dat de wereld nodig heeft. En een interne view — de inhoud die uit AD komt, elke toegetreden host, elke dienstlocator, de sitetopologie — waar de wereld niets te zoeken heeft. Die inhoud is een kaart van de omgeving, en die hoort van buiten onbereikbaar en niet te transfereren te zijn.
Maar het vertrouwen voor de interne view komt van de publieke kant, en dat maakt dit ontwerp beter dan het gebruikelijke eiland van interne DNS:
example.comis publiek en ondertekend, met zijnDSbij de ouder en een keten naar de root.ad.example.comis daaruit gedelegeerd. De publieke ouder publiceert de delegatie en eenDSvoor de sleutel van de interne zone.- De interne autoritatieve servers bieden de ondertekende
ad.example.comaan. De interne resolvers valideren die — root →com→example.com→ad.example.com— met niets anders dan het vertrouwensanker van de root dat ze al hadden.
Geen lokaal vertrouwensanker. Geen eiland. Geen met de hand uitgedeelde sleutel. De data verlaat het gebouw nooit, en het validatiepad is het gewone publieke. Zet je er een resolver bij, dan valideert die interne namen correct met precies nul DNSSEC-configuratie.
Even eerlijk over de afweging, want er is er een. Een delegatie en een DS in de publieke zone publiceren betekent dat het bestaan van ad.example.com, en de namen van zijn naamservers, publiek zijn. De inhoud niet, en die wordt het ook nooit — maar je hebt de wereld verteld dat de zone bestaat. In ruil daarvoor valideert elke resolver die je hebt interne namen tegen de echte root. Dat is voor de meeste omgevingen een goede ruil, en het hoort een bewuste te zijn en geen verrassing.
Twee dingen om er goed naast te zetten:
- Houd de interne view niet op te sommen en niet te transfereren.
allow-transferop de interne autoritatieve servers is voor de ondertekenaar en je eigen secundaire servers, en verder niets. En bedenk dat bekrachtigde ontkenning metNSECiedereen die de zone wel kan ondervragen hem van begin tot eind laat aflopen;NSEC3maakt dat duurder, maar de echte maatregel is dat buitenstaanders de servers helemaal niet kunnen bereiken. - Automatiseer de
DS. EenDSbij de ouder die niet meer bij de sleutel van het kind past, trekt het hele interne domein naarSERVFAIL.CDS/CDNSKEYbestaan zodat het kind een sleutelwijziging kan aankondigen en de ouder die kan oppikken zonder dat een mens tijdens een rotatie een record zit te bewerken. Ondersteunt de registrar of provider van de ouderzone dat, gebruik het dan; en zo niet, dan moet de rotatieprocedure worden opgeschreven vóór de eerste rotatie, niet tijdens.
Windows en Linux echt laten valideren
Alles tot hier ging over een zone publiceren die verifieerbaar kan zijn. Niets daarvan doet iets tot er aan de clientkant iets op verifiëren staat. Een perfect ondertekende zone en een client die nooit een signatuur nakijkt leveren precies dezelfde ervaring als een niet-ondertekende zone, tot de dag dat dat niet zo is.
Er zijn maar twee plekken waar validatie kan gebeuren, en het verschil ertussen is het verschil tussen een veiligheidsmaatregel en een vriendelijke suggestie.
Valideer bij de resolver, en vertrouw de AD-bit. De client vraagt een resolver, de resolver doet de cryptografie, en meldt het resultaat door één bit te zetten — AD, authenticated data — in het antwoord. De client gelooft de bit. Dit is het model dat Windows gebruikt, en het is precies zo sterk als het pad tussen de client en de resolver, want alles dat als de resolver kan antwoorden kan die bit zetten.
Valideer op de client zelf. De machine draait zijn eigen validerende resolver, dus het “pad naar de resolver” is een loopbacksocket binnen de machine en er is niets meer om te spoofen. Dat is sterker, en op Linux is het volledig haalbaar.
De standaard is niets
Voordat je iets instelt, is het de moeite te zien wat een gangbaar Linux-werkstation uit de doos doet. Dit is een Fedora 44-machine, systemd 259, onaangeroerd:
$ resolvectl status | head -3
Global
Protocols: LLMNR=resolve -mDNS -DNSOverTLS DNSSEC=no/unsupported
resolv.conf mode: stub
$ grep options /etc/resolv.conf
options edns0 trust-ad
$ dig +dnssec cloudflare.com A | grep flags
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
Lees die drie samen, want ze vertellen een klein verhaal.
De stub is ingesteld met trust-ad — hem is gezegd de AD-bit te geloven. systemd-resolved meldt DNSSEC=no/unsupported, dus hij valideert zelf niets. En het antwoord voor een ondertekende zone komt terug met de vlaggen qr rd ra en geen ad — niets in dat hele pad beweerde te hebben gevalideerd.
Dat is geen verkeerde instelling. Dat is de standaard. Een client kan worden gezegd een bewering te vertrouwen die niets in de keten doet. Er kijkt niets naar. Even de moeite om bij stil te staan voordat je iets anders instelt.
Linux
Drie opties, in toenemende orde van hoe weinig je het netwerk hoeft te vertrouwen.
1. systemd-resolved, lokaal validerend. Een drop-in in plaats van het meegeleverde bestand bewerken:
# /etc/systemd/resolved.conf.d/dnssec.conf
[Resolve]
DNSSEC=yes
DNSOverTLS=opportunistic
Daarna systemctl restart systemd-resolved en met resolvectl status nakijken dat de regel nu DNSSEC=yes leest.
De instelling om voorzichtig mee te zijn is de middelste. DNSSEC=allow-downgrade ziet uit als een verstandig compromis en is geen veiligheidsmaatregel — resolved.conf(5) zegt het zelf:
Note that this mode makes DNSSEC validation vulnerable to “downgrade” attacks, where an attacker might be able to trigger a downgrade to non-DNSSEC mode by synthesizing a DNS response that suggests DNSSEC was not supported.
Een aanvaller die antwoorden kan verzinnen is precies de aanvaller waarvoor DNSSEC bestaat, dus een modus die ze kunnen uitzetten door een antwoord te verzinnen koopt je tegen hen niets. Het is yes, of het is versiering.
2. Een echte validerende resolver op de host. De validator van systemd-resolved is handig, niet grondig. Waar het uitmaakt, draai je Unbound of BIND op de loopback en laat je de stub daarnaar wijzen:
# unbound: validate against the root anchor, refuse to be stripped
server:
module-config: "validator iterator"
auto-trust-anchor-file: "/var/lib/unbound/root.key"
harden-dnssec-stripped: yes
val-permissive-mode: no
# the internal zone is reached like any other name — no local anchor needed
forward-zone:
name: "ad.example.com."
forward-addr: 192.0.2.53
Het equivalent bij BIND is één regel — dnssec-validation auto; — die zijn ingebouwde kopie van het rootanker gebruikt en de rotatie voor je beheert.
3. Voor de hele omgeving, op de resolvers die je al draait. Dat zijn stap vier van de pijplijn eerder in dit bericht. De validatie gebeurt daar, clients vertrouwen de AD-bit, en de hop ertussen is wat je moet beschermen — met DoT, of met een netwerk waarover je die aanname wilt doen.
En merk op wat in geen van die configuraties staat: een vertrouwensanker voor de interne zone. Omdat ad.example.com een delegatie binnen een publiek ondertekende zone is, valideert elk van deze interne namen via de gewone keten vanaf de root. Dat is het ontwerp uit de vorige sectie dat zich terugbetaalt. Het alternatief is een lokaal anker naar elke client en resolver in de omgeving duwen, en dat bij elke rotatie opnieuw duwen.
Windows
Eerst het belangrijkste, want het wordt routineus verkeerd begrepen: de DNS-client van Windows valideert geen DNSSEC. Hij doet geen cryptografie, kijkt geen signatuur na, en bevat geen vertrouwensanker. Het is een stubresolver, en dat is hij altijd geweest.
Wat je wel kunt doen is hem dwingen antwoorden te weigeren die niet door de server voor hem zijn gevalideerd. Dat is de Name Resolution Policy Table, en die werkt per naamruimte in plaats van overal:
# Require validated answers for the internal zone
Add-DnsClientNrptRule -Namespace ".ad.example.com" `
-DnsSecEnable -DnsSecValidationRequired
# What is actually in force on this machine, including from Group Policy
Get-DnsClientNrptPolicy -Effective
Get-DnsClientNrptRule
Voor de hele omgeving woont hetzelfde in Group Policy onder Computer Configuration → Policies → Windows Settings → Name Resolution Policy: maak een regel voor de naamruimte, vink de DNSSEC-optie aan, en vink de eis aan dat de client nakijkt dat de data door de DNS-server is gevalideerd.
Daar volgen twee dingen uit, en beide doen ter zake.
Iets stroomopwaarts moet nog steeds het valideren doen. De NRPT-regel laat de client de AD-bit eisen; hij maakt er geen. De resolver waar die clients naar wijzen moet een validerende resolver zijn, of elke naam in die naamruimte mislukt.
En daarom staan er IPsec-opties naast de NRPT-regel. Microsoft heeft die er gezet om de reden die aan het begin van deze sectie staat: een bit eisen die elke aanvaller op het pad kan zetten, is niet zo’n eis. Leun je op het model waarin de resolver valideert op een onbetrouwd netwerk, dan moet de laatste hop beschermd worden — IPsec tussen client en resolver, of DoT waar de resolver dat ondersteunt.
Het faalt dicht, dus rol het in die volgorde uit
Validatie afdwingen zet een klasse stille compromitteringen om in een klasse luide storingen. Dat is de juiste ruil, en het is nog steeds een storing: een verlopen RRSIG, een DS bij de ouder die na een rotatie niet meer past, of een resolver die de ouderzone niet kan bereiken leveren allemaal SERVFAIL, en SERVFAIL voor _ldap._tcp.dc._msdcs betekent dat het domein plat ligt in plaats van dat het minder goed werkt.
Doe het dus in deze volgorde:
- Zet validatie eerst aan op de resolvers, en laat de clients met rust. Kijk een paar weken naar
SERVFAILin de resolverlogs — daar vind je de zone die al een jaar stil kapot is. - Automatiseer de
DSvoordat je iets afdwingt, volgens de vorige sectie. De meeste zelf toegebrachte DNSSEC-storingen zijn een rotatie waarbij de ouder nooit is bijgewerkt. - Dwing daarna de clients, één naamruimte per keer, te beginnen met je eigen werkstation en een test-OU in plaats van de hele omgeving.
De storing waartegen je bouwt, is een client die een verzonnen domeincontroller in de handen wordt gedrukt. De storing die je riskeert, is een client die helemaal niets krijgt. De tweede is herstelbaar en zichtbaar; de eerste is geen van beide. Van de storing hoor je binnen een minuut. Van die ander had je nooit iets gehoord.
De laatste hop versleutelen: DoT en DoH op de interne resolvers
De sectie over validatie liet één ding hangen. In het model waarin de resolver valideert — dat wat Windows je geeft — vertrouwt de client op één bit die de resolver heeft gezet, en die bit is niets meer waard dan het pad waarover hij reisde. Iets moet dat pad beschermen.
BIND ondersteunt beide versleutelde transporten van zichzelf, dus dit is een klus van instellen en niet van inkopen:
- DNS over TLS — een
tls-blok waarlisten-onnaar verwijst, gewoonlijk op poort 853. - DNS over HTTPS — hetzelfde
tls-blok plus eenhttp-blok, op 443. - Uitgaande DoT, want
forwardersneemt een TLS-transport per adres of voor de hele lijst. - Zonetransfers over TLS, want de
primaries-verklaring van een zone mettype secondaryneemt er ook een — en dat is direct nuttig voor de pijplijn eerder in dit bericht.
Wees eerst duidelijk over wat dit oplevert, want DoT en DNSSEC worden voortdurend door elkaar gehaald en het zijn geen alternatieven. DNSSEC bekrachtigt de data, helemaal terug tot de zone die hem publiceerde. DoT beschermt het gesprek met de resolver. De een overleeft een vijandige resolver en een vijandig netwerk tussen resolvers; de ander belet dat de machine op je wifi meeleest en herschrijft wat je laptop vroeg. Je wilt ze allebei, en de een vervangt de ander niet. De hop naar een resolver versleutelen die niet valideert, is een privégesprek met iets waartegen nog steeds gelogen kan worden.
Het aanbieden
tls internal-resolver {
key-file "/etc/pki/dns/resolver.key";
cert-file "/etc/pki/dns/resolver.pem";
protocols { TLSv1.3; };
};
http internal-doh {
endpoints { "/dns-query"; };
};
options {
dnssec-validation auto;
listen-on port 53 { 192.0.2.53; };
listen-on port 853 tls internal-resolver { 192.0.2.53; };
listen-on port 443 tls internal-resolver
http internal-doh { 192.0.2.53; };
listen-on-v6 port 853 tls internal-resolver { 2001:db8::53; };
};
Het certificaat is het echte werk, en dat is het deel dat wordt overgeslagen. Een client die verifieert — en dat is het hele punt — heeft een certificaat nodig dat geldig is voor de naam waarmee hij is ingesteld, uitgegeven door iets dat hij al vertrouwt. Dat betekent je interne CA en je bestaande certificaatautomatisering, niet het sleutelwoord ephemeral. ephemeral maakt een wegwerpcertificaat dat zichzelf ondertekent; het staat er zodat je kunt aantonen dat de listener werkt, en het is waardeloos voor elke client die echt nakijkt.
Erover doorsturen
Sturen die resolvers ergens naartoe door in plaats van zelf de boom af te lopen, dan kan de hop stroomopwaarts ook versleuteld — en hier zit een onderscheid dat het waard is goed te doen:
tls upstream {
ca-file "/etc/pki/tls/certs/ca-bundle.crt";
remote-hostname "dns.example.net";
};
options {
forwarders port 853 tls upstream { 192.0.2.1; };
};
Zonder remote-hostname krijg je versleuteling zonder authenticatie: het verkeer is onleesbaar voor een passieve meekijker, en een actieve aanvaller die de verbinding kan onderscheppen presenteert simpelweg zijn eigen certificaat. Met remote-hostname en ca-file verifieert BIND met wie hij praat. Het eerste is iets waard. Alleen het tweede is het waard een maatregel te heten.
De clientkant is niet symmetrisch
Hier wordt een gemengde omgeving lastig, en dit is de reden om beide transporten in te richten in plaats van er een te kiezen.
Linux doet DoT netjes. systemd-resolved neemt DNSOverTLS=yes voor de strenge modus, en de server kan de naam mee krijgen om tegen te verifiëren:
[Resolve]
DNS=192.0.2.53#resolver.ad.example.com
DNSOverTLS=yes
DNSSEC=yes
Net als bij DNSSEC= is de middelste instelling de val: DNSOverTLS=opportunistic valt terug op leesbare tekst als TLS niet beschikbaar is, en dat kan een aanvaller die de verbinding kan storen zelf regelen.
Windows doet DoH, en geen DoT. Ondersteuning voor DoH aan clientkant kwam in Windows 11 en Server 2022, ingesteld per server met een template:
$doh = "https://resolver.ad.example.com/dns-query"
netsh dnsclient add encryption server=192.0.2.53 dohtemplate=$doh
DoT is op het moment van schrijven alleen in Insider-builds verschenen. Op uitgebrachte Windows-versies is de versleutelde optie dus DoH of niets, en precies daarom greep de NRPT-sectie eerder naar IPsec.
Vandaar beide aanbieden uit dezelfde BIND-instantie. DoT voor de Linux-vloot en alles wat het verder spreekt, DoH voor Windows, één resolver, één certificaat.
Welke, waar
Voor een interne resolver is DoT het betere transport en DoH het antwoord op compatibiliteit.
DoT zit op zijn eigen poort. Je kunt hem zien, toestaan, weigeren, en alarmeren op alles dat DNS doet zonder hem te gebruiken. Het voordeel van DoH — niet te onderscheiden van gewoon webverkeer op 443 — is een echt voordeel op een vijandig netwerk en een sta-in-de-weg op je eigen, waar kunnen zien wat DNS is een functie is waarvoor je hebt betaald. Op de omgeving die je zelf beheert kies je het transport dat je kunt zien, en draai je DoH omdat Windows je geen keus laat, niet omdat het beter is.
Nu je er toch bent: versleutel de transfers
De pijplijn eerder in dit bericht verplaatst de AD-zone met AXFR, en de sectie over split views maakte het punt dat de inhoud ervan een kaart van de omgeving is. TSIG bekrachtigt die transfers; hij verbergt ze niet. Omdat de primaries-verklaring van een secundaire server een TLS-configuratie aanneemt, kan de transfer ook over TLS — RFC 9103 als je de standaard wilt:
zone "ad.example.com" {
type secondary;
primaries { 192.0.2.10 port 853 tls xfr-tls key transfer-to-signer; };
...
};
Bekrachtigd door de sleutel, versleuteld door het transport. Steekt een hop in die pijplijn een verbinding tussen locaties over, een hypervisor die je deelt, of wat dan ook waar je niet vrolijk een hub aan zou hangen, dan is het de twintig minuten waard.
Wat het niet oplost
- Het is geen validatie. Hierboven behandeld, en het waard te herhalen omdat leveranciers “secure DNS” verkopen en daarmee alleen versleuteling bedoelen.
- Het verbergt niets voor de resolver. De resolver ziet elke query volledig. Versleuteling beschermt het pad, niet de privacy van de lookup tegenover de beheerder — en dat is prima als de beheerder jij bent.
- Het doet niets voor een client die het certificaat niet verifieert, en opportunistische modi zijn af te zwakken door precies de aanvaller waar je je zorgen over maakt.
- En het is geen reden om een listener op de domeincontroller te zetten. Versleutelde DNS op de DC zou het verkeerde probleem prachtig oplossen. De DC antwoordt nog steeds niemand.
Hoe je nakijkt wat je hebt
Commando’s om tegen je eigen omgeving te draaien. De uitvoer is het interessante deel, en een paar ervan lezen de eerste keer nogal ongemakkelijk.
# Walk the tree yourself, one delegation at a time
dig +trace dc01.ad.example.com
# Validate, and show the chain being built
delv +rtrace +vtrace ad.example.com SOA
# Is the resolver you are pointed at actually validating?
# A deliberately broken test name must come back SERVFAIL, not an address
dig @<resolver> dnssec-failed.org A
# What does the estate advertise as a domain controller?
dig SRV _ldap._tcp.dc._msdcs.<domain>
dig SRV _kerberos._udp.<domain>
# Is a DC answering for names it has no business answering?
# Ask it for something it is not authoritative for. "recursion requested
# but not available" is the answer you want. An actual address means it is
# serving the estate — recursing if it is BIND, relaying to the forwarder
# if it is SAMBA_INTERNAL. Either way it should not be doing that.
dig @<dc> www.example.org A
# Is the DC configured as the estate's DNS relay?
grep -E 'dns forwarder|server services' /etc/samba/smb.conf
# Will a DC hand its zone to anybody who asks?
dig @<dc> AXFR ad.example.com
# Which backend is this DC running, and does named have the module?
grep -r dlz /etc/named.conf /var/lib/samba/bind-dns/ 2>/dev/null
samba-tool dns query <dc> <domain> @ ALL
# Is the internal zone chained to the public parent?
dig DS ad.example.com @<public-authoritative-for-example.com>
# What does the local stub actually do with the AD bit, and is the
# hop to the resolver encrypted?
resolvectl status # DNSSEC= and DNSOverTLS= per link
grep options /etc/resolv.conf # trust-ad, trusting whom exactly?
# Did anything in the path claim to have validated? Look for "ad" in the flags
dig +dnssec ad.example.com SOA | grep flags
# Validate independently of whatever the local resolver believes
delv ad.example.com SOA # "fully validated" is the line you want
# Is the resolver actually listening for DoT, and does its certificate
# match the name clients are configured with?
kdig +tls @192.0.2.53 ad.example.com SOA
openssl s_client -connect 192.0.2.53:853 \
-servername resolver.ad.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -dates
En op een Windows-client, om te zien of hij überhaupt iets eist:
Get-DnsClientNrptPolicy -Effective # the rules actually in force
Resolve-DnsName ad.example.com -DnssecOk
Get-DnsClientDohServerAddress # is the hop to the resolver encrypted?
De twee die het vaakst een verrassing opleveren zijn de recursiecontrole en de poging tot AXFR. Antwoordt een DC een van die twee voor een willekeurige client, dan staat de pijplijn uit dit bericht er niet, wat het diagram op de wiki ook zegt.
De derde is resolvectl status op een machine die niemand heeft aangeraakt. DNSSEC=no/unsupported naast trust-ad in resolv.conf is de normale toestand van een Linux-desktop, en het betekent dat het ondertekenwerk dat hierboven staat beschreven op dit moment door niemand wordt nagekeken.
De korte versie
Een resolver wordt geboren met kennis van de rootservers en één sleutel, en leert al het andere doordat het hem wordt verteld. Namen worden gevonden door delegaties af te lopen, en diensten worden gevonden door om een SRV-record te vragen — dus tegen de tijd dat een client met een domeincontroller aan Kerberos begint, kwam de identiteit van die domeincontroller uit een DNS-antwoord. Vinden via DNS is juist en prima. Machtigen via DNS is dat niet, en een verrassende hoeveelheid infrastructuur doet het stilletjes toch.
DNSSEC is wat die antwoorden verifieerbaar maakt: signaturen op elke set, een DS bij elke ouder, een keten naar één vertrouwensanker bij de root, en bekrachtigde ontkenning zodat een naam niet kan worden weggemaakt. Het levert authenticatie van de herkomst en integriteit — geen privacy, en geen juistheid. Het ondertekent wat de zone ook zegt, en daarom kan het een zone niet redden die onbetrouwde machines mogen schrijven. En het heeft een ouder nodig: een verzonnen interne TLD heeft nergens een DS te plaatsen, dus .local, .lan, .internal en home.arpa laten je allemaal onondertekend of met een privé-eiland van met de hand uitgedeelde sleutels.
Voor een Active Directory-domein levert dat een ontwerp op in plaats van een lijst instellingen. Gebruik een delegatie binnen een publieke zone die je bezit, zodat het vertrouwen langs de gewone keten van de root omlaag komt terwijl de data nooit vertrekt. Bied de AD-partities aan met BIND en dlz_bind9 — DLZ is niet het risico, het is hoe je de zone uit de directory krijgt — en laat de DC een verborgen primaire server zijn die naar buiten transfereert en niemand anders antwoordt. Een DLZ-zone kan zelf niet ondertekend worden, dus het ondertekenen is een gewone secundaire zone met de getransfereerde kopie, met inline-signing en een dnssec-policy erop: een tweede named op de DC als je minder machines wilt, een aparte host als je private sleutels van de directory af wilt. Hoe dan ook wordt het één keer ondertekend, op een vastgelegd punt, onder een sleutelbeleid — en de eerste hop is een poll en geen push, want DLZ kan geen notify sturen. Publiceer vanaf losstaande autoritatieve servers die geen sleutels bevatten en geen route naar de directory hebben.
En houd clients uit de zone die ertoe doet. Beveiligde dynamische update bekrachtigt de schrijver, niet de betekenis, en in een standaard AD-geïntegreerde zone zijn de schrijvers Authenticated Users — elk account, niet alleen elke machine — dus een zone met zowel laptoprecords als _msdcs-locators is één gephishte gebruiker verwijderd van een client die, met een volstrekt geldige signatuur, te horen krijgt dat de domeincontroller ergens anders is. Clientregistraties horen in een subzone, uitgedelegeerd of apart in Samba, waar het ergste wat een gecompromitteerd account kan doen liegen over zichzelf is.
Al is de betere vraag of clients zich überhaupt zouden moeten registreren. Een laptop in 2026 heeft een adres op de wifi thuis, nog een op het kantoornetwerk, nog een via het dock en nog een op de VPN, en hij publiceert er vrolijk de meeste van. Wat het A-record van een laptop gebruikt is bijna niets — het gereedschap dat een werkstation moet bereiken houdt zijn eigen inventaris bij, want DNS was voor mobiele clients toch nooit betrouwbaar. Records voor servers horen uit het inrichten te komen, en de vloot hoort niets te registreren.
Zorg er dan voor dat iets de signaturen nakijkt, want niets van het bovenstaande is iets waard tot een client een antwoord weigert. Op Linux betekent dat DNSSEC=yes in systemd-resolved, of een echte validerende resolver op de loopback — nooit allow-downgrade, dat een aanvaller die antwoorden kan verzinnen simpelweg uitzet door een antwoord te verzinnen. Op Windows betekent het aanvaarden dat de DNS-client zelf nooit iets valideert, en met een NRPT-regel afdwingen dat hij een antwoord eist dat de resolver heeft gevalideerd, met de laatste hop beschermd omdat die eis één bit is. Zet het eerst aan op de resolvers en kijk naar SERVFAIL, automatiseer de DS, en dwing daarna de clients. Het faalt dicht, wat de juiste kant op is en nog steeds een storing.
Niets hiervan is exotisch. Het is delegatie, transfer en ondertekenen — de drie dingen die DNS altijd heeft gedaan — zo opgesteld dat de machine met je directory niet de machine is die vragen aanneemt van de parkeerplaats, en zo dat wanneer iets wél tegen een client liegt, de client het merkt.