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
This commit is contained in:
Emi Griffith 2026-07-21 09:18:29 -07:00
parent 46edb0bca0
commit 7e8a484ad5
4 changed files with 71 additions and 16 deletions

View file

@ -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=

View file

@ -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-container> forgejo admin auth add-oauth \
--name google --provider openidConnect \
--key "<CLIENT_ID>" --secret "<CLIENT_SECRET>" \
--auto-discover-url https://accounts.google.com/.well-known/openid-configuration
```
### 2. Alloy agent (on every node — prod, beta, desktop)
```bash

View file

@ -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
}
}

View file

@ -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