dev: serve at dev.thermograph.org behind basic auth, bound to loopback #111
5 changed files with 77 additions and 31 deletions
|
|
@ -5,13 +5,7 @@
|
|||
# git.thermograph.org -> Forgejo (see deploy/forgejo/caddy-git.conf)
|
||||
# dashboard.thermograph.org -> Grafana (see observability/caddy-grafana.conf)
|
||||
#
|
||||
# and hosts the **dev** environment, which is deliberately ABSENT from this
|
||||
# file. Dev has no site block, no DNS record and no certificate: it publishes on
|
||||
# the WireGuard address only (http://10.10.0.2:8137) and is reachable from the
|
||||
# mesh and nowhere else. Dev runs whatever branch is in flight, on the same box
|
||||
# as Forgejo and its CI — putting it behind a public hostname would be handing
|
||||
# the internet an unreviewed build. If you ever need it reachable from a phone,
|
||||
# join the phone to the mesh; do not add a site block here.
|
||||
# and hosts the **dev** environment at dev.thermograph.org (below).
|
||||
#
|
||||
# beta.thermograph.org is NOT here either — beta moved to vps2, next to prod.
|
||||
# That is the one edit in this file most likely to be made by mistake: a "beta"
|
||||
|
|
@ -54,6 +48,52 @@ http://75.119.132.91 {
|
|||
redir https://emigriffith.dev{uri} permanent
|
||||
}
|
||||
|
||||
# Dev. Publicly resolvable, deliberately NOT publicly usable.
|
||||
#
|
||||
# Dev runs whatever branch is in flight — including unreviewed ones — on the
|
||||
# same box as Forgejo and its CI runner. Three things make that acceptable, and
|
||||
# all three are load-bearing:
|
||||
#
|
||||
# 1. The stack binds LOOPBACK (deploy/env-topology.sh sets DEV_BIND_ADDR to
|
||||
# 127.0.0.1), so this site block is the only route in. Not even the mesh
|
||||
# reaches the app directly.
|
||||
# 2. basic auth, so a stumbled-upon URL is not a running build.
|
||||
# 3. X-Robots-Tag: noindex, so dev never competes with thermograph.org in a
|
||||
# search index the way a second copy of the same content otherwise would.
|
||||
#
|
||||
# Only :8137 is proxied. The dev overlay does not publish the frontend at all
|
||||
# (docker-compose.dev.yml `ports: !reset null`) — the backend reverse-proxies to
|
||||
# it internally, so one upstream serves the whole site here, unlike vps2 where
|
||||
# Caddy path-splits across two ports.
|
||||
#
|
||||
# The credential below is a bcrypt hash, not a password. Rotate with:
|
||||
# caddy hash-password --plaintext '<new>'
|
||||
dev.thermograph.org {
|
||||
encode zstd gzip
|
||||
|
||||
header {
|
||||
X-Robots-Tag "noindex, nofollow"
|
||||
}
|
||||
|
||||
basic_auth {
|
||||
dev $2a$14$LU5sNyxbop3HOsjhXB6ZrOQiveqhbcYVE6.Wi0bydAv4QhNpj4HMC
|
||||
}
|
||||
|
||||
reverse_proxy 127.0.0.1:8137 {
|
||||
health_uri /healthz
|
||||
health_interval 15s
|
||||
health_timeout 3s
|
||||
health_status 2xx
|
||||
}
|
||||
|
||||
log {
|
||||
output file /var/log/caddy/dev.log {
|
||||
roll_size 20MiB
|
||||
roll_keep 5
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Optional: redirect www -> apex. Add the www CNAME/A record first, then
|
||||
# uncomment.
|
||||
# www.emigriffith.dev {
|
||||
|
|
|
|||
|
|
@ -317,13 +317,14 @@ SERVICE=all BACKEND_IMAGE_TAG=<dev tag> FRONTEND_IMAGE_TAG=<dev tag> \
|
|||
infra/deploy/deploy-dev.sh
|
||||
```
|
||||
|
||||
**Verify it is mesh-only.** This is the security-relevant check of the whole
|
||||
cutover — vps1 is a public box and dev runs unreviewed branches:
|
||||
**Verify Caddy is the only way in.** This is the security-relevant check of the
|
||||
whole cutover — vps1 is a public box and dev runs unreviewed branches:
|
||||
|
||||
```
|
||||
ss -ltnp | grep 8137 # MUST show 10.10.0.2:8137, never 0.0.0.0:8137
|
||||
curl -fsS -o /dev/null -w '%{http_code}\n' http://10.10.0.2:8137/healthz # from the mesh
|
||||
curl --max-time 5 http://75.119.132.91:8137/healthz # MUST fail/refuse
|
||||
ss -ltnp | grep 8137 # MUST show 127.0.0.1:8137, never 0.0.0.0
|
||||
curl --max-time 5 http://75.119.132.91:8137/healthz # MUST fail/refuse
|
||||
curl -o /dev/null -w '%{http_code}\n' https://dev.thermograph.org/ # 401 without creds
|
||||
curl -o /dev/null -w '%{http_code}\n' -u dev:<pw> https://dev.thermograph.org/healthz # 200
|
||||
```
|
||||
|
||||
Also confirm dev did **not** get the fleet's shared production credentials —
|
||||
|
|
|
|||
|
|
@ -176,12 +176,16 @@ thermograph_topology() {
|
|||
# instance lives on vps2 and holds real user data.
|
||||
TG_DB_NAME=thermograph
|
||||
TG_DB_USER=thermograph
|
||||
# Mesh-only. vps1 is a public VPS, so the dev overlay must not publish on
|
||||
# 0.0.0.0 the way it did on the operator's LAN box — dev runs whatever
|
||||
# branch is in flight, including unreviewed ones. Reachable at
|
||||
# http://10.10.0.2:8137 from anything on the WireGuard mesh, and from
|
||||
# nowhere else. There is deliberately no dev DNS record and no Caddy site.
|
||||
TG_BIND_ADDR=10.10.0.2
|
||||
# LOOPBACK ONLY. vps1 is a public VPS and dev runs whatever branch is in
|
||||
# flight, including unreviewed ones, so the stack must never publish on
|
||||
# 0.0.0.0 the way it did on the operator's LAN box.
|
||||
#
|
||||
# It was briefly mesh-only (10.10.0.2). It is now fronted by Caddy at
|
||||
# dev.thermograph.org — TLS, HTTP basic auth, and X-Robots-Tag: noindex
|
||||
# (see deploy/Caddyfile.vps1) — so the only reachable path is through that
|
||||
# site block, and binding loopback is what guarantees it: nothing can
|
||||
# reach the app without passing the password, not even from the mesh.
|
||||
TG_BIND_ADDR=127.0.0.1
|
||||
# Dev NEVER layers common.yaml — that file is the internet-facing fleet's
|
||||
# shared production credential set (both S3 keypairs, the VAPID signing
|
||||
# key, REGISTRY_TOKEN, the metrics token), and dev runs whatever branch is
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@
|
|||
# - It renders dev.yaml ALONE, never layering common.yaml (the fleet's shared
|
||||
# production credentials). vps1 also runs Forgejo and its CI, and dev runs
|
||||
# whatever branch is in flight — see the long note in render-secrets.sh.
|
||||
# - It is MESH-ONLY: published on 10.10.0.2:8137 (wg0), never 0.0.0.0. No DNS
|
||||
# record, no Caddy site, no TLS. Anything that can reach it is already on
|
||||
# the WireGuard mesh.
|
||||
# - It binds LOOPBACK, never 0.0.0.0. Caddy fronts it at dev.thermograph.org
|
||||
# with TLS, basic auth and X-Robots-Tag: noindex (deploy/Caddyfile.vps1).
|
||||
# Binding loopback is what makes that guard total rather than decorative:
|
||||
# the site block is the only route in.
|
||||
#
|
||||
# Idempotent; re-run it after changing the branch or repointing the remote.
|
||||
set -euo pipefail
|
||||
|
|
@ -70,10 +71,10 @@ Deploy it:
|
|||
SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> \\
|
||||
$APP_DIR/infra/deploy/deploy-dev.sh
|
||||
|
||||
Reach it (mesh only):
|
||||
http://${TG_BIND_ADDR}:8137
|
||||
Reach it:
|
||||
https://dev.thermograph.org (basic auth; see deploy/Caddyfile.vps1)
|
||||
|
||||
Confirm it is NOT publicly exposed — this must show ${TG_BIND_ADDR}:8137 and
|
||||
never 0.0.0.0:8137:
|
||||
Confirm the app itself is NOT directly exposed — this must show
|
||||
${TG_BIND_ADDR}:8137 and never 0.0.0.0:8137, so Caddy is the only way in:
|
||||
ss -ltnp | grep 8137
|
||||
EOF
|
||||
|
|
|
|||
|
|
@ -22,12 +22,12 @@
|
|||
# unreviewed branch is in flight to the entire internet — with no Caddy, no
|
||||
# TLS and no auth in front of it.
|
||||
#
|
||||
# So the default is 127.0.0.1 (safe anywhere, including a laptop running
|
||||
# `make dev-up`), and deploy.sh sets DEV_BIND_ADDR to the environment's mesh
|
||||
# address from deploy/env-topology.sh — 10.10.0.2 on vps1. Dev is then
|
||||
# reachable at http://10.10.0.2:8137 from anything on the WireGuard mesh and
|
||||
# from nowhere else. There is deliberately no dev DNS record and no Caddy
|
||||
# site block for it.
|
||||
# So the default is 127.0.0.1 — safe anywhere, including a laptop running
|
||||
# `make dev-up` — and on vps1 deploy/env-topology.sh keeps it there. Caddy
|
||||
# fronts the stack at dev.thermograph.org with TLS, HTTP basic auth and
|
||||
# X-Robots-Tag: noindex (deploy/Caddyfile.vps1). Loopback is what makes that
|
||||
# guard total: the site block is the only route in, so nothing reaches the
|
||||
# app without the password — not even from the WireGuard mesh.
|
||||
# 2. frontend's port publish is dropped entirely -- dev has no Caddy to reach it
|
||||
# directly, so it stays compose-internal-only, reached solely through
|
||||
# backend's own reverse-proxy fallback (THERMOGRAPH_FRONTEND_BASE_INTERNAL,
|
||||
|
|
|
|||
Loading…
Reference in a new issue