HTTPS, Websockets, Port-Multiplexing – wenn Apache nicht mehr reicht

Fangen wir mal mit HTTPS an. Wenn man Apache als Webserver einsetzt, heißt die Antwort auf die Frage nach HTTPS in der Regel mod_ssl. Gut integriert, featurereich, stabil. Und so weiter. Vor diesem Hintergrund mag es zunächst nicht ganz offensichtlich sein, warum wir uns nach einer alternativen HTTPS-Implementierung umsehen.

Das ist zugegebenermaßen ein bisschen von hinten durch die Brust ins Auge: Eigentlich geht’s um Websockets, die dem Apache fremd sind. Wem das nichts sagt, dem mag als erste Erklärung reichen, dass es sich hierbei um ein Protokoll handelt, das im Gegensatz zu klassischem statuslosen HTTP (Request, Response, Ende) eine persistente Verbindung zwischen Browser und Webserver offenhält, über die in beiden Richtungen kommuniziert werden kann und die aufgrund ihres binären Protokolls nur wenig Overhead hat, gerade im Vergleich mit HTTP. Das hat große Vorzüge für moderne Webapplikationen, die eben viel stärker mit dem Server interagieren – es dürfte auf der Hand liegen, dass es z.B. für einen Webmailer viel effektiver ist, wenn der Server einfach die Information „1 neue Mail“ an den Clienten senden kann, als wenn der Client alle paar Sekunden beim Server nachfragen muss, ob es etwas Neues gibt (Polling).

Hier fangen aber nun die Unsicherheiten an. Der Verbindungsaufbau einer Websocket-Verbindung läuft über HTTP (oder HTTPS), wobei ein Upgrade-Header mitgeschickt wird, der dem Server mitteilt, dass er auf das Websocket-Protokoll upgraden soll. Damit ist auch schon das primäre Problem beschrieben: Websockets laufen über den HTTP-Port 80 (oder den HTTPS-Port 443), sind aber nun mal kein HTTP, sondern benutzen HTTP nur als eine Art Startrampe. Das bedeutet im Umkehrschluss: Serverseitig wird etwas benötigt, was Websockets kann – oder zumindest ausreichend ignoriert, um sie nicht kaputtzumachen. Webserver können das aber typischerweise nicht, denn die Nutzung von Websockets ist tief in der jeweiligen Applikation verwurzelt, und die Applikationen, die mit Websockets hantieren (die beispielsweise auf node.js und socket.io basieren), sind typischerweise welche, die selbst HTTP und auch Websockets sprechen und dann entsprechend des Upgrade-Headers unterscheiden, was zu tun ist.

Wir stellen insbesondere bei unserer Hostingplattform Uberspace.de fest, dass der Apache im Grunde immer weniger als Webserver genutzt wird, sondern eher als eine Art Applikations-Multiplexer, der eingehende Requests an eigenständig laufende Applikationen weiterleitet. Denn machen wir uns nichts vor: Alle jüngeren Entwicklungen im Web-Umfeld laufen auf solche Setups hinaus und unterstützen dann verschiedenste Deployment-Möglichkeiten – welcher Webserver dann als Frontend eingesetzt wird, tritt zunehmend in den Hintergrund; häufig ist es dann auch kein Apache mehr, sondern ein nginx oder ein lighttpd (was für uns keine Option darstellt, weil wir eben auch noch viele User mit „klassischen“ Webapplikationen haben, bei denen ein Bündel PHP-Dateien irgendwo unterhalb des DocumentRoots abgelegt wird und wo Apaches .htaccess-Features häufig unverzichtbar sind). Ich will diese Entwicklung an dieser Stelle nicht bewerten; sie mag Vor- und auch Nachteile haben. In Bezug auf die Implementierung von HTTP muss ich nur an dieser Stelle einmal loswerden, dass ich schon verwundert bin, warum so viele Tools ihre eigenen HTTP-Stacks implementieren. HTTP ist nämlich durchaus überraschend komplex, und selbst im ziemlich reifen Apache werden bis heute Bugs aufgedeckt (wie kürzlich die allzu sorglose Behandlung fehlerhafter Range-Header, die einen Apache zum Absturz bringen konnte), bei denen ich mich als Applikationsentwickler eher darüber freuen würde, wenn ich mich um die HTTP-Implementierung nicht kümmern müsste und mich stattdessen über ein schmales, strenger definiertes Interface an den Webserver andocken könnte – aber als ausgewiesener Freund von FastCGI bin ich verwunderte Blicke ja bereits lange gewohnt. Vertagen wir jene Diskussion also erstmal, denn darum geht es hier nicht. Fakt ist: Immer mehr Applikationen sprechen selbst HTTP, also brauchen wir einen Weg, jene mittelfristig vernünftiger einzubinden als mit RewriteRule ... [P].

