Derzeit wird viel über das Domain Name System (DNS) diskutiert, wobei weitreichende Änderungen an dem 36 Jahre alten Protokoll vorgeschlagen werden. Der Namensdienst des Internets, dessen Ursprünge im ARPANET liegen, hat seit seiner Einführung noch nie Änderungen erfahren, die die Abwärtskompatibilität beeinträchtigten. Doch neue Vorschläge zur Änderung des DNS-Transportmechanismus könnten dies nun ändern.
In diesem Beitrag befasse ich mich mit zwei neuen Technologien zur Absicherung von DNS, DNS over TLS (DoT) und DNS over HTTPS (DoH), und zeige, wie sie mit NGINX Open Source und NGINX Plus implementiert werden können.
[Redaktion – Dieser Beitrag ist einer von mehreren, in denen Anwendungsfälle für das NGINX-JavaScript-Modul vorgestellt werden. Eine vollständige Liste finden Sie unter „Anwendungsfälle für das NGINX-JavaScript-Modul“.
Der Code in diesem Beitrag wurde aktualisiert und verwendet nun die js_import-Direktive, welche die veraltete js_include-Direktive ab NGINX Plus R23<.htmla> ersetzt. Weitere Informationen finden Sie in der Referenzdokumentation zum NGINX JavaScript-Modul – im Abschnitt Beispielkonfiguration wird die korrekte Syntax für NGINX-Konfigurations- und JavaScript-Dateien gezeigt.]
Die Geschichte des DNS
DNS war der zweite Versuch, einen Namensdienst für das frühe Internet der Advanced Research Projects Agency (ARPA) zu entwickeln. Der erste war das von John Postel 1979 als IEN-116 veröffentlichte Internet-Name-Server-Protokoll. DNS wurde hierarchisch konzipiert und schuf eine Struktur, mit der Hostnamen dezentral in Zonen aufgeteilt und von vielen unabhängigen Stellen verwaltet werden konnten. Die ersten RFCs für DNS wurden 1983 veröffentlicht (RFC 882 und 883). Obwohl sie im Laufe der Jahre mehrfach erweitert wurden, würde ein Client, der nach den damals festgelegten Standards entwickelt wurde, auch heute noch funktionieren.
Warum also sollte das Protokoll jetzt geändert werden? Es funktioniert eindeutig wie vorgesehen und bestätigt damit das Vertrauen seiner Autoren, die sich ihrer Sache so sicher waren, dass sie im DNS-Paket nicht einmal eine Versionsnummer vorsahen – mir fallen nicht viele andere Protokolle ein, die das von sich behaupten können. DNS entstand in einer unschuldigeren Zeit, als die meisten Protokolle Klartext und häufig 7-Bit-ASCII verwendeten. Das heutige Internet ist jedoch ein weitaus gefährlicherer Ort als das ARPANET der 1980er-Jahre. Inzwischen setzen die meisten Protokolle zur Verschlüsselung und Verifizierung auf Transport Layer Security (TLS). Kritiker von DNS sind der Ansicht, dass zusätzliche Sicherheitsvorkehrungen längst überfällig sind.
So wie DNS der zweite Versuch war, ein Namensdienstprotokoll für das Internet bereitzustellen, entwickeln sich DNS over TLS (DoT) und DNS over HTTPS (DoH) nun zu zweiten Versuchen, das DNS-Protokoll abzusichern. Der erste Versuch war eine Erweiterung namens DNSSEC, und obwohl die meisten Top-Level-Domains (TLDs) DNSSEC verwenden, war es nie dafür vorgesehen, die in den DNS-Paketen übertragenen Daten zu verschlüsseln; es bestätigt lediglich, dass die Daten nicht manipuliert wurden. DoT und DoH sind Protokollerweiterungen, die DNS in einen TLS-Tunnel einbetten. Sollten sie sich durchsetzen, würden sie 36 Jahre Abwärtskompatibilität beenden.
DoT und DoH im Detail
DoT wird meiner Ansicht nach weitgehend als sinnvolle Erweiterung angesehen. Die Internet Assigned Numbers Authority (IANA) hat DoT bereits eine eigene Portnummer (TCP/853) zugewiesen. Dabei werden TCP-DNS-Pakete einfach in einen TLS-verschlüsselten Tunnel eingebettet. Viele Protokolle nutzen dieses Verfahren bereits: HTTPS ist HTTP innerhalb eines TLS-Tunnels, und SMTPS, IMAPS sowie LDAPS sind abgesicherte Versionen der jeweiligen Protokolle. DNS hat als Transportprotokoll schon immer UDP (oder in bestimmten Fällen TCP) verwendet, sodass das Hinzufügen der TLS-Schicht keine grundlegende Änderung darstellt.
DoH hingegen ist etwas umstrittener. DoH nimmt ein DNS-Paket und verpackt es in eine HTTP-GET- oder POST-Anfrage, die dann über HTTP/2 oder höher über eine HTTPS-Verbindung gesendet wird. Diese ist praktisch nicht von jeder anderen HTTPS-Verbindung zu unterscheiden, sodass Unternehmen oder Dienstanbieter nicht erkennen können, welche Anfragen gestellt werden. Mozilla und andere Befürworter erklären, dass dieses Verfahren die Privatsphäre der Nutzer verbessert, da es die von ihnen besuchten Websites vor neugierigen Blicken schützt.
Doch das stimmt nicht ganz. Kritiker von DoH weisen darauf hin, dass es nicht gerade das Tor des DNS ist, denn wenn der Browser schließlich eine Verbindung zu dem Host herstellt, den er über DoH ermittelt hat, verwendet die Anfrage mit ziemlicher Sicherheit die TLS-Erweiterung Server Name Indication (SNI) – und diese enthält den Hostnamen und wird im Klartext übertragen. Wenn der Browser außerdem versucht, das Zertifikat des Servers mithilfe des Online Certificate Status Protocol (OSCP) zu validieren, erfolgt dieser Vorgang höchstwahrscheinlich ebenfalls im Klartext. Wer also DNS-Abfragen überwachen kann, kann auch die SNI in der Verbindung oder den Zertifikatsnamen bei der OCSP-Validierung auslesen.
Für viele Menschen besteht das größte Problem bei DoH darin, dass die Browseranbieter festlegen, an welche DNS-Server die DoH-Anfragen ihrer Nutzer standardmäßig gesendet werden (bei Firefox-Nutzern in den USA gehören diese DNS-Server beispielsweise Cloudflare). Der Betreiber der DNS-Server kann die IP-Adresse des Nutzers und die Domainnamen der Websites sehen, an die Anfragen gesendet werden. Das mag nicht nach viel klingen, doch Forscher der University of Illinois haben herausgefunden, dass es möglich ist, zu ermitteln, welche Websites eine Person besucht hat – und zwar allein anhand der Zieladressen von Anfragen nach Elementen einer Webseite, dem sogenannten Page Load Fingerprint. Diese Informationen können dann dazu verwendet werden, „ein Profil des Nutzers zu erstellen und gezielt Werbung an ihn auszuspielen“.
Wie kann NGINX helfen?
DoT und DoH sind nicht grundsätzlich schlecht, und es gibt Anwendungsfälle, in denen sie die Privatsphäre der Nutzer verbessern. Allerdings herrscht zunehmend Einigkeit darüber, dass ein öffentlicher, zentralisierter DoH-Dienst der Privatsphäre der Nutzer schadet. Daher empfehlen wir Ihnen dringend, einen solchen Dienst keinesfalls zu nutzen.
In jedem Fall verwalten Sie für Ihre Websites und Apps wahrscheinlich Ihre eigenen DNS-Zonen – einige öffentliche, einige private und einige mit Split-Horizon-Konfiguration. Vielleicht entscheiden Sie sich irgendwann dafür, einen eigenen DoT- oder DoH-Dienst zu betreiben. Hier kann NGINX helfen.
Die durch DoT ermöglichten Datenschutzverbesserungen bieten große Vorteile für die DNS-Sicherheit. Doch was, wenn Ihr aktueller DNS-Server DoT nicht unterstützt? Hier kann NGINX helfen, indem es als Gateway zwischen DoT und herkömmlichem DNS fungiert.
Oder vielleicht schätzen Sie das Potenzial von DoH, Firewalls zu umgehen, wenn der DoT-Port blockiert ist. Auch hier kann NGINX helfen, indem es ein DoH-zu-DoT/DNS-Gateway bereitstellt.
Bereitstellen eines einfachen DoT-DNS-Gateways
Das NGINX-Stream-Modul (TCP/UDP) unterstützt SSL-Terminierung, sodass sich ein DoT-Dienst ganz einfach einrichten lässt. Mit nur wenigen Zeilen NGINX-Konfiguration können Sie ein einfaches DoT-Gateway erstellen.
Sie benötigen einen upstream-Block für Ihre DNS-Server und einen server-Block für die TLS-Terminierung:
Codebeispiel wird geladen…
Natürlich können wir auch den umgekehrten Weg gehen und eingehende DNS-Anfragen an einen vorgeschalteten DoT-Server weiterleiten. Dies ist jedoch weniger nützlich, da der meiste DNS-Datenverkehr über UDP läuft und NGINX nur zwischen DoT und anderen TCP-Diensten wie TCP-basiertem DNS übersetzen kann.
Codebeispiel wird geladen…
Ein einfaches DoH-DNS-Gateway
Im Vergleich zu einem DoT-Gateway ist die Konfiguration eines einfachen DoH-Gateways etwas komplexer. Wir benötigen sowohl einen HTTPS-Dienst als auch einen Stream-Dienst und verwenden JavaScript-Code sowie das NGINX JavaScript-Modul<.htmla> (njs), um zwischen den beiden Protokollen zu übersetzen. Die einfachste Konfiguration sieht wie folgt aus:
Codebeispiel wird geladen…
Das in dieser Konfiguration verwendete JavaScript-Skript (nginx_stream.js) bindet verschiedene Moduldateien der DNS-Bibliothek ein. Im Modul dns.js legt die Variable dns_decode_level fest, wie umfangreich die DNS-Pakete verarbeitet werden. Die Verarbeitung von DNS-Paketen beeinträchtigt zwangsläufig die Leistung. Wenn Sie eine Konfiguration wie die oben beschriebene verwenden, setzen Sie dns_decode_level auf 0.
Ein fortschrittlicheres DoH-Gateway
Hier bei NGINX kennen wir uns mit HTTP wirklich ziemlich gut aus. Daher halten wir es für eine verpasste Chance, NGINX lediglich als einfaches DoH-Gateway einzusetzen.
Der hier verwendete JavaScript-Code kann so konfiguriert werden, dass die DNS-Pakete vollständig oder teilweise dekodiert werden. Dadurch können wir für die DoH-Abfragen einen HTTP-Inhaltscache erstellen, dessen Expires- und Cache-Control-Header auf Grundlage der niedrigsten TTL-Werte der DNS-Antworten festgelegt werden.
Im Folgenden finden Sie ein ausführlicheres Beispiel mit zusätzlichen Verbindungsoptimierungen sowie Unterstützung für Inhalts-Caching und Protokollierung:
Codebeispiel wird geladen…
Ein erweiterter DNS-Filter mit Funktionen von NGINX Plus
Wenn Sie über ein NGINX Plus-Abonnement verfügen, können Sie die obigen Beispiele mit einigen der erweiterten Funktionen von NGINX Plus kombinieren, etwa mit aktiven Zustandsprüfungen und Hochverfügbarkeit. Sie können sogar die API zur Cache-Bereinigung verwenden, um zwischengespeicherte DoH-Antworten zu verwalten.
Es ist auch möglich, mit dem Schlüssel-Wert-Speicher von NGINX Plus ein DNS-Filtersystem zu erstellen, das Benutzer vor schädlichen Domains schützt, indem es eine DNS-Antwort zurückgibt, die den Zugriff darauf wirksam verhindert. Die Inhalte des Schlüssel-Wert-Speichers verwalten Sie dynamisch über die RESTful API von NGINX Plus.
Wir definieren zwei Kategorien schädlicher Domains: „blocked“ und „blackhole“. Das DNS-Filtersystem verarbeitet eine DNS-Abfrage für eine Domain je nach deren Kategorie unterschiedlich:
- Für blockierte Domains wird die Antwort
NXDOMAINzurückgegeben (was darauf hinweist, dass die Domain nicht existiert) - Für Blackhole-Domains wird bei Anfragen nach
A-Einträgen ein einzelner DNS-A-Eintrag mit 0.0.0.0 zurückgegeben oder ein einzelnerAAAA-Eintrag mit :: bei Anfragen nachAAAA-Einträgen (IPv6). Bei anderen Eintragstypen wird eine Antwort ohne Einträge zurückgegeben.
Wenn unser DNS-Filter eine Anfrage erhält, sucht er zunächst nach einem Schlüssel, der genau dem abgefragten FQDN entspricht. Wird ein solcher Schlüssel gefunden, „bereinigt“ er die Anfrage entsprechend dem zugehörigen Wert, indem er sie blockiert oder ins Blackhole leitet. Gibt es keine exakte Übereinstimmung, sucht er in zwei Listen nach dem Domainnamen – einer Liste blockierter Domains und einer Liste von Blackhole-Domains – und bereinigt die Anfrage bei einer Übereinstimmung auf die entsprechende Weise.
Wenn die abgefragte Domain nicht schädlich ist, leitet das System die Anfrage zur regulären Verarbeitung an einen DNS-Server von Google weiter.
Einrichten des Schlüssel-Wert-Speichers
Wir definieren die Domains, die wir als schädlich einstufen, mithilfe von zwei Arten von Einträgen im Schlüssel-Wert-Speicher von NGINX Plus:
- Einzelne Einträge für vollständig qualifizierte Domänennamen (FQDNs), jeweils mit dem Wert
blockedoderblackhole - Zwei Domänenlisten mit den Schlüsseln
blocked_domainsundblackholed_domains, denen jeweils eine Liste von Domänen im CSV-Format (durch Kommas getrennte Werte) zugeordnet ist
Die folgende Konfiguration für den Schlüssel-Wert-Speicher wird im stream-Kontext angegeben. Die Direktive keyval_zone reserviert einen Speicherblock für den Schlüssel-Wert-Speicher mit dem Namen dns_config. Die erste Direktive keyval lädt alle festgelegten Schlüssel-Wert-Paare mit übereinstimmendem FQDN, während die zweite und dritte Direktive die beiden Domänenlisten definieren:
Codebeispiel wird geladen…
Anschließend können wir über die NGINX Plus-API jedem FQDN-Schlüssel, den wir ausdrücklich filtern möchten, den Wert „blocked“ oder „blackhole“ zuweisen oder die CSV-formatierte Liste ändern, die dem Schlüssel „blocked_domains“ bzw. „blackhole_domains“ zugeordnet ist. Wir können jederzeit exakte FQDNs ändern oder entfernen oder die Domänen in einer der beiden Listen anpassen. Der DNS-Filter wird sofort entsprechend aktualisiert.
Auswählen des Upstream-Servers zur Verarbeitung der Anfrage
Die folgende Konfiguration lädt die Variable „$dns_response“, die durch eine js_preread-Direktive in den weiter unten unter „server“ definierten Blöcken im Abschnitt „Konfigurieren der Server, die DNS-Abfragen entgegennehmen“ mit einem Wert belegt wird. Wenn der aufgerufene JavaScript-Code feststellt, dass die Anfrage bereinigt werden muss, setzt er die Variable je nach Bedarf auf „blocked“ oder „blackhole“.
Wir verwenden eine map-Direktive, um den Wert der Variablen $dns_response der Variablen $upstream_pool zuzuweisen und dadurch zu steuern, welche der unter Definieren der Upstream-Server aufgeführten Upstream-Gruppen die Anfrage verarbeitet. Abfragen für nicht schädliche Domains werden an den standardmäßigen Google-DNS-Server weitergeleitet, während Abfragen für blockierte oder in ein Blackhole umgeleitete Domains vom jeweiligen Upstream-Server für diese Kategorien verarbeitet werden.
Codebeispiel wird geladen…
Upstream-Server definieren
Diese Konfiguration definiert die Upstream-Servergruppen „blocked“, „blackhole“ und „google“, die jeweils Anfragen für blockierte, Blackhole- und nicht schädliche Domains verarbeiten.
Codebeispiel wird geladen…
Konfigurieren der Server, die auf DNS-Abfragen warten
Hier definieren wir die Server im stream-Kontext, die auf eingehende DNS-Anfragen warten. Die js_preread-Direktive ruft den Javascript-Code auf, der das DNS-Paket decodiert, den Domainnamen aus dem Feld NAME des Pakets abruft und die Domain im Schlüssel-Wert-Speicher nachschlägt. Wenn der Domainname mit einem FQDN-Schlüssel übereinstimmt oder sich in der Zone einer der Domains in der Liste blocked_domains oder blackhole_domains befindet, wird die Anfrage verworfen. Andernfalls wird sie zur Namensauflösung an den DNS-Server von Google gesendet.
Codebeispiel wird geladen…
Damit wären wir fast fertig – wir müssen nur noch unseren letzten server-Block hinzufügen, der auf Anfragen entweder mit der verworfenen oder der blockierten Antwort reagiert.
Codebeispiel wird geladen…
Beachten Sie, dass die Server in diesem Block nahezu identisch mit den direkt darüber aufgeführten eigentlichen DNS-Servern sind. Der Hauptunterschied besteht darin, dass diese Server ein Antwortpaket an den Client senden, anstatt die Anfrage an eine übergeordnete Gruppe von DNS-Servern weiterzuleiten. Unser JavaScript-Code erkennt, wenn er über Port 9953 oder 9853 ausgeführt wird, und anstatt ein Flag zu setzen, das angibt, dass das Paket blockiert werden soll, befüllt er $dns_response mit dem eigentlichen Antwortpaket. Das ist auch schon alles.
Natürlich hätten wir diese Filterung auch auf DoT- und DoH-Dienste anwenden können, aber der Einfachheit halber haben wir Standard-DNS verwendet. Die Kombination der DNS-Filterung mit einem DoH-Gateway bleibt dem Leser als Übung überlassen.
Testen des Filters
Wir haben zuvor die NGINX Plus API verwendet, um Einträge für zwei FQDNs sowie einige Domains zu den beiden Domainlisten hinzuzufügen:
$ curl -s http://localhost:8080/api/5/stream/keyvals/dns_config | jq{
"www.some.bad.host": "blocked",
"www.some.other.host": "blackhole",
"blocked_domains": "bar.com,baz.com",
"blackhole_domains": "foo.com,nginx.com"
}Wenn wir also die Auflösung von www.foo.com (einem Host innerhalb der blockierten Domain foo.com) anfordern, erhalten wir einen A-Eintrag mit der IP-Adresse 0.0.0.0:
$ dig @localhost www.foo.com
; <<>> DiG 9.11.3-1ubuntu1.9-Ubuntu <<>> @localhost www.foo.com
; (1 server found)
;; global options: +mcd
;; Got answer:
;; ->>HEADER,,- opcode: QUERY, status: NOERROR, id: 58558
;; flags: qr aa rd ad; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
;; WARNING: recursion requested but not available
;; QUESTION SECTION:
; www.foo.com. IN A
;; ANSWER SECTION:
www.foo.com. 300 IN A 0.0.0.0
;; Query time: 0 msec
;; SERVER: 172.0.0.1#53(126.0.0.1)
;; WHEN: Mon Dec 2 14:31:35 UTC 2019
;; MSG SIZEW rcvd: 45Auf den Code zugreifen
Die Dateien für DOH und DOT sind in meinem GitHub-Repository verfügbar:
- Der JavaScript-Code im Ordner „njs.d/dns“
- Die NGINX-Konfiguration im Ordner „examples“
Wenn Sie NGINX Plus ausprobieren möchten, starten Sie noch heute Ihre kostenlose 30-Tage-Testversion oder kontaktieren Sie uns, um Ihre Anwendungsfälle zu besprechen.
Über den Autor
Ähnliche Blogbeiträge
Die Zukunft von NGINX: Zurück zu unseren Open-Source-Wurzeln
Beim NGINX Sprint 2022 besinnen wir uns auf unsere Open-Source-Wurzeln. Erfahren Sie mehr über die Initiativen, die wir im kommenden Jahr verfolgen, und teilen Sie uns mit, was Sie sich wünschen.

