· Kubernetes  · 8 Min. Lesezeit

ArgoCD an Keycloak hängen: SSO für das GitOps-Werkzeug

Vom leeren Keycloak-Realm bis zur Gruppe, die in ArgoCD eine Rolle bekommt. Beide Seiten Schritt für Schritt, mit dem Mapper, an dem es hängt, und vier Stolpersteinen, darunter einer, bei dem eine Verbotsregel wirkungslos bleibt.

Vom leeren Keycloak-Realm bis zur Gruppe, die in ArgoCD eine Rolle bekommt. Beide Seiten Schritt für Schritt, mit dem Mapper, an dem es hängt, und vier Stolpersteinen, darunter einer, bei dem eine Verbotsregel wirkungslos bleibt.

ArgoCD bringt einen lokalen admin-Account mit. Zum Ausprobieren ist das praktisch, für den Betrieb ist es zu wenig. Wer ArgoCD bedienen kann, kann ausrollen, was in Produktion läuft. Solche Rechte gehören an benannte Identitäten mit klaren Rollen, nicht an ein Passwort, das im Team herumgereicht wird.

Die Anbindung hat zwei Hälften, und die meiste Zeit kostet die, über die selten geschrieben wird: Keycloak muss die Gruppen überhaupt erst ins Token schreiben. Ohne diesen Schritt meldet sich jeder erfolgreich an und niemand hat Rechte.

Dieser Beitrag geht beide Seiten der Reihe nach durch, erst Keycloak, dann ArgoCD, mit einer Prüfung nach jedem Abschnitt. Danach vier Stolpersteine. Der zweite ist der unangenehmste, weil dort eine Verbotsregel dasteht und trotzdem nichts bewirkt.

Vorab: Client-Secret oder PKCE

Die offizielle ArgoCD-Anleitung für Keycloak beschreibt zwei Wege. Die Entscheidung fällt am Anfang und ändert einzelne Schritte auf beiden Seiten.

Client-SecretPKCE
Geheimnis im Cluster nötigjanein
Client-Authentifizierung in Keycloakeingeschaltetausgeschaltet
Zusätzliche Redirect-URI für die CLIneinja, http://localhost:8085/auth/callback
Zusätzliche EinstellungkeineChallenge-Methode S256 im Reiter Advanced

Ich beschreibe hier den Weg mit Client-Secret, weil er der verbreitetere ist. Wo sich PKCE unterscheidet, weise ich darauf hin.

Teil 1: Keycloak

Schritt 1: Den Client anlegen

Im gewünschten Realm unter Clients auf Create client. Die Client-Authentifizierung wird dabei eingeschaltet, für den PKCE-Weg bleibt sie aus.

Danach die Adressen setzen, jeweils mit dem Hostnamen Ihrer ArgoCD-Instanz:

FeldWert
Root URLhttps://argocd.lab.rz.jabi-it.de
Web originshttps://argocd.lab.rz.jabi-it.de
Admin URLhttps://argocd.lab.rz.jabi-it.de
Home URL/applications
Valid Redirect URIshttps://argocd.lab.rz.jabi-it.de/auth/callback
Valid Post logout redirect URIshttps://argocd.lab.rz.jabi-it.de/applications

Beim PKCE-Weg kommt als zweite Redirect-URI http://localhost:8085/auth/callback dazu, damit die CLI-Anmeldung funktioniert, und im Reiter Advanced wird die Code-Challenge-Methode auf S256 gesetzt.

Prüfung: Der Discovery-Endpunkt des Realms muss erreichbar sein und den Aussteller nennen, den Sie später in ArgoCD eintragen.

curl -s https://keycloak.lab.rz.jabi-it.de/realms/plattform/.well-known/openid-configuration \
  | jq -r '.issuer, .authorization_endpoint'

Schritt 2: Den Client-Scope für die Gruppen bauen

Das ist der Schritt, an dem die Anbindung steht oder fällt. Keycloak schreibt Gruppen nicht von sich aus in das Token, dafür braucht es einen Mapper.

Unter Client scopes einen neuen Scope anlegen, Name groups. In diesem Scope einen Mapper hinzufügen, Typ Group Membership, und diese Werte setzen:

FeldWert
Namegroups
Token Claim Namegroups
Full group pathaus

Die Option Full group path ist der Punkt, den man kennen muss. Bleibt sie eingeschaltet, enthält der Claim den vollständigen Pfad der Gruppe statt ihres Namens. Die Zuordnung in ArgoCD passt dann nicht mehr, dazu Stolperstein 1.

Schritt 3: Scope zuweisen und Gruppe anlegen

Den Scope groups im Reiter Client scopes des Clients hinzufügen, als Default oder als Optional.

Danach unter Groups die Gruppen anlegen, die später Rollen bekommen sollen, und Benutzer hineinlegen. Für den Anfang genügt eine Admin-Gruppe.

Prüfung: Sehen Sie sich an, was tatsächlich im Token landet, bevor Sie ArgoCD anfassen. Melden Sie sich als Testbenutzer an und lassen Sie sich die Claims ausgeben.

