Cloudflare Flexible SSL schickt die Anfrage eines Workers in eine Weiterleitungsschleife

Christian Drexler

Auf einer Subdomain dieser Seite läuft ein kleiner Cloudflare Worker. Er nimmt ein paar Ereignisse von den Seiten entgegen und schreibt sie in eine Datenbank, die auf einem meiner Server liegt und unter einem anderen Hostnamen erreichbar ist. Derselbe Worker erledigt dieselbe Aufgabe auf anderen Domains ohne Murren.

Auf dieser Domain schrieb er nichts. Die Seiten luden, der Worker antwortete, und keine Zeile kam an. Das blieb so, bis ich nachsah, weil nirgends etwas rot war.

Das Symptom

Im Log des Workers stand pro Versuch eine Zeile, und es war keine, die ich von einem Datenbankaufruf erwartet hätte:

Too many redirects

Der Worker hat eine kleine Health-Route, die eine triviale Abfrage versucht und das Ergebnis meldet. Auf den gesunden Domains sagt sie db_connected: true. Hier sagte sie db_connected: null. Die Abfrage scheiterte nicht mit einem Fehler der Datenbank. Sie kam gar nicht bis zur Datenbank.

Was los war

Jede Cloudflare-Zone hat einen SSL-Modus, der festlegt, wie Cloudflare mit dem Server dahinter spricht. Die Zonen, in denen der Worker sich benahm, stehen auf Full (strict). Diese stand noch auf Flexible.

Flexible heißt: Der Besucher spricht HTTPS mit Cloudflare, und Cloudflare spricht unverschlüsseltes HTTP mit dem Ursprungsserver. Gedacht ist das für Server, die gar kein Zertifikat haben.

Der Modus gilt auch für das, was ein Worker innerhalb seiner eigenen Zone abruft. Der Datenbank-Hostname, den der Worker anspricht, liegt auf dieser Domain in derselben Zone wie der Worker. Die Anfrage des Workers, im Code als https:// geschrieben, ging also als einfaches HTTP hinaus. Und eine einfache HTTP-Anfrage an diesen Hostnamen wird so beantwortet, wie es sich gehört: mit einem 301 auf die HTTPS-Adresse.

Beides zusammen, einer Anfrage gefolgt:

  1. Der Worker ruft https://db.example.com/ ab.
  2. Weil die Zone auf Flexible steht, geht die Anfrage über HTTP hinaus.
  3. Die Antwort lautet: HTTPS benutzen, 301.
  4. Der Worker folgt der Weiterleitung und ruft wieder https://db.example.com/ ab.
  5. Zurück zu Schritt 2.

Nach einigen Runden gibt die Laufzeitumgebung mit „Too many redirects“ auf. Im Code des Workers war die Anfrage die ganze Zeit HTTPS. Die Zoneneinstellung hat sie auf dem Weg nach draußen immer wieder herabgestuft, und die Weiterleitung hat sie immer wieder zurückgeschickt.

Es ist dieselbe Schleife, die im Browser auf einer Flexible-Zone das bekannte ERR_TOO_MANY_REDIRECTS erzeugt. Im Browser sieht man es sofort, weil die Seite nicht lädt. Bei der Unteranfrage eines Workers sieht es niemand. Der Worker fängt den Fehler ab, der Besucher bekommt seine Antwort, und die einzige Spur ist eine Logzeile.

Warum die anderen Domains in Ordnung waren

Auf den anderen Domains läuft der Worker in einer Zone, die auf Full (strict) steht, also verlässt seine Anfrage an den Datenbank-Hostnamen ihn als HTTPS und kommt als HTTPS an. Der Code des Workers war überall identisch. Die Datenbank auch. Der einzige Unterschied war ein Auswahlfeld in den Einstellungen einer Zone, gewählt lange bevor es einen Worker gab.

Deshalb hat die Suche gedauert. Ich habe Code, Bindings, Routen und Secrets zwischen einer funktionierenden Domain und der kaputten verglichen, und sie stimmten Zeile für Zeile überein.

Die Lösung

Im Cloudflare-Dashboard: die Zone, dann SSL/TLS, dann Übersicht, und den Verschlüsselungsmodus auf Full (strict) stellen. Damit verbindet sich Cloudflare über HTTPS mit dem Ursprungsserver und prüft dessen Zertifikat, und die HTTPS-Anfrage eines Workers bleibt HTTPS. Nichts hat mehr einen Grund umzuleiten, und die Schleife ist weg.

Full (strict) braucht ein gültiges Zertifikat auf dem Ursprungsserver. Hat der Server keines, ist ein Cloudflare Origin Certificate kostenlos, gilt bis zu fünfzehn Jahre und ist in zwei Minuten installiert. Es gibt keinen guten Grund mehr, eine Zone auf Flexible zu betreiben, und mehrere, davon wegzugehen: Bei Flexible ist die Strecke zwischen Cloudflare und dem eigenen Server unverschlüsselt, egal was das Schloss im Browser nahelegt.

Die Änderung wirkt binnen Sekunden. Ich habe den Modus umgestellt, die Health-Route aufgerufen, und sie sagte db_connected: true.

Bevor du umschaltest

Eine Warnung. Der Modus gilt für jeden Hostnamen der Zone. Hat irgendein anderer Ursprungsserver in dieser Zone wirklich kein Zertifikat, fällt er in dem Moment aus, in dem Cloudflare auf HTTPS besteht. Geh also zuerst die DNS-Einträge der Zone durch und stelle sicher, dass jeder Server hinter dem Proxy auf Port 443 antworten kann. Das lässt sich vom Terminal aus prüfen, ohne durch Cloudflare zu gehen:

curl -sI --resolve example.com:443:203.0.113.10 https://example.com/ | head -1

Das fragt die IP-Adresse des Ursprungsservers direkt nach dem Hostnamen. Eine Statuszeile heißt, der Server spricht für diesen Namen HTTPS. Ein Verbindungsfehler heißt, er tut es nicht, und dieser Hostname braucht ein Zertifikat, bevor die Zone umziehen kann.

Die Prüfung, die dazugekommen ist

Die Health-Route gab es schon. Es hat nur niemand gefragt. Wenn der Worker jetzt an eine neue Domain gehängt wird, ist der letzte Schritt eine Zeile:

curl -s https://t.example.com/health

Sie muss db_connected: true sagen. Ein Worker, der antwortet, beweist nur, dass der Worker läuft. Die Health-Zeile beweist, dass er erreicht, wohin er schreibt, und zwar durch die Zone, in der er tatsächlich sitzt.

Der SSL-Modus ist seitdem auch das Erste, was ich mir ansehe, wenn ein Worker sich auf einer Domain anders verhält als auf den anderen. Es ist eine Einstellung vom ersten Tag der Zone, und niemand erinnert sich, sie gewählt zu haben.