Fest steht erstmal, dass solche Applikationen, die selbst HTTP sprechen, entsprechend nicht auf Port 80 laufen können, weil der ja schon durch den Apache belegt ist. Mit zunehmender Verbreitung von IPv6 könnte man natürlich ins Auge fassen, Usern die freie Wahl zu lassen, auf Port 80 „ihrer“ IPv6-IP (bei Uberspace.de vergeben wir jedem User eine eigene) dann eben ihre eigene Applikation laufen zu lassen, aber machen wir uns nichts vor: IPv4 wird uns noch viele Jahre erhalten bleiben und für vermutlich gar nicht mal so kurze Zeit auch noch unverzichtbar sein. Und da wir bei IPv4 keine separaten IPs vergeben, bleibt hier also nur jenes Multiplexing, bei dem ein HTTP-Frontend die Requests verteilt. Und da wird der Umstand, dass der Apache keine Websockets beherrscht – hier also in dem Sinne, dass er sie nicht via mod_proxy an die Applikation durchreicht und die Verbindung dann auch offenhält – zunehmend zum Problem. Nicht akut, denn nur Chrome 14, Firefox 7 und IE 10 bieten derzeit Unterstützung für den aktuellen Websockets-Standard, und Libraries wie socket.io machen automatisch Fallbacks auf Polling, was ja auch funktioniert. Aber die Frage nach Websockets ist zumindest ausreichend akut, dass wir uns schon heute damit beschäftigen, Lösungen zu finden, die über Hacks und Workarounds hinausgehen.

Den Apache werden wir nicht los, und das wollen wir auch gar nicht, schon aus den oben genannten Gründen, insbesondere wegen seiner .htaccess-Features. Aber er taugt wohl kaum als künftiges Frontend auf Port 80. Dort brauchen wir etwas, was sozusagen Port-Multiplexing macht, und idealerweise auch nur das – da die Applikationen mit ihren ganzen Features erst hintendran laufen, sollte das Frontend bitte besonders schlank und flink sein und durch eine minimale Codebasis auch weitaus einfacher auf mögliche Schwachstellen untersuchbar sein als ein Moloch wie Apache.

Was sich uns zuerst als vielversprechende Alternative in den Weg gestellt hat, war Varnish, und um es gleich vorwegzunehmen: Wir haben Varnish auch nach wie vor im Auge, primär aufgrund seines hervorragenden Rufs als „accelerating cache“. Websockets sind mit Varnish ganz offiziell kein Problem.

Bemerkt Varnish einen Upgrade-Header, öffnet es (geeignete Konfiguration vorausgesetzt) eine Pipe zur Zielapplikation und hält sich bei der weiteren Kommunikation einfach raus – es reicht alles unverändert durch. Das Problem: Varnish beherrscht kein HTTPS, und zwar mit Absicht. Wer mehr darüber lesen will, kann sich diesen launigen Rant des Varnish-Autors zu Gemüte führen, dessen sonstige random thoughts nebenbei auch sonst mehr als lesenswert sind. Seine Empfehlung: Man möge doch bitte einfach einen SSL-Proxy wie nginx oder Pound vor Varnish setzen, was dann HTTPS macht. Gut, das klingt erstmal machbar. nginx kann derzeit noch nicht mit Websockets umgehen (nur via TCP, was dann aber wieder eine eigene IP oder einen eigenen Port braucht und somit nicht auf Basis von Hostnamen oder URLs funktioniert). Zwar gibt es derzeit kein offizielles Statement, inwieweit Pound mit Websockets zurechtkommt, aber zumindest einer gibt an, dass diese Kombination prima funktioniere, so dass wir das für den Moment mal als gegeben hinnehmen, bis wir es selbst getestet haben.

Je länger wir uns mit Pound beschäftigen, desto charmanter erscheint uns diese Lösung – und zwar auch unter dem Aspekt, dass es damit problemlos möglich ist, „das ganze SSL-Zeugs“ unter einer autarken User-ID laufen zu lassen, in einer chroot-Umgebung, und damit gut vom Rest des Systems abgeschottet – im Gegensatz zu mod_ssl, bei dem potentielle Sicherheitslücken immer das Risiko bieten, die Berechtigungen des Apache-Users und damit zumindest Leserechte auf verdammt vielen Sachen zu erlangen.

