dev: serve at dev.thermograph.org behind basic auth, bound to loopback
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
All checks were successful
PR build (required check) / changes (pull_request) Successful in 7s
secrets-guard / encrypted (pull_request) Successful in 5s
PR build (required check) / build-backend (pull_request) Has been skipped
shell-lint / shellcheck (pull_request) Successful in 6s
PR build (required check) / build-frontend (pull_request) Has been skipped
PR build (required check) / validate-observability (pull_request) Has been skipped
PR build (required check) / gate (pull_request) Successful in 2s
Dev was mesh-only: bound to 10.10.0.2, no DNS record, no Caddy site. A public
record now exists for it, so it gets a real front door instead of a name that
resolves to a timeout.
Three things keep that acceptable on a box that also runs Forgejo and its CI
runner, and all three are load-bearing together:
- the stack binds LOOPBACK, so the site block is the only route in — not even
the mesh reaches the app directly, which is what makes the guard total
rather than decorative,
- HTTP basic auth, so a stumbled-upon URL is not a running build,
- X-Robots-Tag: noindex, so dev never competes with thermograph.org for the
same content in a search index.
The credential in Caddyfile.vps1 is a bcrypt hash, not a password.
This commit is contained in:
parent
64986189e1
commit
fed307e75c
5 changed files with 77 additions and 31 deletions
|
|
@ -5,13 +5,7 @@
|
||||||
# git.thermograph.org -> Forgejo (see deploy/forgejo/caddy-git.conf)
|
# git.thermograph.org -> Forgejo (see deploy/forgejo/caddy-git.conf)
|
||||||
# dashboard.thermograph.org -> Grafana (see observability/caddy-grafana.conf)
|
# dashboard.thermograph.org -> Grafana (see observability/caddy-grafana.conf)
|
||||||
#
|
#
|
||||||
# and hosts the **dev** environment, which is deliberately ABSENT from this
|
# and hosts the **dev** environment at dev.thermograph.org (below).
|
||||||
# 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.
|
|
||||||
#
|
#
|
||||||
# beta.thermograph.org is NOT here either — beta moved to vps2, next to prod.
|
# 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"
|
# 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
|
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
|
# Optional: redirect www -> apex. Add the www CNAME/A record first, then
|
||||||
# uncomment.
|
# uncomment.
|
||||||
# www.emigriffith.dev {
|
# www.emigriffith.dev {
|
||||||
|
|
|
||||||
|
|
@ -317,13 +317,14 @@ SERVICE=all BACKEND_IMAGE_TAG=<dev tag> FRONTEND_IMAGE_TAG=<dev tag> \
|
||||||
infra/deploy/deploy-dev.sh
|
infra/deploy/deploy-dev.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
**Verify it is mesh-only.** This is the security-relevant check of the whole
|
**Verify Caddy is the only way in.** This is the security-relevant check of the
|
||||||
cutover — vps1 is a public box and dev runs unreviewed branches:
|
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
|
ss -ltnp | grep 8137 # MUST show 127.0.0.1:8137, never 0.0.0.0
|
||||||
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
|
||||||
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 —
|
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.
|
# instance lives on vps2 and holds real user data.
|
||||||
TG_DB_NAME=thermograph
|
TG_DB_NAME=thermograph
|
||||||
TG_DB_USER=thermograph
|
TG_DB_USER=thermograph
|
||||||
# Mesh-only. vps1 is a public VPS, so the dev overlay must not publish on
|
# LOOPBACK ONLY. vps1 is a public VPS and dev runs whatever branch is in
|
||||||
# 0.0.0.0 the way it did on the operator's LAN box — dev runs whatever
|
# flight, including unreviewed ones, so the stack must never publish on
|
||||||
# branch is in flight, including unreviewed ones. Reachable at
|
# 0.0.0.0 the way it did on the operator's LAN box.
|
||||||
# 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.
|
# It was briefly mesh-only (10.10.0.2). It is now fronted by Caddy at
|
||||||
TG_BIND_ADDR=10.10.0.2
|
# 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
|
# Dev NEVER layers common.yaml — that file is the internet-facing fleet's
|
||||||
# shared production credential set (both S3 keypairs, the VAPID signing
|
# shared production credential set (both S3 keypairs, the VAPID signing
|
||||||
# key, REGISTRY_TOKEN, the metrics token), and dev runs whatever branch is
|
# 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
|
# - 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
|
# 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.
|
# 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
|
# - It binds LOOPBACK, never 0.0.0.0. Caddy fronts it at dev.thermograph.org
|
||||||
# record, no Caddy site, no TLS. Anything that can reach it is already on
|
# with TLS, basic auth and X-Robots-Tag: noindex (deploy/Caddyfile.vps1).
|
||||||
# the WireGuard mesh.
|
# 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.
|
# Idempotent; re-run it after changing the branch or repointing the remote.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
@ -70,10 +71,10 @@ Deploy it:
|
||||||
SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> \\
|
SERVICE=all BACKEND_IMAGE_TAG=sha-<a> FRONTEND_IMAGE_TAG=sha-<b> \\
|
||||||
$APP_DIR/infra/deploy/deploy-dev.sh
|
$APP_DIR/infra/deploy/deploy-dev.sh
|
||||||
|
|
||||||
Reach it (mesh only):
|
Reach it:
|
||||||
http://${TG_BIND_ADDR}:8137
|
https://dev.thermograph.org (basic auth; see deploy/Caddyfile.vps1)
|
||||||
|
|
||||||
Confirm it is NOT publicly exposed — this must show ${TG_BIND_ADDR}:8137 and
|
Confirm the app itself is NOT directly exposed — this must show
|
||||||
never 0.0.0.0:8137:
|
${TG_BIND_ADDR}:8137 and never 0.0.0.0:8137, so Caddy is the only way in:
|
||||||
ss -ltnp | grep 8137
|
ss -ltnp | grep 8137
|
||||||
EOF
|
EOF
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,12 @@
|
||||||
# unreviewed branch is in flight to the entire internet — with no Caddy, no
|
# unreviewed branch is in flight to the entire internet — with no Caddy, no
|
||||||
# TLS and no auth in front of it.
|
# TLS and no auth in front of it.
|
||||||
#
|
#
|
||||||
# So the default is 127.0.0.1 (safe anywhere, including a laptop running
|
# 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
|
# `make dev-up` — and on vps1 deploy/env-topology.sh keeps it there. Caddy
|
||||||
# address from deploy/env-topology.sh — 10.10.0.2 on vps1. Dev is then
|
# fronts the stack at dev.thermograph.org with TLS, HTTP basic auth and
|
||||||
# reachable at http://10.10.0.2:8137 from anything on the WireGuard mesh and
|
# X-Robots-Tag: noindex (deploy/Caddyfile.vps1). Loopback is what makes that
|
||||||
# from nowhere else. There is deliberately no dev DNS record and no Caddy
|
# guard total: the site block is the only route in, so nothing reaches the
|
||||||
# site block for it.
|
# 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
|
# 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
|
# directly, so it stays compose-internal-only, reached solely through
|
||||||
# backend's own reverse-proxy fallback (THERMOGRAPH_FRONTEND_BASE_INTERNAL,
|
# backend's own reverse-proxy fallback (THERMOGRAPH_FRONTEND_BASE_INTERNAL,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue