Wie wir die Seite für 56 Sekunden abgeschossen haben

In der Nacht zum 17. August war diese Seite 56 Sekunden lang nicht erreichbar. Jede Anfrage bekam einen Fehler, von 23:57:37 bis 23:58:33, dann war sie per Rollback zurück. Die Geschichte dahinter ist zu lehrreich, um sie zu verschweigen, denn der Fehlschlag hat einen älteren Fehler aufgedeckt, den wir sonst nicht gefunden hätten.

Angefangen hat es harmlos. Im Protokoll standen seit Tagen Serverfehler, alle ausgelöst von Scannern mit absichtlich kaputten Host-Headern, etwa einer offenen eckigen Klammer. Unsere Fehlerseite setzte mit request.url die vollständige angefragte Adresse in ihren Text, und dafür braucht Flask den Host-Header: bei einem kaputten warf die Bibliothek darunter einen ValueError, und aus einem harmlosen 404 wurde ein 500.

Der Fix ist eine einzige Zeile, request.path statt request.url, denn für den Text der Fehlerseite genügt der Pfad. Nachlesen kann man das im Commit dazu, der auch gleich den Test mitbringt, der so einen Header nachstellt. Denn ein Fehler, den man behebt, ohne ihn festzuhalten, kommt wieder.

Damit solche Anfragen künftig gleich am Eingang abgewiesen werden, haben wir danach eine Liste erlaubter Hostnamen aktiviert. Und da fiel die Seite um. Alle Anfragen wurden abgelehnt, auch die legitimen, weil der Host, der ankam, nicht der war, den wir erwarteten.

Der Grund war ein Fehler, der von der ersten Fassung unserer Serverkonfiguration an bestanden hatte: wir setzten Weiterleitungs-Header, die die Plattform ohnehin schon setzt. nginx schickte den Hostnamen deshalb zweimal, und was in der Anwendung ankam, war beides aneinandergehängt.

Solange niemand hinsah, war das unauffällig, denn im Alltag baut die Seite ihre Adressen relativ. Nur eine Stelle brauchte den Host wirklich und hat es die ganze Zeit sichtbar verraten: unser Kalender-Feed. Der setzt die Adresse jedes Termins mit url_for(..., _external=True), also aus genau dem Host, der angekommen ist. Heraus kam eine Adresse mit einem Komma und dem doppelten Hostnamen mitten darin. Wer den Kalender abonniert hatte, konnte den Link zum Termin nicht anklicken.

Die überflüssigen Zeilen sind weg, die Liste erlaubter Hosts ist aktiv, und die Adressen im Kalender-Abo sind erstmals korrekt. Bleibt die unangenehme Erkenntnis: der doppelte Header hat monatelang niemandem wehgetan, außer den Leuten, die unseren Kalender abonniert hatten, und gefunden haben wir ihn nur, weil ein anderer Versuch spektakulär gescheitert ist.