Skip to content

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=google plus a Google OAuth client (the same brand used by Argo CD's Dex and Grafana's auth.google block).
  • --email-domain=tomoda.life means only @tomoda.life Google accounts can sign in. Other Google identities are rejected after the OAuth callback.
  • --cookie-domain=.tomoda.life makes the session cookie valid for every subdomain — sign in once at argo-app.tomoda.life and the same cookie is honoured at grafana.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. If data/redis is 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.