Blog

Authentifizierung bei Microservices und verteilten Systemen

Herausforderungen bei der Authentifizierung mit Microservices

Bei klassischen monolithischen Systemen ist die Authentifizierung kein Problem. Dort gibt es häufig eine Login-Seite oder ähnliches und nach dem Login wird mit Hilfe einer Session-ID wieder auf die gleiche Applikation zugegriffen.

Bei verteilten Systemen, wie bei Microservice-Architekturen, ist dies so jedoch nicht mehr möglich, da diese zentrale Komponente fehlt. Da diese unabhängig voneinander agieren und mitunter direkt angesprochen werden, muss der Microservice selbst entscheiden können, ob ein Aufruf berechtigt ist oder nicht. In diesem Blog-Artikel geht es also darum, wie das Problem der Authentifizierung und der Berechtigungsprüfung in verteilten Systemen gelöst werden kann.

Ausflug: Unterscheidung Authentifizierung, Authentisierung und Autorisierung

In diesem Blog-Artikel werden die Begriffe Authentifizierung und Autorisierung permanent verwendet. Wenn Sie die Begrifflichkeiten schon kennen, können Sie den kleinen Ausflug gerne überspringen.

Die drei Begriffe werden häufig synonym verwendet, wobei dabei jedoch einige wichtige Aspekte außen vor gelassen werden. Die Wortherkunft ignorieren wir und konzentrieren uns auf die Anwendung im IT-Umfeld.

Bei der Authentifizierung geht es darum, dass ein Benutzer auch tatsächlich der ist, der er vorgibt zu sein. Übermittelt die aufrufende Entität (also z. B. der Benutzer) seine Zugangsdaten an einen Server, ist dies die Authentisierung. Mit den übermittelten Daten kann der Server den Aufruf nun authentifizieren. Authentifizierung und Authentisierung gehören demnach zusammen. Der Einfachheit halber wird im Folgenden nur von Authentifizierung gesprochen.

Nun ist es in den meisten Anwendungen jedoch nicht ausreichend, einfach nur sicherzustellen, dass ein Aufruf von einem bestimmten Benutzer durchgeführt wurde. Häufig muss auch noch eine Art von Berechtigungsprüfung durchgeführt werden: die Autorisierung.

Die Autorisierung kann natürlich nur für authentifizierte Benutzer durchgeführt werden. Ohne sichere Authentifizierung kann deswegen auch kein sicheres Berechtigungsmanagement durchgeführt werden.

Zusammenfassend lässt sich sagen, dass Authentifizierung sicherstellt, dass ein Benutzer der ist, der er vorgibt zu sein und Autorisierung dazu dient zu gewährleisten, dass der Benutzer auch über die entsprechenden Rechte verfügt.

API-Gateway

Eine einfache Möglichkeit, sowohl Authentifizierung als auch Autorisierung zu regeln, ist der Einsatz eines API-Gateways. Im einfachsten Fall ist das API-Gateway als Proxy zu sehen, welches von den Clients angesprochen wird. Das Gateway leitet die Anfragen dann an den eigentlichen Service weiter.

Da das API-Gateway die zentrale Anlaufstelle ist, kann dies als hervorragender Punkt gesehen werden, um die Authentifizierung und Autorisierung vorzunehmen. Das Gateway stellt also sicher, dass alle Aufrufe authentifiziert sind und leitet diese nur dann weiter, wenn diese dazu berechtigt sind. Das hat den Vorteil, dass der Zugriff auf die Services sofort zurückgezogen werden kann – was bei anderen Lösungen nicht unbedingt gegeben ist.

Der Nachteil der Lösung ist, dass Änderungen im Berechtigungsmanagement auch immer am API-Gateway umgesetzt werden. Außerdem muss noch sichergestellt werden, dass die Berechtigungen nicht nur auf Funktionsebene, sondern auch auf Datensatzebene umgesetzt werden. Der Unterschied ist: Das erste stellt sicher, dass ein Benutzer nur die Funktionen aufrufen darf, für die er berechtigt ist. Bei der Berechtigung auf Datensatzebene muss außerdem sichergestellt werden, dass nur die Datensätze ansprechbar sind, für die der Benutzer autorisiert ist. Tatsächlich ist das bei unseren Pentests immer das größere Problem: die Berechtigungsprüfung auf Datensatzebene.

Mutual SSL/TLS

Dabei findet die Hilfe mit TLS und einer Zertifikatsbasierten Authentifizierung statt. Mutual SSL/TLS bedeutet also, dass sowohl der Server sich mit seinem Zertifikat authentifizieren muss (das ist das Standardszenario, z. B. auch im Browser), als auch der Client sich gegenüber dem Server.

In Bezug auf Authentifizierung zählt Mutual TLS zu den sehr sicheren Methoden. Nachteil ist vor allem die Verwaltung der Zertifikate – jeder Client muss sein eigenes Zertifikat haben, was bedeutet, dass man eine PKI-Infrastruktur aufbauen oder einkaufen muss. Hinzu kommt, dass die Zertifikate verteilt werden müssen. Aus diesem Grund ist dieses Szenario hauptsächlich für Dienste geeignet, weniger für den Einsatz mit Endnutzern.

