Im Rahmen eines privaten Projekts habe ich mich genauer mit einem Modul für nginx beschäfigt. Dieses erlaubt es, zusätzlich Authentifizierung für Webanwendungen zu implementieren, ohne dass diese Anwendungen selbst geändert werden müssen - etwa wenn Drittanwendungen zusätzlich geschützt werden sollen.
Alle Konfigurations-Beispiele und Skripte sind auch über das Code-Repository für diesen Blogpost herunterladbar.
Warum über nginx?
Nicht immer hat man die Möglichkeit, Anwendungen anzupassen. In meinem Fall will ich den Zugriff auf eine Software nochmal zusätzlich absichern, da diese nur für meine persönliche Nutzung gedacht ist.
Die betroffene Anwendung selbst hat zwar bereits eine eigene Authentifizierung, der Ansatz per auth_request schützt jedoch zuverlässig auch gegen etwaige Sicherheitsprobleme in der Anwendung bzw. deren API. Ich bin in diesem Punkt etwas paranoider, da andere Systeme wie Wordpress häufig über deren (öffentlichen) APIs angegriffen wurden.
Überlegungen
Das auth_request-Modul ist recht flexibel, da es die eigentliche Authentifizierung an einen selbst definierbaren Webservice delegiert wird.
Im Blogpost welcher mich inspiriert hat wurde Vouch Proxy vorgeschlagen, eine fertige Lösung welche OAuth als Backend nutzt. Da ich jedoch gerne unabhängig von Dritten bin für eigene Infrastruktur, baue ich den Dienst selbst :-).
Der eigene Authentication-Dienst
Für die ersten Tests habe ich eine simple Flask-Anwendung gebaut, welche ein Login-Formular bereitstellt und mit statischen Usern arbeitet. Den Code der Anwendung ist vollständig im Code-Repository für diesen Blogpost verfügbar, ich gehe hier daher nur auf Ausschnitte ein.
Der Dienst stellt zwei HTTP-Endpunkte bereit: Zum einen den Benutzer-Login (/
), zum anderen den Validierungs-Endpunkt für nginx (/validate
).
Bei ersterem ist der Fantasie (bzw. den Anforderungen) kaum Grenzen gesetzt: Egal ob als Backend eine Datenbank oder z.B. LDAP genutzt wird, oder Funktionen wie 2FA integriert werden - alles ist möglich.
Der Validierungs-Endpunkt wird bei jedem Request den nginx erhält durchgeführt, und muss daher entsprechend performant sein. In meinem Beispiel verwende ich die itsdangerous-Bibliothek, welche es erlaubt Werte mit einem privaten Schlüssel zu signieren, und später wieder zu prüfen. Als Wert für den Cookie verwende ich im Beispiel den Benutzernamen. Dieser wird unter /validate
als HTTP-Header zurück gegeben, was auch an die Anwendung durchgereicht werden kann.
Ein weiterer Vorteil dieses Ansatzes ist es, dass er stateless ist: Es ist keine Verwaltung der Tokens in einer Datenbank notwendig, und die Reaktionszeiten der Validierung sind dadurch stabil.
Boilerplate
Im ersten Schritt gilt es, eine Reihe von Modulen zu importieren sowie mehrere globale Objekte zu erzeugen.
Zur Erzeugung von zufälligen Tokens und Passwörtern ist openssl recht praktisch:
Login-Endpunkt
Dieser Endpunkt wird aufgerufen, wenn Benutzer ohne gültigen Cookie von nginx auf unseren Webdienst umgeleitet werden. In unserem Fall ziehen wir die Benutzerinformationen aus dem statischen Objekt von oben, in einer realen Anwendung müssten wir die Infos aus einer Datenbank laden.
Ein mittlerweile häufiger Sicherheits-Vorschlag ist es, die beiden Meldungen für "Benutzer unbekannt" und "Passwort falsch" zusammenzufassen, um einem Angreifer nicht die Info zu geben ob ein Benutzer existiert oder nicht. In diesem Fall ist mir die Bedienbarkeit jedoch wichtiger als ein Prozent mehr an Sicherheit. In meiner Implementierung habe ich sowieso erzwingend TOTP integriert, was Brute Force praktisch unmöglich macht.
Als Wert für den Sitzungs-Cookie wird der signierte Benutzername verwendet, an sich interessiert in diesem Fall nur ob ein Benutzer authentifiziert ist - oder eben nicht. Je nach Anforderung könnte man jedoch auch z.B. ein JSON-Objekt mit zusätzlichen Informationen verwenden.
Validierungs-Endpunkt
Der Endpunkt zur Validierung ist ziemlich kurz und besteht primär aus dem Laden des Cookies sowie der Validierung dessen.
Die verschiedenen Rückgabe-Texte habe ich primär fürs Debugging integriert, da der Endpunkt in der Regel nicht öffentlich verfügbar ist, können Benutzer dessen Wert niemals einsehen.
Optional könnten mit der Rückgabe weiter HTTP-Header gesetzt werden, welche dann in nginx als Variablen zur Verfügung stehen. Dies erlaubt es, dass Authentifizierungs-Informationen an den geschützten Dienst übergeben werden.
Konfiguration von nginx
Der folgende Block kann als eine einzelne Datei in der nginx-Konfiguration abgelegt werden, und dann bei Bedarf pro server {}
-Block per include
integriert werden.
Zusätzlich muss (als Subdomain) ein eigener virtueller Host konfiguriert werden, welcher den Authentifizierungs-Dienst anbietet:
Quellen und Referenzen