CluPilotCloud/deploy/caddy/Caddyfile.example

140 lines
5.7 KiB
Caddyfile

{
servers {
# Address-based rules and QUIC early data do not mix: a decision taken
# on 0-RTT data can answer 425, and some browsers do not retry — an
# intermittent lockout from the console, which is the worst kind.
protocols h1 h2
}
# Replace: this address receives the certificate-expiry notices for THIS
# installation. Left as the copied original, they reach whoever wrote the
# file instead of whoever runs the server.
email you@example.com
}
# Reference reverse-proxy configuration.
#
# Copy to /etc/caddy/Caddyfile, replace the hostnames, then:
# sudo bash deploy/install-agent.sh
# which replaces the console's allowlist line with an import generated from the
# list the owner keeps in the console itself.
#
# The rule this file exists to enforce: only the marketing site, the customer
# portal and the status page are public. The console and the websocket endpoint
# must not merely refuse — they must be indistinguishable from a hostname that
# serves nothing at all. An empty 404 is itself information: it says something
# terminates TLS here and chose not to answer.
# ── Customer portal ──────────────────────────────────────────────────────────
app.example.com {
reverse_proxy 127.0.0.1:8080
}
# ── Stripe webhook, and nothing else ─────────────────────────────────────────
# The application answers the whole site on every hostname, so without this the
# login and registration pages are served here too. Narrowed to the one request
# this hostname exists for; everything else never reaches PHP.
api.example.com {
@webhook {
method POST
path /webhooks/stripe
}
handle @webhook {
reverse_proxy 127.0.0.1:8080
}
handle {
respond 404
}
}
# ── Websockets (Reverb) ──────────────────────────────────────────────────────
ws.example.com {
# Only an actual websocket upgrade. A browser opening this name has no
# business here, and answering it — even with a 404 — advertises that
# something is listening.
@websocket {
header Connection *Upgrade*
header Upgrade websocket
}
handle @websocket {
reverse_proxy 127.0.0.1:8081
}
handle {
abort
}
}
# ── Operator console ─────────────────────────────────────────────────────────
# The network-level lock. The hostname checks inside the application are
# defence in depth — a Host header is chosen by the caller, so they cannot
# decide WHO gets through. This can.
#
# 10.66.0.0/24 is the WireGuard subnet. 62.178.50.33 is temporary, so the first
# VPN access can be created at all; it comes out once that is done.
admin.example.com {
import /etc/caddy/clupilot-console-allow.conf
handle @allowed {
reverse_proxy 127.0.0.1:8080
}
# Nothing at all for anyone else. An empty 404 still says "something
# terminates TLS here and chose not to answer" — which is itself the
# information worth hiding. Closing the connection is what a host that does
# not serve this name looks like.
handle {
abort
}
}
www.example.com {
reverse_proxy 127.0.0.1:8080
}
status.example.com {
reverse_proxy 127.0.0.1:8080
}
# ── Von der Konsole verwaltete Hostnamen ─────────────────────────────────────
# Erzeugt von `clupilot-host-step apply-proxy-hosts` aus der Liste, die der
# Betreiber in der Konsole pflegt. Damit lässt sich ein Name hinzufügen, ohne
# sich auf den Server zu melden — der Handgriff, der `files.clupilot.com`
# gekostet hat.
#
# Die Datei MUSS existieren, bevor diese Zeile sie importiert: ein Import ins
# Leere macht die ganze Proxy-Konfiguration ungültig, und das nimmt die
# öffentliche Website mit. `install-agent.sh` legt sie deshalb an, bevor es
# hierauf hinweist. Wer die Zeile von Hand einträgt, legt vorher an:
#
# sudo touch /etc/caddy/clupilot-proxy-hosts.conf
#
import /etc/caddy/clupilot-proxy-hosts.conf
# ── Dateien zum Herunterladen ────────────────────────────────────────────────
# FILES_HOST aus der .env. Zwei Dinge liegen hier, und beide müssen von überall
# erreichbar sein:
#
# - Rechtsdokumente (AGB, AV, TOM). Ihre Adressen stehen in Verträgen und auf
# Rechnungen und müssen Jahre halten.
# - Das Bootstrap-Archiv für die Host-Übernahme. Es wird von einem Server im
# RETTUNGSSYSTEM geholt — einer Maschine ohne Tunnel, mit einer öffentlichen
# Adresse, die in keiner Freigabeliste steht. Genau deshalb steht hier KEIN
# `import clupilot-console-allow.conf`: die Sperre der Konsole wäre hier die
# Sperre gegen den einzigen, der es braucht.
#
# Der Schutz sitzt in der Anwendung, nicht im Proxy: ohne gültigen Einmal-Code
# antwortet das Archiv mit 404, und die Dokumente sind ohnehin öffentlich.
#
# Auch KEIN `http://… { abort }` wie bei admin und ws weiter unten. Dieser Name
# soll bekannt sein — und ein abgewürgtes Port 80 nimmt der ACME-Prüfung den
# Weg, wenn Caddys eigener Handler einmal nicht als Erster drankommt.
files.example.com {
reverse_proxy 127.0.0.1:8080
}
# Plain HTTP for the two hidden names. Without this Caddy answers port 80 with
# an automatic redirect to https, which announces that both hostnames are
# served here — the one thing this file exists to avoid. The ACME challenge is
# unaffected: Caddy's own challenge handler runs ahead of these routes.
http://admin.example.com, http://ws.example.com {
abort
}