Ein weiterer Nachteil ist, dass der Login nur dann blockiert werden kann, wenn das Zertifikat widerrufen wird. Das bedeutet, dass man Certificate Revocation-Listen pflegen und verteilen muss, oder alternativ ein OCSP-Server betrieben werden muss.

Der Einsatz von Mutual SSL/TLS bringt zwar eine sichere Authentifizierung, kümmert sich aber natürlich nicht um Autorisierung. Eine Möglichkeit hierfür besprechen wir später.

OAuth für die Authentifizierung

In massiv verteilten Umgebungen kann der Einsatz eines OAuth-Servers zur Authentifizierung verwendet werden. Im Folgenden wird die Funktionsweise von OAuth 2 besprochen.

Um OAuth zu verstehen, müssen als erstes die vorhandenen Rollen erklärt werden:

  • Resource Owner: Dies beschreibt den Benutzer, dem eine bestimmte Ressource gehört. Im einfachsten Fall ist dies der Benutzer.
  • Resource Server: Der Server verwaltet die zu schützende Ressource und kann – mit Hilfe eines Access Tokens – den Zugriff darauf gewähren.
  • Client: Der Client ist derjenige, der auf die Daten des Resource Owners zugreifen möchte. Dies könnte beispielsweise ein Microservice sein oder eine extern gehostete Applikation.
  • Authorization Server: Dieser übernimmt die Authentifizierung und die Erstellung der zugehörigen Token. Er legt auch den Scope fest, sagt also, auf welche Resourcen zugegriffen werden darf.

Wikipedia hat ein schönes Beispiel, das den Ablauf und die Funktionsweise beschreibt: Ein Benutzer (Resource Owner) fragt einen Druckdienst an (Client), um Fotos drucken zu lassen. Diese Fotos liegen auf einem Fotodienst (Resource Server). Die Authentifizierung läuft nun mit folgendem Muster ab:

  1. Der Benutzer (Resource Owner) fragt den Druckdienst (Client) an, ob er seine Fotos drucken kann.
  2. Der Druckdienst leitet den Benutzer an den Authorization Server weiter, der die Authentifizierung durchführt und ein Access Token generiert.
  3. Das Access Token wird im nächsten Schritt an den Druckdienst, also den Client, übermittelt.
  4. Dieses Token kann der Client nun nutzen, um auf den Resource Server – den Fotodienst – zuzugreifen und die Fotos des Benutzers auszulesen. Nochmal zur Klarheit: Der Client ist der Druckdienst, nicht der eigentliche Benutzer.

Wer tiefergehende Informationen möchte, kann sich den oben erwähnten Wikipedia-Artikel durchlesen.

OAuth ist zwar ein wenig komplex, spielt seine Stärken aber vor allem dann aus, wenn Anwendungen miteinander arbeiten, die nichts miteinander zu tun haben. Ein bekanntes Beispiel ist der Einsatz der Google- oder Facebook-Authentifizierung, die auch in der eigenen Applikation verwendet werden kann.

Es gibt jedoch – abgesehen von der Komplexität – noch zwei weitere Nachteile. Zum einen haben die Access Token eine gewisse Gültigkeit. Solange diese gültig sind, ist es nur schwer möglich, den Zugriff zu entziehen. Aus diesem Grund darf die Gültigkeit der Tokens nicht sonderlich lange gewählt werden.

Ein zweites Problem ist, dass keine Zustandsinformationen übertragen werden können. So kann zwar mit OAuth sichergestellt werden, dass nur die Dienste angesprochen werden können, auf die der Resource Owner die Berechtigung erteilt hat. Ein wirkliches Rollen- und Rechte-Konzept oder eine Mandantentrennung lässt sich damit aber nur schwer realisieren. Hierfür können dann beispielsweise JSON Web Tokens verwendet werden, die im nächsten Kapitel angesprochen werden.

Eine Liste mit Bibliotheken und Servern ist auf der OAuth-Community Seite zu finden.

JSON Web Token (JWT)

JSON Web Token sind in RFC 7519 als offener Standard definiert. Sie dienen dazu, „Claims“ zu übermitteln. Ein Claim dient – vereinfacht gesagt – dazu, eine Aussage über sich selbst zu treffen. Ein mögliches Beispiel ist die Aussage: „Ich verfüge über die Rolle Redakteur“. Das JWT dient dazu, diesen Claim in maschinenlesbare Sprache zu übersetzen. Das Token wird beim Login vom Authentifzierungsserver generiert und an den aufrufenden Client übergeben. Bei jedem weiteren Aufruf an die Microservices wird das Token dann übermittelt, damit dieser die Authorisierung durchführen kann.

Der Aufbau des JWT ist denkbar einfach:

  • Header: Definiert den Typen und die eingesetzten kryptographischen Algorithmen
  • Payload: Die Payload enthält die eigentlichen Claims
  • Signature: Sicherheitstechnisch gesehen der wichtigste Teil: Das JWT ist durch kryptographische Signaturen vor Manipulation geschützt.

