From cb96d5d590147c59097fe35c76a1a9c51c6babcf Mon Sep 17 00:00:00 2001 From: Viktor Barzin Date: Thu, 4 Jun 2026 03:23:59 +0000 Subject: [PATCH] fix(k8s-dashboard): use email_verified=true + groups scope mappings The apiserver rejects the email username-claim when email_verified is false (invalid bearer token 401). Authentik external/social users are unverified, so the default scope-email mapping fails. Mirror the proven kubernetes provider: use the custom 'Kubernetes Email (verified)' mapping (hardcodes email_verified=true) + 'Kubernetes Groups'. Drop the now-unneeded dual-aud mapping (apiserver trusts the k8s-dashboard issuer w/ audience=client_id) and align oauth2-proxy scope to 'openid email profile groups'. Co-Authored-By: Claude Opus 4.8 --- stacks/k8s-dashboard/authentik.tf | 49 +++++++++++++++------------- stacks/k8s-dashboard/oauth2_proxy.tf | 3 +- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/stacks/k8s-dashboard/authentik.tf b/stacks/k8s-dashboard/authentik.tf index 708f60d2..af6f2574 100644 --- a/stacks/k8s-dashboard/authentik.tf +++ b/stacks/k8s-dashboard/authentik.tf @@ -43,25 +43,28 @@ data "authentik_certificate_key_pair" "signing" { name = "authentik Self-signed Certificate" } -# Default OIDC scope mappings. `profile` carries the `groups` claim in -# Authentik's default expression, which the apiserver reads via -# --oidc-groups-claim=groups. offline_access enables refresh tokens. -data "authentik_property_mapping_provider_scope" "defaults" { - managed_list = [ - "goauthentik.io/providers/oauth2/scope-openid", - "goauthentik.io/providers/oauth2/scope-email", - "goauthentik.io/providers/oauth2/scope-profile", - "goauthentik.io/providers/oauth2/scope-offline_access", - ] +# Scope mappings — MIRROR the proven `kubernetes` provider exactly. Two are +# custom (no `managed` field) and are looked up by name: +# * "Kubernetes Email (verified)" hardcodes `email_verified: true`. REQUIRED: +# the apiserver rejects the email username-claim when email_verified is +# false (Authentik external/social users are unverified), so the default +# `scope-email` mapping (which passes through the real false) yields +# "invalid bearer token" 401s. This custom mapping is why the CLI works. +# * "Kubernetes Groups" emits the `groups` claim (scope_name=groups), so the +# client must request the `groups` scope (see oauth2_proxy.tf). +# The token `aud` defaults to the client_id (`k8s-dashboard`), which the +# apiserver's k8s-dashboard issuer trusts — no custom audience mapping needed. +data "authentik_property_mapping_provider_scope" "openid" { + managed = "goauthentik.io/providers/oauth2/scope-openid" } - -# Custom scope mapping that overrides the audience. It only fires when the -# client REQUESTS this scope, so oauth2-proxy must include -# `k8s-dashboard-audience` in its --scope (see oauth2_proxy.tf). -resource "authentik_property_mapping_provider_scope" "k8s_dashboard_aud" { - name = "k8s-dashboard audience" - scope_name = "k8s-dashboard-audience" - expression = "return {\"aud\": [\"kubernetes\", \"k8s-dashboard\"]}" +data "authentik_property_mapping_provider_scope" "profile" { + managed = "goauthentik.io/providers/oauth2/scope-profile" +} +data "authentik_property_mapping_provider_scope" "email_verified" { + name = "Kubernetes Email (verified)" +} +data "authentik_property_mapping_provider_scope" "groups" { + name = "Kubernetes Groups" } resource "authentik_provider_oauth2" "k8s_dashboard" { @@ -85,10 +88,12 @@ resource "authentik_provider_oauth2" "k8s_dashboard" { include_claims_in_id_token = true signing_key = data.authentik_certificate_key_pair.signing.id - property_mappings = concat( - data.authentik_property_mapping_provider_scope.defaults.ids, - [authentik_property_mapping_provider_scope.k8s_dashboard_aud.id], - ) + property_mappings = [ + data.authentik_property_mapping_provider_scope.openid.id, + data.authentik_property_mapping_provider_scope.profile.id, + data.authentik_property_mapping_provider_scope.email_verified.id, + data.authentik_property_mapping_provider_scope.groups.id, + ] } resource "authentik_application" "k8s_dashboard" { diff --git a/stacks/k8s-dashboard/oauth2_proxy.tf b/stacks/k8s-dashboard/oauth2_proxy.tf index 47cd8bb1..31e07bed 100644 --- a/stacks/k8s-dashboard/oauth2_proxy.tf +++ b/stacks/k8s-dashboard/oauth2_proxy.tf @@ -53,8 +53,7 @@ resource "kubernetes_deployment" "oauth2_proxy" { "--redirect-url=https://k8s.viktorbarzin.me/oauth2/callback", "--upstream=${local.oauth2_proxy_upstream}", "--ssl-upstream-insecure-skip-verify=true", - "--scope=openid email profile offline_access k8s-dashboard-audience", - "--oidc-extra-audience=kubernetes", + "--scope=openid email profile groups", "--pass-authorization-header=true", "--set-authorization-header=true", "--pass-access-token=true",