oauth2-proxy¶
A single oauth2-proxy Deployment gates every admin UI in the cluster (Argo CD, Grafana). It speaks the Google OAuth dance, plants a .tomoda.life cookie, and hands a verdict back to Traefik via forwardAuth.
Installed by k8s/envs/dev/sys/oauth2-proxy/application.yaml. A standalone values.yaml exists at k8s/envs/dev/sys/oauth2-proxy/values.yaml but the active configuration is the inline helm.values block in application.yaml — that's what Argo CD uses.
Chart and source¶
| Field | Value |
|---|---|
| Helm chart | oauth2-proxy |
| Repository | https://oauth2-proxy.github.io/manifests |
| Version | 7.7.1 |
| Destination namespace | sys (created by Argo CD) |
| Argo CD Application | oauth2-proxy |
| Public hostname | auth.tomoda.life |
The proxy itself sits behind Traefik with cert-manager-issued TLS at auth.tomoda.life, and its session store is Redis in the data namespace (redis://redis-master.data.svc.cluster.local:6379) — sessions survive proxy restarts and scale across replicas.
Configuration¶
Key arguments from application.yaml:
extraArgs:
- --provider=google
- --email-domain=tomoda.life
- --cookie-domain=.tomoda.life
- --whitelist-domain=.tomoda.life
- --cookie-secure=true
- --reverse-proxy=true
- --redirect-url=https://auth.tomoda.life/oauth2/callback
- --session-store-type=redis
- --redis-connection-url=redis://redis-master.data.svc.cluster.local:6379
--provider=googleplus a Google OAuth client (the same brand used by Argo CD's Dex and Grafana'sauth.googleblock).--email-domain=tomoda.lifemeans only@tomoda.lifeGoogle accounts can sign in. Other Google identities are rejected after the OAuth callback.--cookie-domain=.tomoda.lifemakes the session cookie valid for every subdomain — sign in once atargo-app.tomoda.lifeand the same cookie is honoured atgrafana.tomoda.life.
Traefik integration¶
oauth2-proxy is wired into Traefik using two Middleware CRs in the sys namespace (k8s/envs/dev/sys/manifests/middleware.yaml):
# forwardAuth: ask oauth2-proxy whether the request is authenticated
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: oauth2-proxy-auth
namespace: sys
spec:
forwardAuth:
address: "http://oauth2-proxy.sys.svc.cluster.local:80/oauth2/auth"
trustForwardHeader: true
authResponseHeaders:
- X-Auth-Request-User
- X-Auth-Request-Email
- Set-Cookie
# errors: 401/403 -> redirect to /oauth2/start?rd=<original-url>
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: oauth2-proxy-errors
namespace: sys
spec:
errors:
status: ["401-403"]
service:
name: oauth2-proxy
port: 80
query: "/oauth2/start?rd={url}"
Protected Ingresses include both middlewares as an annotation:
traefik.ingress.kubernetes.io/router.middlewares: sys-oauth2-proxy-errors@kubernetescrd,sys-oauth2-proxy-auth@kubernetescrd
The order matters: errors runs first so unauthenticated requests are turned into a redirect to Google, and auth then verifies the session on the request that comes back.
Argo CD UI gating¶
The Argo CD Ingress (k8s/envs/dev/sys/argocd-ingress/ingress.yaml) carries both middlewares at argo-app.tomoda.life. It also includes a path-level route /oauth2 that points at an ExternalName Service called oauth2-proxy-redir in the argocd namespace. That Service is one of four siblings declared in k8s/envs/dev/sys/manifests/external-services.yaml, each in a different namespace (argocd, tomoda, prod, data), all aliasing oauth2-proxy.sys.svc.cluster.local. The reason: the OAuth callback URL is registered as https://auth.tomoda.life/oauth2/callback, but the initial /oauth2/start request happens on whatever host the user was trying to reach (e.g. argo-app.tomoda.life) — that path needs to route to oauth2-proxy too, and ExternalName Services give every namespace a local handle.
See Argo CD for the full picture of how the UI is authenticated.
Grafana gating¶
Grafana uses the same two middlewares (k8s/envs/dev/sys/monitoring/values.yaml) plus auth.proxy.enabled = true on the Grafana side — Grafana trusts the X-Auth-Request-Email header that oauth2-proxy injects and auto-provisions users from it.
Operational notes¶
- Session store is Redis in
data. Ifdata/redisis down, no one can sign in. The session check is in the hot path of every protected request — keep an eye on Redis health when Argo CD or Grafana logins start failing. - Cookie secret is in
application.yaml. It's checked into Git because the inline Helm values mechanism doesn't have a great way to mix Secret-backed values with chart values. Treat it as a known-shared credential; rotating it logs everyone out. - Adding a new protected UI is two annotations and two middleware references — no oauth2-proxy config changes needed as long as the hostname is under
*.tomoda.life.