Damit die Informationen stressfrei über HTTP übermittelt werden können, werden die einzelnen Bestandteile noch Base64 kodiert und durch Punkte voneinander abgetrennt.

Ganz bequem kodieren und dekodieren geht online unter https://jwt.io.

Es gibt ein paar standardmäßige Claims, wobei die wichtigsten im Folgenden aufgezählt sind:

  • exp: Gibt an, wie lange das JWT gültig ist (Expiration). Das ist der mit Abstand wichtigste Claim, da damit die Zeitdauer eingeschränkt werden kann, bis ein JWT neu ausgestellt werden muss.
  • nbf: Ist auch ein Zeitstempel, der angibt, ab wann ein JWT gültig ist (Not before).
  • sub: Spezifiziert das „Subjekt“ des Claims, im einfachsten Fall also den aufrufenden Benutzer.
  • iss: Steht für „Issuer“ und beschreibt, wer den Claim unterzeichnet hat.

Weitere Claims sind vor allem für größere Umgebungen relevant und werden im oben genannten RFC genauer beschrieben.

Die Signatur ist ausgesprochen wichtig und wird mittels eines HMAC erstellt. Die Generierung erfolgt denkbar einfach:

Signatur = HMAC(Base64(Header) + "." + Base64(Payload), Secret)

Als alternative Möglichkeit kann die Signatur auch asymmetrisch erstellt werden, was bei massiv verteilten Microservices von Vorteil sein kann. Allerdings muss noch darauf hingewiesen werden, dass hier besonders auf die Aktualität der eingesetzten Bibliothek geachtet wird, da es eine schwerwiegende Sicherheitslücke in der Signaturerstellung gab.

Das Secret muss natürlich zwingend geheim gehalten werden. Jeder, der über das Secret verfügt, kann damit natürlich eigene JWT herstellen. Allerdings muss das Secret natürlich bei jedem Microservice hinterlegt sein – andernfalls kann die Signatur nicht geprüft werden.

Die Payload des JWT kann nun dazu verwendet werden, alle relevanten Informationen bereitzustellen, die ein Microservice benötigt, um einerseits sicherzustellen, dass ein Benutzer authentifiziert ist. Außerdem können andererseits alle Daten übermittelt werden, sodass der Service auch die Autorisierung durchführen kann.

Um die Generierung muss man sich dank der verfügbaren Bibliotheken für nahezu alle bekannten Programmiersprachen keine Gedanken machen. Eine Liste der Bibliotheken gibt es auf der JWT.io-Website. Achten Sie jedoch darauf, dass die Bibliothek auch gleich die Expiration-Checks für Sie übernimmt.

Fazit

In verteilten Systemen müssen sowohl die Authentifizierung als auch die Autorisierung implementiert werden. Da eine zentrale, vorgelagerte Komponente wie bei monolithischen Architekturen fehlt, müssen neue Konzepte her.

Um diese zentral bereitzustellen kann ein API-Gateway verwendet werden, welches diese Aufgaben übernimmt. Ein API-Gateway hat noch andere Vorteile (z. B. API-Versionierung oder Übersetzung zur Kompatibilität), allerdings ist der Einsatz nicht immer möglich oder sinnvoll.

Mutual TLS ist ein sicheres Verfahren, jedoch nur sinnvoll in einer vorhandenen PKI zu betreiben. Nur wenn die Clients bereits über gültige Zertifikate verfügen (z. B. mit Smartcards), macht es Sinn, darüber nachzudenken. Für Server-zu-Server-Kommunikation kann es allerdings auch mit einer einfachen selbst aufgesetzten Offline-PKI umgesetzt werden.

OAuth hat sich als Standard in verteilten Systemen etabliert und wird bereits erfolgreich von „den Großen“ eingesetzt. Allerdings kommt in meinen Augen die Möglichkeit für eine sinnvolle Berechtigungsprüfung zu kurz, wie sie in Microservices benötigt werden.

Diese Lücke können unter anderem JSON Web Token (JWT) lösen. Damit können alle relevanten Informationen gesichert übermittelt werden, die benötigt werden, damit ein Microservice die Autorisierung durchführen kann.

OAuth in Kombination mit JWT ist also durchaus ein sinnvolles und empfehlenswertes Vorgehensmodel. Falls der Overhead von OAuth jedoch zu groß erscheint, kann ein eigener Authentifizierungsserver implementiert werden und die JWT unabhängig von OAuth verwendet werden. Es muss jedoch gut überlegt sein, schließlich ist die Implementierung einer sicheren Authentifizierung kein einfaches Unterfangen. Wenn Sie Beratung in Bezug auf ein geeignetes Verfahren wünschen, können wir Ihnen mit unserer Beratung zur sicheren Entwicklung weiterhelfen. Wir freuen uns auf Ihre Anfrage!

Vorheriger Beitrag
Die OWASP Top 10 für Mobile Applikationen
Nächster Beitrag
Security by Obscurity

Ähnliche Beiträge

Es wurden keine Ergebnisse gefunden, die deinen Suchkriterien entsprechen.