curl -s -X POST \
  https://keycloak.lab.rz.jabi-it.de/realms/plattform/protocol/openid-connect/token \
  -d grant_type=password -d scope=openid \
  -d client_id=argocd -d client_secret="$CLIENT_SECRET" \
  -d username=testnutzer -d password="$PW" \
  | jq -r '.id_token' | cut -d. -f2 | base64 -d 2>/dev/null | jq '.groups'

Kommt hier eine Liste mit den Gruppennamen zurück, ist die Keycloak-Seite fertig. Kommt null, fehlt der Mapper oder der Scope hängt nicht am Client. Kommen Namen mit führendem Schrägstrich, ist Full group path noch an.

Teil 2: ArgoCD

Schritt 4: Das Client-Secret hinterlegen

Der einfachste Weg legt das Geheimnis in das bereits vorhandene argocd-secret, so wie es die offizielle Anleitung zeigt:

kubectl -n argocd patch secret argocd-secret \
  --patch='{"stringData": {"oidc.keycloak.clientSecret": "<client-secret>"}}'

Wer stattdessen ein eigenes Secret verwenden will, verweist mit $<secretname>:<key> darauf. Dafür verlangt die Doku zur Benutzerverwaltung allerdings, dass dieses Secret das Label app.kubernetes.io/part-of: argocd trägt. Ohne Namensteil vor dem Doppelpunkt greift ArgoCD immer auf argocd-secret zu.

Schritt 5: Die OIDC-Konfiguration

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-cm
  namespace: argocd
data:
  url: https://argocd.lab.rz.jabi-it.de
  oidc.config: |
    name: Keycloak
    issuer: https://keycloak.lab.rz.jabi-it.de/realms/plattform
    clientID: argocd
    clientSecret: $oidc.keycloak.clientSecret
    refreshTokenThreshold: 2m
    requestedScopes: ["openid", "profile", "email", "groups"]

Beim PKCE-Weg entfällt clientSecret, stattdessen kommt enablePKCEAuthentication: true hinein.

Der Wert unter issuer muss exakt dem entsprechen, den der Discovery-Endpunkt aus Schritt 1 gemeldet hat.

Prüfung:

kubectl -n argocd rollout restart deploy/argocd-server
kubectl -n argocd rollout status deploy/argocd-server

Danach zeigt die Anmeldeseite eine zusätzliche Schaltfläche für den Keycloak-Login.

Schritt 6: Gruppen auf Rollen abbilden

Das RBAC von ArgoCD besteht aus zwei Sorten Zeilen. g bindet eine Gruppe an eine Rolle, p vergibt einer Rolle ein Recht.

apiVersion: v1
kind: ConfigMap
metadata:
  name: argocd-rbac-cm
  namespace: argocd
data:
  policy.default: role:readonly
  scopes: '[groups]'
  policy.csv: |
    g, argocd-admins, role:admin
    g, argocd-developers, role:developer
    g, argocd-viewers, role:readonly

    p, role:developer, applications, get, */*, allow
    p, role:developer, applications, sync, */*, allow
    p, role:developer, applications, action/*, */*, allow
    p, role:developer, repositories, get, *, allow
    p, role:developer, projects, get, *, allow
    p, role:developer, logs, get, */*, allow
    p, role:developer, exec, create, */*, deny

Die Namen in den g-Zeilen sind genau die Gruppennamen aus Schritt 3. scopes legt fest, welche OIDC-Angaben beim Durchsetzen der Regeln herangezogen werden, laut RBAC-Doku ist '[groups]' die Voreinstellung.

Prüfung mit einem Konto, das nachweislich in der Entwicklergruppe ist:

argocd login argocd.lab.rz.jabi-it.de --sso
argocd account can-i sync applications '*/*'

Erwartet wird yes. Kommt no, greift Stolperstein 1.

Die vier Stolpersteine

1. Alle landen in der Standardrolle

Symptom: Der Login funktioniert, aber niemand hat mehr Rechte als die Voreinstellung. Die g-Zeilen scheinen wirkungslos.

Ursache: Der Wert im Token passt nicht zu dem in der g-Zeile. Zwei Varianten sind häufig. Entweder fehlt der Gruppen-Claim ganz, weil der Mapper aus Schritt 2 fehlt oder der Scope nicht am Client hängt. Oder er ist da, enthält aber den vollständigen Gruppenpfad, weil Full group path eingeschaltet blieb. Dann steht im Token /argocd-admins, in der Regel aber argocd-admins.

Prüfung: Der Token-Befehl aus Schritt 3. Er zeigt genau, was ankommt.

Lösung: Mapper anlegen oder Full group path ausschalten. Wer den Pfad braucht, kann alternativ die g-Zeilen an die Pfadform anpassen, muss das dann aber überall konsistent tun.

2. Die Verbotsregel bleibt wirkungslos

Symptom: In policy.csv steht eine deny-Zeile, und trotzdem dürfen alle das, was verboten sein sollte.

