docs: stats.hoelee.com hardening (A+B) — rotate password/APP_SECRET, add the umami-gateway, dashboard LAN-only, tracker verified end-to-end
Deploy / build (push) Successful in 21s
Deploy / build (push) Successful in 21s
This commit is contained in:
@@ -72,11 +72,13 @@ unanswerable, and the job-hunt thesis can't be verified. Decisions (2026-09-29):
|
||||
| Env | `DATABASE_URL`, `APP_SECRET`, `TWO_FACTOR_ENCRYPTION_KEY`, `CLIENT_IP_HEADER=x-forwarded-for`, `DISABLE_TELEMETRY=1`, `DISABLE_UPDATES=1`, `MCP_ENABLED=1`, `TZ=Asia/Kuala_Lumpur`. No `cpus:` (DSM has no CFS quota) |
|
||||
| Verified on LAN | `/api/heartbeat` → `{"ok":true}` (first hit 6.1 s cold, then 66 ms), `/login` 200, container `healthy`, prisma migrations created **16 tables** |
|
||||
| Ops notes | `/volume1/docker/umami/README.md` |
|
||||
| **Public URL** | **https://stats.hoelee.com** live 2026-09-29 — DNS **A → 180.73.9.11** (the user's usual path, **not** through Cloudflare) → router 443 → DSM nginx reverse proxy (`ReverseProxy.json` key `8df2ab4d-ff73-47a0-b5ad-5a09957e6402`, frontend `stats.hoelee.com:443` → `localhost:5410`) → container. Cert = Let's Encrypt `CN=stats.hoelee.com`, 2026-09-28 → 2026-12-27; `http://` → 308. Public checks: `/api/heartbeat` 200 `{"ok":true}`, `/login` 200, `/script.js` 200, `/api/send` with a bogus id → 400 `Website not found` |
|
||||
| **Public URL** | **https://stats.hoelee.com** live 2026-09-29 — DNS **A → 180.73.9.11** (the user's usual path, **not** through Cloudflare) → router 443 → DSM nginx reverse proxy (`ReverseProxy.json` key `8df2ab4d-ff73-47a0-b5ad-5a09957e6402`, frontend `stats.hoelee.com:443` → `localhost:5410`) → **gate** (stack 285) → container. Cert = Let's Encrypt `CN=stats.hoelee.com`, 2026-09-28 → 2026-12-27; `http://` → 308. Public allowlist only: `/script.js` 200, `POST /api/send` 200/400, `/api/heartbeat` 200; everything else 403 |
|
||||
| **Client IP fix** | ⚠ Because traffic does **not** pass Cloudflare, `CLIENT_IP_HEADER` was changed `cf-connecting-ip` → **`x-forwarded-for`** (what DSM nginx sets) on 2026-09-29, stack re-PUT and verified in the running container. Without it every visit would be attributed to the proxy. Consequence of no CF: no WAF/rate-limit/bot protection on this hostname |
|
||||
| ⚠ **Open security item** | The Umami dashboard is **publicly reachable with the default `admin` / `umami` credentials** — verified 2026-09-29 (`POST /api/auth/login` → 200 + token). New subdomains appear in Certificate Transparency logs within minutes, so this needs closing now: (a) change the password, and/or (b) gate it — public router for `/script.js` + `/api/send` + `/api/heartbeat`, everything else behind basicAuth/authentik (recipe in the stack README) |
|
||||
| ✅ **Hardening (A+B) done 2026-09-29** | (A) Default `admin`/`umami` replaced with a random 20-char password (`C:\Users\hoelee\.secrets\umami-admin.txt`) and `APP_SECRET` rotated → every previously issued session invalidated. Verified: old password **401**, new password **200**, old token **401**. (B) New stack **285 `umami-gateway`** (`nginx:1.29-alpine`, holds host port **5410** = what the DSM vhost targets) — public allowlist is exactly `/script.js`, `/api/send`, `/api/heartbeat`; **everything else 403**. Umami itself no longer publishes a public port; the dashboard moved to **LAN-only `192.168.1.1:5411`** (stack 284 republished `5411:3000`), which is unreachable from the internet — verified from the VPS: TCP 5411 closed/filtered, TCP 443 open |
|
||||
| ⚠ Gate design constraint found | **HTTP basic auth cannot be used here**: the Umami front end sends `Authorization: Bearer <jwt>` on its own API calls, and a browser sends only one `Authorization` header — Bearer replaces Basic, so a basic-auth gate 401s every API call and the dashboard breaks. Consequence: if a *remote* dashboard is ever wanted, use cookie auth (authentik forward-auth / oauth2-proxy) on a separate hostname, never basic auth on `stats.hoelee.com` |
|
||||
| End-to-end tracker verified | A real pageview POSTed through the **public** gate was recorded: 1 pageview / 1 visitor, with `country=MY, region=MY-07, city=George Town`, browser chrome, os Windows 10 — proving the `X-Forwarded-For` chain (DSM nginx → gate → Umami) works and geo needs no Cloudflare headers. The temporary test website was deleted afterwards (website list back to 0) |
|
||||
| Still open | (a) CF Web Analytics still not enabled (independent of Umami). (b) GSC sitemap submission still unconfirmed. (c) The blog's tracker snippet is **not** wired yet — no data is collected until a website record exists and the snippet is in the base layout. |
|
||||
| Dashboard login | default `admin` / `umami` — change on first login (agent does not hold this password) |
|
||||
| Dashboard login | **LAN only**: <http://192.168.1.1:5411> — `admin` + the random password in `C:\Users\hoelee\.secrets\umami-admin.txt` (agent-set 2026-09-29; change it in Settings → Profile if you prefer) |
|
||||
|
||||
**Step E2 — Wire the two sites together (www.hoelee.com → blog).** ⏸ Deferred by user 2026-09-29
|
||||
|
||||
|
||||
Reference in New Issue
Block a user