From fe3cfef90a14a0d80263faf38e0509c6ca5d9181 Mon Sep 17 00:00:00 2001 From: Ivan Okhotnikov Date: Wed, 1 Jul 2026 10:49:57 +0300 Subject: [PATCH] fix(apps/kubernetes): switch AuthenticationConfiguration username claim to email MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Real bug caught by an end-to-end OIDC login test on dev3: apiserver accepts the JWT (auth succeeds) but the RBAC check fails with 403 because the CRB `User:` subject was `alice-oidc@test3.example` (from `users[].email`) but the apiserver-visible identity was `alice-oidc` (from `preferred_username`, which in the cozy realm is the Keycloak *username*, not the email). The design proposal example used `claim: preferred_username`, but the operator-facing `users[].email` field explicitly asks for the email address. In the cozy realm — verified — `preferred_username` is the Keycloak username (`kvaps`, `i.okhotnikov`), so the two do NOT coincide. Every user whose Keycloak username differs from their email would silently 403 after a successful OIDC handshake. Switch the AuthenticationConfiguration `claimMappings.username.claim` to `email`, aligning the apiserver-visible identity with the operator-declared `users[].email`. Every conformant OIDC provider emits `email` — cozy realm does (verified), and any BYO `CustomConfig` issuer that follows OIDC does too. The switch is invisible to installers where `preferred_username` already equals the email (a common Keycloak setup); for installers where it doesn't, this fix is required for RBAC to work at all. Signed-off-by: Ivan Okhotnikov --- .../templates/oidc-authn-config.yaml | 19 ++++++++++++++++++- packages/apps/kubernetes/tests/oidc_test.yaml | 6 ++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/packages/apps/kubernetes/templates/oidc-authn-config.yaml b/packages/apps/kubernetes/templates/oidc-authn-config.yaml index 0ee8d6d49..84529dcf8 100644 --- a/packages/apps/kubernetes/templates/oidc-authn-config.yaml +++ b/packages/apps/kubernetes/templates/oidc-authn-config.yaml @@ -29,7 +29,24 @@ {{- if eq .Values.oidc.mode "System" }} {{- $issuer := include "kubernetes.oidc.systemIssuerURL" . | trim }} {{- $audience := include "kubernetes.oidc.clientId" . | trim }} -{{- $authnConfig := printf "apiVersion: apiserver.config.k8s.io/v1beta1\nkind: AuthenticationConfiguration\njwt:\n- issuer:\n url: %s\n audiences:\n - %s\n claimMappings:\n username:\n claim: preferred_username\n prefix: \"\"\n groups:\n claim: groups\n prefix: \"\"\n" $issuer $audience }} +{{- /* +User identity claim: `email`, not `preferred_username`. + +The design doc example used `claim: preferred_username`, but in the +cozystack `cozy` realm `preferred_username` is the Keycloak *username* +(`kvaps`, `i.okhotnikov`), NOT the email — and the operator-facing +`users[].email` field asks for the email address. Matching the `User:` +subject in the ClusterRoleBinding to `preferred_username` would +silently misidentify every user whose Keycloak username differs from +their email — verified on dev3 where token auth succeeded but the CRB +match failed with 403. + +Switching the claim to `email` aligns the apiserver-visible identity +with the operator's declared `users[].email` value. Requires the +issuer to emit `email` — cozy realm (verified) and every conformant +OIDC provider do. +*/ -}} +{{- $authnConfig := printf "apiVersion: apiserver.config.k8s.io/v1beta1\nkind: AuthenticationConfiguration\njwt:\n- issuer:\n url: %s\n audiences:\n - %s\n claimMappings:\n username:\n claim: email\n prefix: \"\"\n groups:\n claim: groups\n prefix: \"\"\n" $issuer $audience }} --- apiVersion: v1 kind: Secret diff --git a/packages/apps/kubernetes/tests/oidc_test.yaml b/packages/apps/kubernetes/tests/oidc_test.yaml index 27d7ada37..d1a4cad99 100644 --- a/packages/apps/kubernetes/tests/oidc_test.yaml +++ b/packages/apps/kubernetes/tests/oidc_test.yaml @@ -253,6 +253,12 @@ tests: - matchRegex: path: stringData["config.yaml"] pattern: "url: https://keycloak.example.org/realms/cozy" + # Verified end-to-end on dev3: cozy realm's preferred_username is the + # Keycloak *username* (not the email), so a CRB subject sourced from + # users[].email would silently mis-authenticate. Lock the claim name. + - matchRegex: + path: stringData["config.yaml"] + pattern: "claim: email" - matchRegex: path: stringData["config.yaml"] pattern: "- tenant-foo-kubernetes-app"