Ursache: Deny gewinnt gegen allow, aber nicht gegen die Voreinstellung. Die RBAC-Doku formuliert es unmissverständlich: Alle authentifizierten Nutzer erhalten mindestens die Rechte aus den Standardregeln, und dieser Zugriff lässt sich durch eine deny-Regel nicht blockieren.

Wer policy.default großzügig setzt und Ausnahmen per deny nachreichen will, baut eine Regel, die nie greift.

Prüfung:

kubectl -n argocd get cm argocd-rbac-cm -o jsonpath='{.data.policy\.default}'

Lösung: policy.default so eng wie möglich halten. Die Doku empfiehlt statt breiter Standardrechte eine minimale eigene Rolle für authentifizierte Nutzer. Rechte werden dann gezielt vergeben, statt breit gewährt und punktuell wieder entzogen.

3. Die Exec-Berechtigung hängt an der Kubernetes-Version

Symptom: Das Web-Terminal funktioniert nicht, obwohl die ArgoCD-Regeln es erlauben. Oder umgekehrt: es funktioniert, obwohl zusätzliche Cluster-Rechte nie vergeben wurden.

Ursache: Es sind zwei Ebenen. ArgoCD entscheidet über die eigene exec-Regel, darunter entscheidet Kubernetes. Die Doku zum Web-Terminal hält fest, dass die Aktivierung vor Kubernetes 1.31 zusätzliche Rechte braucht, nämlich create auf pods/exec. Ab 1.31 genügt das get-Recht.

Prüfung:

kubectl version -o json | jq -r '.serverVersion.gitVersion'

Lösung: Die Cluster-Version zuerst feststellen und die Rechte danach setzen. Wer eine Anleitung für ältere Cluster auf einem neueren anwendet, vergibt Rechte, die niemand mehr braucht.

4. Der Aussteller passt nicht zum Token

Symptom: Die Anmeldung bricht bei der Rückkehr aus Keycloak ab, oft mit einer Meldung, die auf den Aussteller verweist.

Ursache: Der Wert unter issuer muss dem entsprechen, der in den ausgestellten Tokens steht. Erreicht der ArgoCD-Server Keycloak intern unter einem anderen Namen als der Browser, gibt es zwei Sichten auf dieselbe Instanz, und nur eine davon passt zum Token.

Prüfung: Der Discovery-Befehl aus Schritt 1, einmal vom Arbeitsplatz und einmal aus einem Pod im Cluster heraus. Beide müssen denselben Aussteller melden.

Lösung: Denselben öffentlichen Namen für beide Wege verwenden und dafür sorgen, dass er auch aus dem Cluster heraus auflösbar ist. Die Alternative, zwei Namen dauerhaft konsistent zu halten, bedeutet zusätzliche Pflege an einer Stelle, an der Fehler erst beim Login auffallen.

Grenzen

SSO löst die Frage, wer jemand ist. Es löst nicht, was die Person darf. Das Rollenmodell müssen Sie trotzdem entwerfen, und es lohnt sich, mit wenigen Rollen anzufangen.

Ebenso wenig ersetzt SSO die zweite Leitplanke. Wer über ArgoCD synchronisieren darf, ist immer noch dadurch begrenzt, was das jeweilige AppProject zulässt. Beide Ebenen gehören zusammen gedacht.

Zur Reihenfolge: Identität und Rollen stehen vor der Erreichbarkeit von außen. Wer ein ArgoCD mit geteiltem Passwort ins offene Netz stellt, gibt damit den Zugriff auf alles her, was dieser Cluster ausrollt.

Sie wollen, dass der Zugriff auf Ihre Deploy-Werkzeuge an echte Identitäten und Rollen gebunden ist? Ich binde GitOps- und Admin-Oberflächen an Ihren zentralen Login an, mit sauberem Rollenmodell. Mehr unter IT-Sicherheit & ISMS, das Erstgespräch ist unverbindlich.

Alex Jabi, Jabi IT

Alex Jabi

Ich betreue IT, Informationssicherheit und Datenschutz für KMU und Praxen an der Bergstraße und im Odenwald, persönlich, dokumentiert und ohne Cloud-Zwang.

Mehr über mich
Zurück zum Blog

Passende Artikel

Alle Artikel »
Mattermost auf Kubernetes: Operator, Datenbank und SSO

Mattermost auf Kubernetes: Operator, Datenbank und SSO

Ein selbst gehosteter Team-Chat als Slack-Alternative, betrieben über den Mattermost-Operator. Voraussetzungen, externe Datenbank und Filestore, die Mattermost-Ressource, Login über OpenID Connect und die drei folgenreichsten Fallen.

Mattermost auf Kubernetes: die vollständige Stolperstein-Sammlung

Mattermost auf Kubernetes: die vollständige Stolperstein-Sammlung

Fünfzehn dokumentierte Fallen beim Betrieb von Mattermost über den Operator, von hängenden Init-Containern über das Write-Only-Verhalten von spec.size bis zu den Upgrade-Hinweisen aus dem v11-Changelog. Jeder Fall im Schema Symptom, Ursache, Prüfung, Lösung.