Aufbau einer sicheren, schnellen und angenehmen Nutzer-Authentifizierung

Dies ist der Erste in einer Reihe von technologie-orientierten Blogbeiträgen. Wir bei Gini sind stolz auf unseren Technologie-Stack und unsere einzigartige Kultur. Technologie-Beiträge sind leicht an dem himbeerfarbenen Header-Bild zu erkennen. Lass uns gleich eintauchen!

Ein kurzer Blick auf unsere Infrastruktur

Der Titel hat es bereits verraten: Wir nutzen Kubernetes. Unsere Systeme waren lange Zeit auf Containern aufgebaut und etwa im Dezember 2016 haben wir unser benutzerdefiniertes Ansible/Docker-Setup auf Kubernetes migriert (wir haben mit v1.3 begonnen).

Wir betreiben Cluster in Amazon EC2 und Bare-Metal-Maschinen. Die Cluster sind mit Namespaces partitioniert, um den Akademien volle Autonomie über ihre Infrastruktur zu gewähren. Zukünftige Beiträge werden unsere Infrastruktur im Detail besprechen.

 

.        .        .

 

Es gibt (mindestens) eine Sache, die alle Ginis gemeinsam haben: Ein Google-Konto.

Viele der Tools und Dienste, die wir im Alltag nutzen, erlauben es uns, uns mit Google anzumelden und das lieben wir total. Wir sind seit dem ersten Tag glückliche G-Suite-Nutzer und versuchen, wo immer möglich, Google-Logins zu verwenden.

Es vereinfacht das Onboarding und Deboarding, erzwingt 2-Faktor-Logins und reduziert die Anzahl der Passwörter und Konten, die wir uns merken und verwalten müssen.

Ein blinder Fleck in unserer Infrastruktur

Bis vor kurzem gab es eine große Ausnahme von dieser Regel: Kubernetes. Bei der anfänglichen Implementierung von Kubernetes haben wir den einfachen Weg gewählt und mit einer statischen Passwortdatei begonnen. Das funktionierte gut für die überschaubare Anzahl an Akademie-Usern in unseren Clustern, aber diese Einfachheit hatte ihren Preis:

  • Benutzer, Gruppen und Passwörter werden separat verwaltet
  • Jede Änderung an der Kennwortdatei erfordert einen Neustart aller API-Server
  • Passwörter werden von einer zentralen Instanz erstellt und an den Benutzer verteilt
  • Aktualisierungen von Berechtigungen und Gruppenmitgliedschaften erfordern in vielen Szenarien Änderungen an der Kennwortdatei und an RBAC-Definitionen
  • Die Passwortdatei wurde in einem Git-Repository verwaltet und einige wenige Benutzer konnten alle Token sehen. Dies machte RBAC weniger effektiv und ließ Spielraum für Verbesserungen beim Identitätswechsel.

Evaluation von Alternativen

Kubernetes bietet eine breite Palette von Authentifizierungsoptionen, aber für unsere Bedürfnisse kamen nur einige wenige für die weitere Evaluierung in Frage:

  • x509-Client-Zertifikate
  • OpenID-Connect-Tokens
  • Festhalten an statischen Passwortdateien

x509-Zertifikate

Auf den ersten Blick sahen x509-Zertifikate wie die perfekte Lösung aus, da wir bereits eine interne PKI (Public Key Infrastructure) betreiben. Ein späterer Beitrag wird unsere Verwendung von vault in diesem Zusammenhang behandeln. Wir verwenden TLS-Zertifikate für eine Vielzahl von Anwendungsfällen und verfügen sowohl über die Erfahrung als auch die geeigneten Tools. Das hat Vorteile auf ganzer Linie:

  • Kubernetes-Gruppenmanagement mit OUs (Organizational Units)
  • Gut verstandene und kampferprobte Technologie
  • PKI-Infrastruktur ist bereits vorhanden
  • Es ist sogar möglich, Zertifikate direkt in Kubernetes zu verwalten (allerdings nur für Kubelets)

Keine Zeit zu verlieren – lass uns das machen, … oder?

Es gibt einen Haken

