CluPilotCloud/deploy/caddy/Caddyfile.example

103 lines
3.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
}
# 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
}