From 7e8a484ad51ebb28498fa5773b0b44cd37b45f57 Mon Sep 17 00:00:00 2001 From: Emi Griffith Date: Tue, 21 Jul 2026 09:18:29 -0700 Subject: [PATCH] Use dashboard.thermograph.org + Google SSO for the dashboard - Grafana served at dashboard.thermograph.org (the record now points at beta) - Google OAuth (OIDC) login, env-gated (OAUTH_ENABLED + GOOGLE_CLIENT_ID/SECRET); allow_sign_up=false so access is locked to pre-provisioned accounts, with a break-glass local admin fallback - README: Google Cloud OAuth client setup with the redirect URIs for both Grafana and Forgejo, plus the Forgejo add-oauth command --- .env.example | 17 ++++++++++++++--- README.md | 36 +++++++++++++++++++++++++++++++++--- caddy-grafana.conf | 12 ++++++------ docker-compose.yml | 22 ++++++++++++++++++---- 4 files changed, 71 insertions(+), 16 deletions(-) diff --git a/.env.example b/.env.example index 7ff6852..60c6633 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,20 @@ # Copy to .env on the monitoring host (beta) before `docker compose up`. # .env is gitignored — never commit real secrets. -# Grafana admin login (the UI's own auth; Grafana is the only thing exposed). +# The public hostname beta's Caddy serves Grafana at (see caddy-grafana.conf). +GRAFANA_DOMAIN=dashboard.thermograph.org + +# Break-glass local admin (Google SSO below is the primary login). Keep this +# strong; it's the fallback if OAuth ever breaks. GF_ADMIN_USER=admin GF_SECURITY_ADMIN_PASSWORD=change-me-to-something-strong -# The public hostname beta's Caddy serves Grafana at (see caddy-grafana.conf). -GRAFANA_DOMAIN=grafana.thermograph.org +# --- Google SSO ------------------------------------------------------------------ +# Create a Google Cloud "OAuth 2.0 Client ID" (type: Web application) and set: +# Authorized redirect URI: https://dashboard.thermograph.org/login/google +# Then paste the client id/secret here and flip OAUTH_ENABLED=true. +# Login is locked to pre-provisioned accounts (allow_sign_up=false) — provision +# your email as an admin (see README) so only you can sign in. +OAUTH_ENABLED=false +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= diff --git a/README.md b/README.md index cee80ec..5841582 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,8 @@ persistent, queryable, fleet-wide log store and UI. - **Loki** stores logs (filesystem, 30-day retention). Listens on the mesh IP `10.10.0.2:3100` — never public. -- **Grafana** is the UI, fronted by beta's Caddy at `grafana.thermograph.org` - (its own login; Loki is not exposed). +- **Grafana** is the UI, fronted by beta's Caddy at `dashboard.thermograph.org`, + login via **Google SSO** (with a break-glass local admin); Loki is not exposed. - **Alloy** runs on each node and ships three sources to Loki: every Docker container's stdout/stderr, Caddy's host access logs, and the app's structured JSON logs (`errors`/`access`/`audit` `*.jsonl`, parsed so `level`/`tag`/`phase` @@ -47,9 +47,39 @@ cp .env.example .env # set GF_SECURITY_ADMIN_PASSWORD docker compose up -d # Loki on 10.10.0.2:3100 + 127.0.0.1:3100; Grafana on 127.0.0.1:3000 ``` -Expose the Grafana UI: point `grafana.thermograph.org` at beta, append +Expose the Grafana UI: point `dashboard.thermograph.org` at beta, append `caddy-grafana.conf` to `/etc/caddy/Caddyfile`, `systemctl reload caddy`. +### Google SSO (Grafana + Forgejo) + +Both the dashboard and Forgejo log in with Google. Create **one** Google Cloud +OAuth 2.0 Client (type: *Web application*) and register both redirect URIs: + +- Grafana: `https://dashboard.thermograph.org/login/google` +- Forgejo: `https://git.thermograph.org/user/oauth2/google/callback` + +**Grafana:** put the client id/secret in `.env`, set `OAUTH_ENABLED=true`, and +`docker compose up -d`. Login is locked to pre-provisioned accounts +(`allow_sign_up=false`), so provision your email as an admin once: + +```bash +# on beta, after the stack is up (uses the break-glass admin creds from .env): +curl -s -u admin:"$GF_SECURITY_ADMIN_PASSWORD" -H 'Content-Type: application/json' \ + -X POST http://127.0.0.1:3000/api/admin/users \ + -d '{"name":"you","login":"you@gmail.com","email":"you@gmail.com","password":"'"$(openssl rand -base64 24)"'"}' +# then make that user a Grafana admin (org role Admin + server admin) via the UI +# or /api/org/users; the Google login matches on email. +``` + +**Forgejo:** add the Google auth source (once, as a Forgejo admin): + +```bash +docker exec -u git forgejo admin auth add-oauth \ + --name google --provider openidConnect \ + --key "" --secret "" \ + --auto-discover-url https://accounts.google.com/.well-known/openid-configuration +``` + ### 2. Alloy agent (on every node — prod, beta, desktop) ```bash diff --git a/caddy-grafana.conf b/caddy-grafana.conf index 641bf5f..787b816 100644 --- a/caddy-grafana.conf +++ b/caddy-grafana.conf @@ -1,15 +1,15 @@ # Append to beta's /etc/caddy/Caddyfile (beta is the monitoring host), then # `systemctl reload caddy`. Grafana publishes to 127.0.0.1:3000; this fronts it -# with TLS, same pattern as beta's other site blocks. Grafana owns its own login, -# so the UI is safe to expose publicly — but Loki (the log store, on -# 10.10.0.2:3100) is NOT proxied here and stays mesh-only. +# with TLS, same pattern as beta's other site blocks. Login is Google SSO (with a +# break-glass local admin); Loki (the log store, on 10.10.0.2:3100) is NOT proxied +# here and stays mesh-only. # -# Point grafana.thermograph.org's A record at beta (75.119.132.91) first. +# dashboard.thermograph.org's A record must already point at beta (75.119.132.91). -grafana.thermograph.org { +dashboard.thermograph.org { encode zstd gzip reverse_proxy 127.0.0.1:3000 log { - output file /var/log/caddy/grafana.log + output file /var/log/caddy/dashboard.log } } diff --git a/docker-compose.yml b/docker-compose.yml index 57a6d41..e8298cf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -27,16 +27,30 @@ services: image: grafana/grafana:11.6.1 depends_on: [loki] environment: - # Grafana owns its own auth. Admin password is injected from the host env - # (set GF_SECURITY_ADMIN_PASSWORD before `up`, or in an .env file — see - # .env.example); never bake a credential into the compose file. + # Primary login is Google SSO (below). The admin user is kept as a + # break-glass local fallback; its password comes from the host env + # (.env — see .env.example), never baked into the compose file. GF_SECURITY_ADMIN_USER: ${GF_ADMIN_USER:-admin} GF_SECURITY_ADMIN_PASSWORD: ${GF_SECURITY_ADMIN_PASSWORD:?set GF_SECURITY_ADMIN_PASSWORD} GF_USERS_ALLOW_SIGN_UP: "false" GF_ANALYTICS_REPORTING_ENABLED: "false" GF_ANALYTICS_CHECK_FOR_UPDATES: "false" # Served behind Caddy at this external URL (sub-path-safe cookie/redirects). - GF_SERVER_ROOT_URL: "https://${GRAFANA_DOMAIN:-grafana.thermograph.org}/" + GF_SERVER_ROOT_URL: "https://${GRAFANA_DOMAIN:-dashboard.thermograph.org}/" + # --- Google SSO (OIDC) ----------------------------------------------------- + # Enabled once GOOGLE_CLIENT_ID/SECRET are set in .env and OAUTH_ENABLED=true. + # allow_sign_up is FALSE: a Google login only succeeds if a Grafana user with + # that email already exists — so this dashboard is locked to pre-provisioned + # accounts (see README), not "any Google user". Redirect URI to register in + # Google Cloud: https://dashboard.thermograph.org/login/google + GF_AUTH_GOOGLE_ENABLED: ${OAUTH_ENABLED:-false} + GF_AUTH_GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID:-} + GF_AUTH_GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET:-} + GF_AUTH_GOOGLE_SCOPES: "openid email profile" + GF_AUTH_GOOGLE_AUTH_URL: "https://accounts.google.com/o/oauth2/v2/auth" + GF_AUTH_GOOGLE_TOKEN_URL: "https://oauth2.googleapis.com/token" + GF_AUTH_GOOGLE_API_URL: "https://openidconnect.googleapis.com/v1/userinfo" + GF_AUTH_GOOGLE_ALLOW_SIGN_UP: "false" volumes: - ./grafana/provisioning:/etc/grafana/provisioning:ro - ./grafana/dashboards:/var/lib/grafana/dashboards:ro