Named-bridge-network production Compose (not host networking, unlike dev) for rn-platform-01; egress-proxy + agents for rn-lab-host-01. Caddy reverse proxy, single public origin, Grafana restricted to management_cidr. iptables-restore firewall rulesets for all three hosts (Proxmox host, rn-platform-01, rn-lab-host-01) codifying the Platform/Lab-Host boundary: no student-to-student traffic, no direct Proxmox exposure, no direct Postgres/Provisioner exposure from the Lab Host. Mutual TLS for the Provisioner<->Lab Host Docker connection: certificate generation/verification scripts, dockerd TLS config, firewall-restricted port 2376. Application support already existed since Phase 1; this is purely the operational half. Validated: docker compose config, caddy validate, firewall structural lint, and a real openssl s_server/s_client mTLS handshake proving the cert chain and mutual-auth rejection (legitimate client verified, rogue client correctly rejected with verify code 19).
39 lines
1.6 KiB
Caddyfile
39 lines
1.6 KiB
Caddyfile
# Caddy 2 — single stable RootNode Academy origin, automatic HTTPS (Let's Encrypt) unless
|
|
# Cloudflare origin certs are used instead (see docs/PRODUCTION_NETWORK_SECURITY.md §8 for the
|
|
# Cloudflare-vs-direct-LE decision). Rendered by the `platform` Ansible role from
|
|
# infra/ansible/group_vars/production.yml's `academy_domain`.
|
|
#
|
|
# The student/instructor path proxies to the Gateway (platform/gateway) — the sole
|
|
# student-facing entrypoint, unchanged architectural rule from Phase 2A onward. Nothing here
|
|
# reaches the Provisioner, Postgres, or Docker TLS directly; those have no public route at all.
|
|
# Grafana is reachable at /grafana/ but restricted to the management CIDR even though it has
|
|
# its own login — internal observability tooling shouldn't be internet-facing at all, same
|
|
# posture as everything else that isn't the student/instructor product surface.
|
|
|
|
{{ academy_domain }} {
|
|
encode gzip zstd
|
|
|
|
handle_path /grafana/* {
|
|
@notmanagement not remote_ip {{ management_cidr }}
|
|
respond @notmanagement 403
|
|
reverse_proxy grafana:3000
|
|
}
|
|
|
|
# WebSocket terminal traffic (shell-ws) needs the connection upgraded, not buffered —
|
|
# Caddy's reverse_proxy handles this automatically for Upgrade: websocket requests, no
|
|
# special directive needed, but flush_interval -1 avoids buffering SSE/long-poll-shaped
|
|
# responses too. `gateway` is the Docker Compose service name (rn-platform-internal
|
|
# network), not localhost — Caddy no longer shares host networking with the Gateway in
|
|
# production, unlike the dev compose file.
|
|
reverse_proxy gateway:4200 {
|
|
flush_interval -1
|
|
}
|
|
|
|
log {
|
|
output file /var/log/caddy/access.log {
|
|
roll_size 100mb
|
|
roll_keep 7
|
|
}
|
|
}
|
|
}
|