docs: document the Zoraxy deployment (README + config example)
- config.example.yaml: listen 0.0.0.0:8080, base_url https://myday.sechpoint.app, trusted_proxies = Zoraxy; a comment stating :8080 must be firewalled to Zoraxy only and that the bootstrap is tunnel-only (D-025/D-043). - README: a Deployment section (TLS at the proxy, the app on 0.0.0.0:8080, the firewall rule) and the tunnel-only bootstrap (ssh -L 8080:127.0.0.1:8080 → http://127.0.0.1:8080/setup, the one path a proxy request cannot take).
This commit is contained in:
parent
f0927fb0f0
commit
9a9888fa7b
2 changed files with 50 additions and 17 deletions
51
README.md
51
README.md
|
|
@ -41,29 +41,56 @@ go build -o myday ./cmd/myday
|
|||
## ⚠️ First run — do NOT expose it publicly
|
||||
|
||||
The **first user becomes the top admin** (the root of the chain). So the instance bootstraps
|
||||
**loopback-only**, running a **`/setup` wizard** until that admin exists.
|
||||
through a **`/setup` wizard** that is reachable **only over a direct SSH tunnel**, until that admin
|
||||
exists.
|
||||
|
||||
```bash
|
||||
# on the server it listens on 127.0.0.1:8080 only (config.example.yaml)
|
||||
ssh -L 8080:127.0.0.1:8080 user@server # from your workstation
|
||||
# from your workstation — tunnel to the app's port on the server
|
||||
ssh -L 8080:127.0.0.1:8080 user@server
|
||||
# then open http://127.0.0.1:8080/setup and complete the wizard
|
||||
```
|
||||
|
||||
> **Loopback is enforced per request, not just at bind.** While in setup mode the app answers **404**
|
||||
> to any request whose client is not loopback — so a reverse proxy must **not** sit in front of a
|
||||
> fresh instance, even one bound to `127.0.0.1`. Bootstrap over the SSH tunnel (above) first.
|
||||
> **The bootstrap is tunnel-only, enforced per request** (D-025 revised). While in setup mode the app
|
||||
> answers **404** to any request that is not the direct tunnel — i.e. a request whose **TCP peer is
|
||||
> not loopback**, **or which carries an `X-Forwarded-For`** header. So a reverse proxy (even one on
|
||||
> the same host) **cannot** reach a fresh instance: a same-host proxy presents a loopback peer but
|
||||
> always adds `X-Forwarded-For`, which gives it away. Bootstrap over the SSH tunnel (above) **first**
|
||||
> — then expose the instance behind the proxy.
|
||||
|
||||
> **Reach the app over `localhost` / `127.0.0.1` — not a hostname.** With an `https://` `base_url`
|
||||
> the CSRF and session cookies are marked **`Secure`**, so they are **not** sent over plain
|
||||
> `http://`; and only a **loopback** origin counts as a trustworthy/secure context for the
|
||||
> browser's **WebAuthn** dialogs. So for first run and any human testing, open the app at
|
||||
> **`http://localhost:8080`** or **`http://127.0.0.1:8080`** (via the tunnel above) — never at a
|
||||
> bare server hostname.
|
||||
> **Reach the app over `localhost` / `127.0.0.1` — not a hostname.** Only a **loopback** origin
|
||||
> counts as a trustworthy/secure context for the browser's **WebAuthn** dialogs. So for first run and
|
||||
> any human testing, open the app at **`http://localhost:8080`** or **`http://127.0.0.1:8080`** (via
|
||||
> the tunnel above) — never at a bare server hostname. Over plain http the cookies are sent without
|
||||
> `Secure` (needed by the bootstrap); behind the proxy (https) they are hardened — see *Deployment*.
|
||||
|
||||
Only **after** the top admin exists should you put MyDay behind a reverse proxy and expose it.
|
||||
|
||||
---
|
||||
|
||||
## Deployment — behind Zoraxy (TLS at the proxy)
|
||||
|
||||
The reference deployment terminates **TLS at [Zoraxy](https://github.com/tobychui/zoraxy)**; the app
|
||||
itself speaks **plain HTTP**. Config (`config.example.yaml` → `config.yaml`, D-043):
|
||||
|
||||
- **App** — `server.listen: "0.0.0.0:8080"`; the app binds any address (there is **no** setup-mode
|
||||
bind refusal, D-025 revised).
|
||||
- **Proxy** — Zoraxy holds the certificate and forwards to `127.0.0.1:8080`, setting
|
||||
`X-Forwarded-For` and `X-Forwarded-Proto`.
|
||||
- **`base_url`** — `https://myday.sechpoint.app`: the public origin, used in invite/recovery links.
|
||||
The app **honours `X-Forwarded-Proto`** from a trusted proxy (`server.trusted_proxies` = Zoraxy) to
|
||||
decide a request is `https`, and then sets the session cookie **`Secure` + `__Host-`** and sends
|
||||
**HSTS**. Over plain http (the tunnel) it keeps the bare, non-`Secure` cookie so the bootstrap works.
|
||||
- **Firewall** — **`:8080` MUST be reachable only from Zoraxy.** Anything else that can reach the
|
||||
port bypasses the proxy **and its TLS**; the app would then serve plain HTTP to that client.
|
||||
|
||||
```bash
|
||||
# the tunnel-only bootstrap — the one path a proxy request cannot take
|
||||
ssh -L 8080:127.0.0.1:8080 user@server
|
||||
# open http://127.0.0.1:8080/setup (before the first admin exists)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What `/setup` writes — and the restart
|
||||
|
||||
The wizard **persists `config.yaml`** (`setup: false`, the base URL, mail/locale, and the secrets)
|
||||
|
|
|
|||
|
|
@ -4,15 +4,21 @@
|
|||
# Settings live HERE, not in the database (D-035).
|
||||
|
||||
# ── setup & recovery ─────────────────────────────────────────────────────────
|
||||
setup: true # D-015 / D-026 — while true the app serves ONLY /setup (loopback-only)
|
||||
setup: true # D-015 / D-026 — while true the app serves ONLY /setup.
|
||||
# D-025 (revised): the wizard is reachable ONLY over the
|
||||
# direct SSH tunnel — a request relayed by a proxy (a
|
||||
# loopback peer carrying X-Forwarded-For) is 404'd.
|
||||
admin_recovery: false # D-033 — set true + restart to email user 1 an enrolment link
|
||||
|
||||
# ── server ───────────────────────────────────────────────────────────────────
|
||||
# D-043 (deployment behind Zoraxy): TLS terminates at the PROXY, so the app
|
||||
# speaks plain HTTP and binds 0.0.0.0:8080. FIREWALL :8080 TO ZORAXY ONLY —
|
||||
# anything else that can reach the port bypasses the proxy and its TLS.
|
||||
server:
|
||||
listen: "127.0.0.1:8080" # D-025 — loopback during setup; bind the public iface only after
|
||||
base_url: "https://myday.example.com" # used in invite / recovery links
|
||||
trusted_proxies: # XFF is resolved from these (A6)
|
||||
- "127.0.0.1"
|
||||
listen: "0.0.0.0:8080" # D-043 — any bind is allowed (no setup-mode refusal)
|
||||
base_url: "https://myday.sechpoint.app" # the PUBLIC origin: invite / recovery links
|
||||
trusted_proxies: # XFF + X-Forwarded-Proto are honoured from these (A6/D-043)
|
||||
- "127.0.0.1" # Zoraxy's address (127.0.0.1 if it runs on this host)
|
||||
|
||||
# ── app ──────────────────────────────────────────────────────────────────────
|
||||
app:
|
||||
|
|
|
|||
Loading…
Reference in a new issue