Der Wechsel zu x509-Zertifikaten würde eine Reihe von Unzulänglichkeiten unseres aktuellen Ansatzes beheben, aber auch ein großes Sicherheitsproblem mit sich bringen:

Kubernetes ist derzeit nicht in der Lage, den Status eines bereitgestellten Client-Zertifikats zu überprüfen. Weder CRL (Certificate Revocation Lists) noch OCSP (Online Certificate Status Protocol) sind implementiert.

Das bedeutet, dass ein Zertifikat nicht widerrufen werden kann – und das ist für uns ein absoluter Dealbreaker. 😱

Foto von Todd Cravens auf Unsplash

Wir brauchen eine Möglichkeit, ein verlorenes oder ausgesetztes Zertifikat zu sperren und sicherzustellen, dass die aktuell gewährten Berechtigungen respektiert werden. Es gibt Möglichkeiten, Teile davon zu entschärfen (kurzlebige Zertifikate, Überspringen von Gruppen in RBAC und bleiben bei CN), aber es fühlte sich einfach nicht richtig an. So ein Mist.

OpenID Connect

Als ich mir OIDC vor einiger Zeit zum ersten Mal ansah, hat es bei mir nicht wirklich Klick gemacht und heute frage ich mich, warum. Das mag ein Rätsel bleiben…

Es war dieser Blog-Beitrag von Joel Speed, der mich wieder auf OIDC aufmerksam werden ließ – ich teilte ihn sofort mit meinen Fakultätsmitgliedern.

Am nächsten Tag haben wir uns direkt zusammengetan und dex, einen OIDC-Identitätsanbieter von den guten Leuten bei CoreOS, in unserem internen Kubernetes-Cluster implementiert. Wir waren in der Lage, den Proof-of-Concept in < 1 Stunde zum Laufen zu bringen und waren mit den Ergebnissen sehr zufrieden.

Wir haben dex so konfiguriert, dass es Google als Backing-Service verwendet und Token mit der mitgelieferten Beispielanwendung anfordert und erneuert. Nachdem wir die Konfiguration des Kubernetes-API-Servers geändert hatten, konnten wir kubectl mit unserem von Google genehmigten JWT (JSON Web Token) verwenden. Wir verließen das Büro mit einem breiten Grinsen im Gesicht.

Foto von Jonathan Daniels auf Unsplash

Wo ist der Haken?

OIDC ist nicht perfekt und hier sind einige Probleme, die wir identifiziert haben:

  • Der Zugriff kann zentral entzogen werden, wird aber von der Kubernetes-API nicht sofort beachtet. Das ID-Token hat eine TTL (Time To Live) und solange diese nicht abgelaufen ist, kann man es noch nutzen. Nach Ablauf (max. 24h) wird die Anmeldung gesperrt.
  • Man kann keine Gruppen verwenden – Google bietet sie einfach nicht an. Stattdessen müssen einzelne E-Mail-Adressen in den RBAC-Definitionen verwendet werden.
  • Abhängigkeit von einer externen Instanz: Es stimmt, dass ein Ausfall von Googles OIDC-Servern unsere Fähigkeit zur Verwaltung des Clusters beeinträchtigen könnte. Wir sehen dies als ein geringes Problem an, da wir jederzeit eine statische Passwortdatei bereitstellen können, um Zugang zu erhalten, ohne dass dies Auswirkungen auf alle im Cluster laufenden Prozesse hat.

Lasst uns die Vorteile nicht vergessen

  • Weniger zu verwaltende Konten, Gruppenmitgliedschaften und Passwörter
  • 2. Faktor (2FA) automatisch erzwungen
  • Impersonation unmöglich (oder sehr, sehr unwahrscheinlich)
  • Keine Abhängigkeit von der Ops-Fakultät zur Verwaltung von Geheimnissen oder Konten: Selbstbedienung

Vom Proof-of-Concept zur Produktion

Am nächsten Tag nahmen wir uns ein paar Stunden Zeit, um die Evaluation fortzusetzen und den perfekten Workflow für alle Gini-Kubernetes-Benutzer zu finden.

