Am 4. Oktober 2023 traten bei Cloudflare Probleme bei der DNS-Auflösung auf, die um 07:00 UTC begannen und um 11:00 UTC endeten. Einige Nutzer von 1.1.1.1 oder Produkten wie WARP, Zero Trust oder DNS-Resolvern von Drittanbietern, die 1.1.1.1 verwenden, haben möglicherweise SERVFAIL DNS-Antworten auf gültige Anfragen erhalten. Wir möchten uns vielmals für diesen Ausfall entschuldigen. Dieser Ausfall war ein interner Softwarefehler und nicht das Ergebnis eines Angriffs. In diesem Blogartikel werden wir erläutern, was der Fehler war, warum er auftrat und was wir unternehmen, um sicherzustellen, dass sich so etwas nicht wiederholt.
Hintergrund
Im Domain Name System (DNS) existiert jeder Domain-Name innerhalb einer DNS-Zone. Die Zone ist eine Sammlung von Domain-Namen und Host-Namen, die gemeinsam kontrolliert werden. So ist Cloudflare beispielsweise für die Domain cloudflare.com verantwortlich, die sich in der Zone „cloudflare.com“ befindet. Die Top-Level-Domain (TLD) .com gehört einer dritten Partei und befindet sich in der Zone „com“. Sie gibt Auskunft darüber, wie cloudflare.com zu erreichen ist. Über allen TLDs befindet sich die Root-Zone, die Hinweise darauf gibt, wie die TLDs erreicht werden. Das bedeutet, dass die Root-Zone wichtig ist, um alle anderen Domain-Namen auflösen zu können. Wie andere wichtige Teile des DNS ist auch die Root-Zone mit DNSSEC signiert, was bedeutet, dass die Root-Zone selbst kryptografische Signaturen enthält.
Die Root-Zone wird aufden Root-Servern, veröffentlicht, es ist jedoch auch üblich, dass DNS-Betreiber automatisch eine Kopie der Root-Zone abrufen und aufbewahren. Damit sind die Informationen in der Root-Zone auch dann noch verfügbar, wenn die Root-Server nicht erreichbar sind. Die rekursive DNS-Infrastruktur von Cloudflare verfolgt diesen Ansatz, da er auch den Auflösungsprozess beschleunigt. Neue Versionen der Root-Zone werden normalerweise zweimal täglich veröffentlicht. 1.1.1.1 verfügt über eine WebAssembly-Anwendung namens static_zone, die über der Haupt-DNS-Logik läuft und diese neuen Versionen bereitstellt, sobald sie verfügbar sind.
Was ist passiert?
Am 21. September wurde im Rahmen einer bekannten und geplanten Änderung der Verwaltung der Root-Zonen erstmals ein neuer Typ von Ressourceneintrag in die Root-Zonen aufgenommen. Der neue Ressourceneintrag trägt die Bezeichnung ZONEMD und ist im Grunde eine Prüfsumme für den Inhalt der Root-Zone.
Die Root-Zone wird von einer Software abgerufen, die im Kernnetzwerk von Cloudflare läuft. Anschließend wird sie an die Rechenzentren von Cloudflare auf der ganzen Welt weiterverteilt. Nach der Änderung wurde die Root-Zone, die den ZONEMD-Eintrag enthält, weiterhin wie gewohnt abgerufen und verteilt. Die 1.1.1.1-Resolver-Systeme, die diese Daten verwenden, hatten jedoch Probleme beim Parsen des ZONEMD-Eintrags. Da Zonen in ihrer Gesamtheit geladen und bereitgestellt werden müssen, bedeutete das Versagen des Systems beim Parsen von ZONEMD, dass die neuen Versionen der Root-Zone nicht in den Resolver-Systemen von Cloudflare verwendet wurden. Einige der Server, die die Resolver-Infrastruktur von Cloudflare hosten, gingen dazu über, die DNS-Root-Server direkt abzufragen, wenn sie die neue Root-Zone nicht erhalten haben. Andere verließen sich jedoch weiterhin auf die bekannte funktionierende Version der Root-Zone, die noch in ihrem Speichercache vorhanden war, d. h. die Version, die am 21. September vor der Änderung abgerufen wurde.
Am 4. Oktober 2023 um 07:00 UTC sind die DNSSEC-Signaturen in der Version der Root-Zone vom 21. September abgelaufen. Da es keine neuere Version gab, die die Cloudflare-Resolversysteme verwenden konnten, waren einige der Cloudflare-Resolversysteme nicht mehr in der Lage, DNSSEC-Signaturen zu validieren und begannen daher, Fehlerantworten (SERVFAIL) zu übermitteln. Die Rate, mit der Cloudflare-Resolver SERVFAIL-Antworten generierten, stieg um 12 %. Die folgenden Abbildungen veranschaulichen den Verlauf des Ausfalls und wie er für die Nutzer sichtbar wurde.
Zeitlicher Ablauf und Auswirkungen des Vorfalls
21. September 6:30 UTC: Letzter erfolgreicher Abruf (Pull) der Root-Zone4. Oktober 7:00 UTC: DNSSEC-Signaturen in der Root-Zone, die am 21. September erhalten wurden, sind abgelaufen, was zu einem Anstieg der SERVFAIL-Antworten auf Client-Abfragen führt.7:57: Erste externe Berichte über unerwartete SERVFAILs treffen ein.8:03: Interner Cloudflare-Vorfall gemeldet.8:50: Ein erster Versuch wird unternommen, den 1.1.1.1-Dienst mittels einer Override-Regel davon abzuhalten, Antworten mit der veralteten Root-Datei zu übermitteln.10:30: 1.1.1.1 hat das Vorladen der Root-Zonendatei vollständig gestoppt.10:32: Die Antworten sind wieder normal.11:02: Vorfall abgeschlossen.
Das folgende Diagramm zeigt den zeitlichen Verlauf der Auswirkungen zusammen mit dem Prozentsatz der DNS-Abfragen, auf die ein SERVFAIL-Fehler ausgegeben wurde:
Bei normalem Traffic erwarten wir ein bestimmtes Volumen an SERVFAIL-Fehlern im Normalbetrieb. Normalerweise liegt dieser Prozentsatz bei etwa 3 %. Diese SERVFAILs können durch legitime Probleme in der DNSSEC-Kette, Fehler bei der Verbindung zu autoritativen Servern, zu lange Antwortzeiten der autoritativen Server und viele andere Faktoren verursacht werden. Während des Vorfalls erreichte die Anzahl der SERVFAILs einen Spitzenwert von 15 % der gesamten Abfragen, obwohl die Auswirkungen nicht gleichmäßig über die Welt verteilt waren und sich hauptsächlich auf unsere größeren Rechenzentren wie Ashburn, Virginia, Frankfurt und Singapur konzentrierten.
Warum es zu diesem Vorfall kam
Warum das Parsen des ZONEMD-Eintrags fehlgeschlagen ist
DNS hat ein binäres Format für die Speicherung von Ressourceneinträgen. In diesem Binärformat wird der Typ des Ressourceneintrags (TYPE) als 16-Bit-Integer gespeichert. Der Typ des Ressourceneintrags bestimmt, wie die Ressourcendaten (RDATA) geparst werden. Wenn der Eintragstyp 1 ist, bedeutet dies, dass es sich um einen A-Eintrag handelt und die RDATA als IPv4-Adresse geparst werden kann. Eintragstyp 28 ist ein AAAA-Eintrag, dessen RDATA stattdessen als IPv6-Adresse geparst werden kann. Wenn ein Parser auf einen unbekannten Ressourcentyp stößt, weiß er nicht, wie er dessen RDATA parsen soll, aber glücklicherweise muss er das auch nicht: Das Feld RDLENGTH gibt an, wie lang das RDATA-Feld ist, so dass der Parser es als undurchsichtiges Datenelement behandeln kann.
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| |
/ /
/ NAME /
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TYPE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| CLASS |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| TTL |
| |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| RDLENGTH |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--|
/ RDATA /
/ /
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
Warum static_zone den neuen ZONEMD-Eintrag noch nicht unterstützte? Weil wir uns bisher dafür entschieden hatten, die Root-Zone intern in ihrem Darstellungsformat und nicht im Binärformat zu verteilen. Ein Blick auf die Textdarstellung einiger Ressourceneinträge zeigt, dass es bei der Darstellung der verschiedenen Datensätze sehr viel mehr Unterschiede gibt.
Beispieldatensätze entnommen von https://www.internic.net/domain/root.zone
. 86400 IN SOA a.root-servers.net. nstld.verisign-grs.com. 2023100400 1800 900 604800 86400
. 86400 IN RRSIG SOA 8 0 86400 20231017050000 20231004040000 46780 . J5lVTygIkJHDBt6HHm1QLx7S0EItynbBijgNlcKs/W8FIkPBfCQmw5BsUTZAPVxKj7r2iNLRddwRcM/1sL49jV9Jtctn8OLLc9wtouBmg3LH94M0utW86dKSGEKtzGzWbi5hjVBlkroB8XVQxBphAUqGxNDxdE6AIAvh/eSSb3uSQrarxLnKWvHIHm5PORIOftkIRZ2kcA7Qtou9NqPCSE8fOM5EdXxussKChGthmN5AR5S2EruXIGGRd1vvEYBrRPv55BAWKKRERkaXhgAp7VikYzXesiRLdqVlTQd+fwy2tm/MTw+v3Un48wXPg1lRPlQXmQsuBwqg74Ts5r8w8w==
. 518400 IN NS a.root-servers.net.
. 86400 IN ZONEMD 2023100400 1 241 E375B158DAEE6141E1F784FDB66620CC4412EDE47C8892B975C90C6A102E97443678CCA4115E27195B468E33ABD9F78C
Wenn wir auf einen unbekannten Ressourceneintrag stoßen, ist es nicht immer einfach zu wissen, wie wir damit umgehen sollen. Aus diesem Grund unternimmt die Bibliothek, die wir zum Parsen der Root-Zone an der Edge verwenden, keinen entsprechenden Versuch und gibt stattdessen einen Parser-Fehler aus.
Warum eine veraltete Version der Root-Zone verwendet wurde
Die static_zone-App, die mit dem Laden und Parsen der Root-Zone beauftragt ist, um die Root-Zone lokal bereitzustellen (RFC 7706), speichert die neueste Version im Speicher. Wenn eine neue Version veröffentlicht wird, parst sie diese und verwirft bei erfolgreichem Parsen die alte Version. Da das Parsen jedoch fehlschlug, wechselte die static_zone-App nie zu einer neueren Version, sondern verwendete stattdessen weiterhin die alte Version auf unbestimmte Zeit. Wenn der 1.1.1.1-Dienst zum ersten Mal gestartet wird, hat die static_zone-App keine bestehende Version im Speicher. Wenn sie versucht, die Root-Zone zu analysieren, schlägt dies fehl, aber da sie nicht auf eine ältere Version der Root-Zone zurückgreifen kann, fragt sie die Root-Server direkt nach eingehenden Anfragen ab.
Warum der erste Versuch, static_zone zu deaktivieren, nicht funktioniert hat
Zunächst versuchten wir, die static_zone-App durch Überschreibungsregeln zu deaktivieren, ein Mechanismus, mit dem wir einige Verhaltensweisen von 1.1.1.1 programmatisch ändern können. Die von uns angewandte Regel lautete:
Bei jeder eingehenden Anfrage fügt diese Regel das Tag rec_disable_static zur Anfrage hinzu. In der static_zone-App wird auf dieses Tag geprüft, und wenn es gesetzt ist, wird keine Antwort aus der gecachten, statischen Root-Zone ausgegeben. Zur Verbesserung der Cache-Performance werden Abfragen jedoch manchmal an einen anderen Knoten weitergeleitet, wenn der aktuelle Knoten die Antwort nicht in seinem eigenen Cache finden kann. Leider ist das rec_disable_static-Tag nicht in den Abfragen enthalten, die an andere Knoten weitergeleitet werden, was dazu führte, dass die static_zone-App weiterhin mit veralteten Informationen antwortete, bis wir die App schließlich ganz deaktivierten.
phase = pre-cache set-tag rec_disable_static
Warum die Auswirkungen nur teilweise eintraten
Cloudflare führt regelmäßig fortlaufende Neustarts der Server durch, auf denen unsere Dienste gehostet werden, um Aufgaben wie Kernel-Updates durchzuführen, die nur nach einem vollständigen Neustart des Systems wirksam werden können. Zum Zeitpunkt dieses Ausfalls trugen Resolver-Server-Instanzen, die zwischen der ZONEMD-Änderung und der DNSSEC-Ungültigmachung neu gestartet wurden, nicht zu den Auswirkungen bei. Hätten sie während dieses zweiwöchigen Zeitraums neu gestartet, hätten sie die Root-Zone beim Start nicht geladen und stattdessen DNS-Abfragen an Root-Server gesendet. Darüber hinaus verwendet der Resolver eine Technik namens „serve stale“ (RFC 8767) mit dem Ziel, beliebte Einträge aus einem möglicherweise veralteten Cache weiter zu übermitteln, um die Auswirkungen zu begrenzen. Ein Eintrag gilt als veraltet, wenn die TTL-Sekundenzahl seit dem Abruf des Eintrags aus dem vorgelagerten Bereich verstrichen ist. Dadurch konnte ein Totalausfall verhindert werden; die Auswirkungen waren vor allem in unseren größten Rechenzentren zu spüren, in denen viele Server standen, die den 1.1.1.1-Dienst in diesem Zeitraum nicht neu gestartet hatten.
Abhilfe- und Folgemaßnahmen
Dieser Vorfall hatte weitreichende Auswirkungen, und wir nehmen die Verfügbarkeit unserer Dienste sehr ernst. Wir haben mehrere Bereiche identifiziert, in denen Verbesserungen möglich sind, und werden weiter daran arbeiten, weitere Lücken aufzudecken, die einen erneutes Auftreten verursachen könnten.
An diesen Aspekten arbeiten wir sofort:
Sichtbarkeit: Wir fügen Warnungen hinzu, um zu benachrichtigen, wenn static_zone eine veraltete Root-Zonendatei bereitstellt. Es hätte nicht sein dürfen, dass das Bereitstellen einer veralteten Root-Zonendatei so lange unbemerkt bleibt, wie es der Fall war. Hätten wir dies besser überwacht, hätte es dank der vorhandenen Zwischenspeicherung keine Auswirkungen gegeben. Unser Ziel ist es, unsere Kunden und deren Nutzer vor vorgelagerten Änderungen zu schützen.
Ausfallsicherheit: Wir werden neu bewerten, wie wir die Root-Zone intern einlesen und verteilen. Unsere Aufnahme- und Verteilungspipelines sollten neue RRTYPEs nahtlos verarbeiten, und jede kurze Unterbrechung der Pipeline sollte für Endnutzer nicht sichtbar sein.
Tests: Obwohl wir Tests zu diesem Problem durchgeführt haben, einschließlich Tests zu noch nicht veröffentlichten Änderungen beim Parsen der neuen ZONEMD-Einträge, haben wir nicht ausreichend getestet, was passiert, wenn die Root-Zone nicht geparst werden kann. Wir werden den Testumfang und die damit verbundenen Prozesse verbessern.
Architektur: Wir sollten ab einem bestimmten Punkt keine veralteten Kopien der Root-Zone mehr verwenden. Es ist zwar möglich, veraltete Root-Zonendaten für eine begrenzte Zeit weiter zu verwenden, aber ab einem bestimmten Punkt gibt es inakzeptable Betriebsrisiken. Wir werden Maßnahmen ergreifen, um sicherzustellen, dass die Lebensdauer von gecachten Root-Zonendaten besser verwaltet wird, wie in „RFC 8806: Running a Root Server Local to a Resolver“ beschrieben.
Fazit
Wir bedauern zutiefst, dass es zu diesem Vorfall gekommen ist. Aus diesem Vorfall ergibt sich eine klare Botschaft: Gehen Sie niemals davon aus, dass sich etwas nicht ändern wird! Viele moderne Systeme bestehen aus einer langen Kette von Bibliotheken, die in die endgültige ausführbare Datei einfließen. Jede dieser Bibliotheken kann Fehler enthalten oder nicht früh genug aktualisiert werden, damit die Programme bei Änderungen der Eingaben korrekt funktionieren. Wir wissen, wie wichtig gute Tests sind, die es ermöglichen, Regressionen zu erkennen und Systeme und Komponenten zu entwickeln, die bei Änderungen der Eingaben zuverlässig versagen. Wir verstehen, dass wir immer davon ausgehen müssen, dass „Format“-Änderungen in den kritischsten Systemen des Internets (DNS und BGP) Auswirkungen haben werden.
Wir haben intern viel zu klären und arbeiten rund um die Uhr, um sicherzustellen, dass sich so etwas nicht wiederholt.