Dazu bietet Pound eben die Möglichkeit, Verbindungen anhand einer Vielzahl von Kriterien – primär seien hier reguläre Ausdrücke auf die aufgerufene URL sowie auf HTTP-Header genannt – an unterschiedliche Backends weiterzuleiten. Es wäre damit also kein Problem, Aussagen wie „Verbindungen zu www.userdomain.tld an Port 8012 schicken“ oder „Verbindungen zur URL /pad von www.userdomain.tld an Port 8012 schicken“ in einer Pound-Konfiguration auszudrücken, was letztlich Möglichkeiten eröffnet, hier für User ein kleines Interface zu bauen, mit dem sie ihre Hostnamen und ggf. auch die URL-Pfade ihrer Hostnamen selbstständig auf Ports ihrer Wahl routen könnten, was ein hohes Maß an Flexibilität eröffnen und den Support entlasten könnte, der sich in Bezug auf solche Applikationen vor allem auf die wiederkehrende Erläuterung von Proxy-RewriteRules und der Erklärung der Frage „Wieso geht das mit den Websockets nicht“ konzentriert und damit zunehmend Leidensdruck erzeugt.

Und schließlich könnten wir noch eine weitere Fliege mit der gleichen Klappe schlagen: Pound beherrscht ab der Version 2.6 – die zwar noch als experimentell gilt, aber bereits mehrere Releases erfahren hat – auch Server Name Indication (SNI), was Voraussetzung dafür ist, mehrere SSL-Zertifikate auf einer einzelnen IP zu betreiben – bei IPv4 herrscht hier ja nun eben Knappheit, und wir würden durchaus gerne viel lauter „Nutzt doch HTTPS mit euren eigenen Zertifikaten!“ rufen, wenn das nicht derzeit noch regelmäßig damit verbunden wäre, dafür IPv4-IPs zu verbrauchen, was wir gerne vermeiden würden – eine höchst ärgerliche Zwickmühle.

Nun ist der SNI-Support bei Pound noch ein wenig eingeschränkt, weil bei der Auswahl eines passenden Certs ausschließlich nach dem Common Name (CN), nicht aber nach den Subject Alternative Names gematcht wird, die mittlerweile durchaus sehr verbreitet sind – nicht zuletzt bei den SSL-Zertifikaten von StartCom, die wir auf unseren Uberspace-Hosts einsetzen. Hier prüfen wir gerade Möglichkeiten, mit einem entsprechenden Patch diese Funktionalität noch zu erweitern. Wenn’s klappt, könnten wir damit die Möglichkeit bieten, beliebige Stückzahlen von SSL-Zertifikaten einfach in unsere Konfiguration einzubauen – und zwar ohne Apache-Restarts, die ja letztlich immer dazu führen, dass alle aktuellen FastCGI-Prozesse (und damit auch PHP-Interpreter) gestoppt werden und neu gestartet werden müssen, weshalb Apache-Restarts immer möglichst zu vermeiden sind.

Und Varnish? Bleibt interessant, aber vor allem wegen seiner Caching-Fähigkeiten. Als reines Frontend und als Multiplexer tut’s Pound auch alleine – und wenn sich herausstellt, dass es bei Pound mit den Websockets nicht hinhaut, dann müssen wir sowieso nochmal nach einer Alternative suchen, denn Varnish allein tut’s ja auch nicht – wegen fehlendem SSL-Support. Allein wegen seiner vielfältigen Routing-Fähigkeiten und wegen seines SNI-Supports könnte Pound aber schon heute einen wichtigen Beitrag zu leisten, unser Hosting bei Uberspace.de besser zu machen.

8 Antworten auf „HTTPS, Websockets, Port-Multiplexing – wenn Apache nicht mehr reicht“

  1. Wow, was fuer ein Beitrag und das in deutscher Sprache! Ich bin hell auf begeistert, wie sehr sich mit dem Thema auseinander gesetzt wurde und wie gut dieses hochprozentige Konzentrat zu lesen ist. Danke fuer den guten Job und die Muehe!

  2. Hallo,
    Danke für den Einblick ins Rechenzentrum!
    Ich finde es super, wenn die Leute, die meine Webseite ein Heim geben, kompetent sind und wissen was sie tun, und noch besser, wenn sie ihr Wissen auch teilen.

    Wenn Du kein Bock mehr auf Hosting hast, kannst du als Redakteur bei Heise weiterschreiben… 😉

    Mit besten Grüßen
    Marius

    1. Das müsstest du bitte etwas ausführen. Worauf genau beziehst du dich da? Bei den New Features sehe ich ad hoc erstmal nichts, was in diese Richtung ginge – abgesehen davon, dass der Apache als generalisierter Proxy-Server ja ohnehin bei weitem nicht so eine gute Figur macht wie Software, die nur als Proxy fungiert. Aber vielleicht hat sich das in 2.4 ja auch geändert..?

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.