Die erste Frage, die wir beantworten wollten, war, ob die zusätzliche Schicht, die durch dex eingeführt wurde ausreichend Vorteile bietet, da wir in absehbarer Zeit nur an Googles OIDC-Implementierung interessiert waren. Wir kamen zu der Erkenntnis, dass dex für unseren aktuellen Zustand nicht benötigt wird und ließen es daher weg.

Der nächste Punkt auf unserer Liste war der Ablauf, den ein Benutzer durchlaufen musste, um die Token in die kubectl-Konfiguration zu bekommen.

Zu unserer Überraschung erwies es sich als ziemlich mühsam, ein id- und refresh-Token in die Konfiguration zu bekommen. Wir entdeckten Micah Hauslers k8s-oidc-helper und dieser leistete großartige Arbeit bei der Automatisierung von Teilen des Ablaufs. Das Kopieren von authCodes aus dem Browser in das Terminal fühlte sich für uns allerdings nicht ganz reibungslos an.

Wir 💙 Automatisierung, und der gesamte Prozess sollte so wenig Interaktion wie möglich erfordern. Wir fingen an, über eine individuelle Lösung nachzudenken und ich freue mich sehr, das Ergebnis dieser Bemühungen mit euch zu teilen: dexter.

Der Name wurde inspiriert und abgeleitet von dex und obwohl dex nicht mehr Teil unseres Stacks ist, haben wir uns entschieden, bei dem Namen zu bleiben. Das Git Repo war bereits erstellt, die Wände waren mit Flussdiagrammen und Ideen gefüllt, … nun, es war einfach zu spät für einen neuen Namen.

Inwiefern ist dexter anders?

Wir wollten die Benutzerinteraktion auf den Start von dexter und die Anmeldung bei Google beschränken. Es sollte keine weitere Benutzerinteraktion erforderlich sein. dexter erreicht dieses Ziel, indem es einen HTTP-Server auf localhost startet, der den Google-Callback des OAuth2-Web-App-Flows empfängt. Dadurch können wir die Token abrufen und die kubectl-Konfiguration ohne zusätzliche Benutzerinteraktion aktualisieren. Hier ist der vollständige Flow:

Der ganze OpenID-Connect-Flow

Ein Gif sagt mehr als tausend Worte.

dexter aktualisiert automatisch deine ~/.kube/config . Hier ist ein Ausschnitt aus der erstellten kubectl-Konfiguration:

kubectl wird automatisch den refresh-token verwenden, um den id-token zu aktualisieren, wenn es abläuft. Auch hier ist keine zusätzliche Benutzerinteraktion erforderlich.

Wir haben unsere RBAC-Konfigurationen so aktualisiert, dass sie auf den E-Mail-Adressen der Benutzer basieren.

Dein API-Server benötigt noch diese zusätzlichen Flags, um Google OIDC-Tokens zu verifizieren:

Die statischen Passwortdateien behalten?

Angesichts der Vorteile von OIDC und dexter war es eine leichte Entscheidung, unsere statischen Passwortdateien hinter sich zu lassen. Eine Sache weniger, um die man sich kümmern muss.

Fazit

Wir sind sehr zufrieden mit unserer OIDC-Einrichtung und das Feedback der internen Benutzer bei Gini war sehr positiv. Wir würden gerne Dein Feedback zu dexter hören:

 

.        .        .

 

Wenn Du bis hierher gelesen hast, schau Dir unsere offenen Stellen an. Wir sind immer auf der Suche nach exzellenten Technikern, die sich uns anschließen möchten!

Gini

The Gini Way is our place where you can learn how we do things around here. We are driven by culture, values, and creating happy people.

Wir bei Gini möchten mit unseren Beiträgen, Artikeln, Leitfäden, Whitepaper und Pressemitteilungen alle Menschen erreichen. Deshalb betonen wir, dass sowohl weibliche, männliche als auch anderweitige Geschlechteridentitäten dabei ausdrücklich angesprochen werden. Sämtliche Personenbezeichnungen beziehen sich auf alle Geschlechter, auch dann, wenn in Inhalten das generische Maskulinum genutzt wird.