name: oauth description: OAuth2-proxy authentication setup, configuration, and troubleshooting for the K3s cluster.
OAuth / Authentication
Architecture
- Provider: GitHub via oauth2-proxy (lightweight, ~128Mi vs ~2GB for Authentik/Keycloak)
- Flow: Cloudflare Access (wildcard
*.gkcluster.org) → oauth2-proxy gateway → backend services - No native OIDC — Cloudflare Access + oauth2-proxy only (branch
external-auth-n-zwas parked)
Configuration
- oauth2-proxy enabled in
kubernetes-services/values.yaml - Reusable ingress sub-chart at
kubernetes-services/additions/ingress/supportsoauth2_proxyannotation mode - Services behind OAuth get ingress annotations pointing to the oauth2-proxy auth endpoint
MCP servers need a different auth model — do NOT try to reuse oauth2-proxy
Non-browser MCP clients (claude.ai's connector, Claude Code
--transport http) cannot authenticate through this cluster's
standard pattern. Two reasons:
- Cloudflare Access challenges with Google SSO — solvable only in a browser.
- oauth2-proxy is cookie-based forward-auth — it's not an OAuth 2.1 authorization server, so MCP clients can't run a token flow against it.
The established pattern for MCP servers in this cluster (open-brain-mcp historically; thoth going forward) is:
- The MCP server embeds an OAuth 2.1 authorization server (RFCs 8414 + 9728 + 7591 DCR + 7636 PKCE) and issues its own JWTs with GitHub as the upstream IdP.
- The MCP hostname (
brain.gkcluster.org,thoth.gkcluster.org) needs a Cloudflare Access bypass policy so the in-pod OAuth flow isn't shadowed by Google SSO. See/cloudflareskill. - The MCP server's 401 response on
/mcpmust includeWWW-Authenticate: Bearer resource_metadata="<host>/.well-known/oauth-protected-resource"— without this header, MCP clients silently fail to discover the auth server and just give up. - Single-replica MCP pods can keep PKCE state and DCR
client_ids in-memory; clients re-register transparently on pod restart. - Reference implementation:
gilesknap/open-brain-mcp/oauth.py(provider-agnostic). When standing up the next MCP service, port the structure rather than reinventing.
Gotchas
- Chrome caching can cause stale redirects/blank pages after auth changes — fix with
chrome://settings/reset - SealedSecrets for oauth2-proxy credentials must match name+namespace exactly (encryption is bound)
- Merging into existing secrets requires
sealedsecrets.bitnami.com/managed: "true"annotation on target - ArgoCD Dex audiences are hardcoded —
server.additional.audiencesdoes nothing for Dex. Override theargo-cdclient index.configwithtrustedPeersinstead. Seeadditions/argocd/README.md. oidc.configdisables Dex — havingoidc.configin argocd-cm causesIsDexDisabled()=true. Usedex.configonly.- Re-sealing secrets requires pod restart — pod env vars from
secretKeyRefare read at startup. After rotating withjust seal-argocd-dex <subcommand>, restart affected pods. - Dex secret needs ArgoCD label — the
argocd-dex-secretSealedSecret template must includeapp.kubernetes.io/part-of: argocdlabel. Without it, ArgoCD's$secret:keyresolution index.configsilently fails, passing literal key names as OAuth client IDs (→ GitHub 404). - Dex static client secrets must all be in argocd-dex-secret —
dex.configuses$argocd-dex-secret:keyfor each static client. Missing keys silently resolve to empty, causing "Failed to get token from provider" on login. The rebuild path (scripts/seal-from-json) seals all clients (grafana, open-webui, argocd-monitor) along with the matching service-side secrets in one pass. - Sidecar oauth2-proxy cookie clash — the shared oauth2-proxy sets
cookie-domain=.gkcluster.org, so its_oauth2_proxycookie reaches all subdomains. Any service with its own oauth2-proxy sidecar (e.g. argocd-monitor) must use--cookie-name=<unique>to avoid validating the shared proxy's cookie with its own secret (→ infinite login loop). - oauth2-proxy
email_domainsmust be[]— the Helm chart defaults toemail_domains = ["*"], which allows any GitHub user through and silently overridesauthenticatedEmailsFile. The fix isconfig.configFilewithemail_domains = []. This bug was found and fixed in PR #279 — do not remove theconfigFileoverride. - oauth2-proxy
cookie-secretsize — must be exactly 16, 24, or 32 bytes for the AES cipher.base64.b64encode(token_bytes(32))produces 44 chars and crashes oauth2-proxy. Usesecrets.token_hex(16)(32 hex chars = 32 bytes). This bug has regressed before — do not change the generation inscripts/seal-argocd-dex. See/sealed-secretsskill. - DEX duplicate
argo-cdstatic client — ArgoCD auto-generates anargo-cdDEX client (withouttrustedPeers). Ourdex.configalso declares one (withtrustedPeers: [argocd-monitor]). DEX v2.45+ stores the first and drops the duplicate, sotrustedPeersnever takes effect. Fixed in PR #297 by addingoidc.configwithallowedAudiences: [argo-cd, argocd-monitor], which lets argocd-monitor authenticate as itself. The duplicateargo-cdclient index.configis harmless but redundant — kept for clarity. - Dex/Grafana need restart after re-sealing — pods that read secrets
via
envFromorsecretKeyRefcache values at startup. After--tags clusterorjust seal-argocd-dex, runjust restart-dexandkubectl rollout restart sts grafana-prometheus -n monitoring. Without this, Dex reports "invalid client_secret" even though the Secret objects match. See/sealed-secretsfor the full namespace list. - Ingress auth-url must be cluster-internal — the ingress sub-chart's
auth-urluses the internal service (oauth2-proxy.oauth2-proxy.svc). Using the external domain resolves via Cloudflare to IPv6, which is unreachable from the cluster, causing intermittent 500s on all oauth2-protected ingresses. - Dex base URL redirects —
/api/dex301s to/api/dex/which returns- OIDC clients that don't follow redirects (e.g. Open WebUI's authlib)
need the full discovery URL:
.well-known/openid-configuration.
- OIDC clients that don't follow redirects (e.g. Open WebUI's authlib)
need the full discovery URL:
- Grafana 12.x requires
[users].allow_sign_up— the per-providerallow_sign_upunder[auth.generic_oauth]is not sufficient alone. Also set[auth].disable_signup_form: trueto block manual signup.
Key Files
kubernetes-services/values.yaml— oauth2-proxy toggle and configkubernetes-services/additions/ingress/— reusable ingress with auth modeskubernetes-services/templates/— ArgoCD Application CRDs