Ein Leitfaden zur Auswahl eines Ingress-Controllers, Teil 1: Ermitteln Sie Ihre Anforderungen
Ein Ingress-Controller kann eines der leistungsstärksten Tools in Ihrem Kubernetes-Stack sein. Erfahren Sie, wie Sie Ihre Ingress-Anforderungen ermitteln, um die beste Option auszuwählen.
Bereitstellung des NGINX Ingress Controllers auf Amazon EKS: So haben wir getestet
Wir haben kürzlich den Lösungsleitfaden zum NGINX Ingress Controller um Empfehlungen zur Dimensionierung für Amazon Elastic Kubernetes Service (EKS) ergänzt. In diesem Blogbeitrag erläutern wir, wie wir diese Empfehlungen erarbeitet haben, und stellen Ihnen alle Informationen zur Verfügung, die Sie benötigen, um selbst ähnliche Tests durchzuführen.

Protokollierung des Datenverkehrs zwischen NGINX und Upstream-Servern bei CDN77
Bei der Fehlersuche bei Datenverkehrsproblemen zwischen NGINX und Upstream-Servern konnte CDN77 die benötigten Daten nur aus dem Fehlerprotokoll mit aktivierter Debug-Ausgabe beziehen, was die Leistung beeinträchtigt und riesige Dateien erzeugt. Daher entwickelte das Team ein neues benutzerdefiniertes Modul, das ausschließlich die benötigten Daten protokolliert.
Ankündigung von NGINX Plus R16
NGINX Plus R16 ist eine einheitliche, elastische Ingress- und Egress-Schicht für Anwendungen, die die Funktionen eines Load Balancers, API-Gateways und einer WAF vereint und neue Features wie clusterfähige Ratenbegrenzung, Schlüssel-Wert-Speicher, „Random with Two Choices“, verbessertes UDP-Load-Balancing und vieles mehr bietet.
5 Gründe für den Umstieg auf softwarebasiertes Load Balancing
Entdecken Sie fünf Gründe für den Wechsel zu einem softwarebasierten Load Balancer wie NGINX Plus, darunter Kosteneinsparungen, die Einführung von DevOps und eine einheitliche Lösung für alle Umgebungen.