server: drop in-process TLS — HTTP-only behind reverse proxy

Self-hosted deployments already terminate TLS at Caddy/Traefik/nginx;
making the server do TLS too means double cert config, dual ACME
plumbing, and an untested code path. Drop RM_TLS_CERT/RM_TLS_KEY,
remove TLSEnabled() and the ListenAndServeTLS branch.

Replace the cookie's "Secure if TLS-in-process" check with a new
RM_COOKIE_SECURE flag (default true). Local HTTP-only testing sets
RM_COOKIE_SECURE=false; production is always behind a TLS proxy and
the cookie stays Secure.

Default port :8443 → :8080. docker-compose binds 127.0.0.1 only and
populates RM_TRUSTED_PROXY. spec.md §4.1/§10.1 rewritten with a
Caddyfile snippet and a hard "do not expose RM_LISTEN publicly"
warning. enrollResponse keeps cert_pin_sha256 in the shape but the
server can't introspect a cert it doesn't terminate — operator
pastes the proxy's hash into -cert-pin at install time.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-01 11:20:41 +01:00
parent 80a57b3b84
commit 811157b4ce
8 changed files with 102 additions and 64 deletions
+34 -14
View File
@@ -99,14 +99,20 @@ It is built for small-to-medium fleets (initial target: ~12 endpoints) and is in
- **UI:** HTMX + Tailwind, server-rendered Go templates, no Node build step
- **Distribution:** single static binary, packaged in a Docker image; published `docker-compose.yml`
- **Config:** YAML or env vars:
- `RM_LISTEN` — bind address, e.g. `:8443` (source of truth for the port;
the `8443` in the reference compose is just a default mapping)
- `RM_DATA_DIR`, `RM_BASE_URL`, `RM_TLS_CERT`, `RM_TLS_KEY`,
`RM_SECRET_KEY_FILE`
- `RM_LISTEN` — bind address, e.g. `:8080` (source of truth for the port;
the `8080` in the reference compose is just a default mapping). Bind to
`127.0.0.1:8080` when running behind a same-host proxy.
- `RM_DATA_DIR`, `RM_BASE_URL`, `RM_SECRET_KEY_FILE`
- `RM_TRUSTED_PROXY` — comma-separated CIDR list of reverse proxies
whose `X-Forwarded-For` / `X-Forwarded-Proto` we honour. Empty (the
default) = trust no one. Set this when fronted by Caddy/Traefik.
- **TLS:** terminate TLS in-process (cert from Caddy/Traefik sidecar acceptable; agents require HTTPS)
- `RM_COOKIE_SECURE``true` (default) marks session cookies `Secure`.
Only set to `false` for local HTTP-only testing.
- **TLS:** the server speaks plain HTTP and is **always** expected to sit
behind a TLS-terminating reverse proxy (Caddy / Traefik / nginx). This
keeps cert renewal, ACME, and SNI in the proxy where operators already
manage it. Agents must reach the server over HTTPS; the cert pin
(`cert_pin_sha256`) pins whatever cert the proxy serves.
### 4.2 Agent
@@ -371,6 +377,10 @@ Stack: HTMX + Tailwind + Go html/templates. No SPA framework. Server-rendered, p
### 10.1 Control plane (Proxmox host or LXC)
The server is HTTP-only by design — operators front it with their own
TLS-terminating reverse proxy (Caddy / Traefik / nginx). Bind the
container to localhost so the only public path is through the proxy.
`docker-compose.yml`:
```yaml
services:
@@ -378,24 +388,34 @@ services:
image: ghcr.io/<owner>/restic-manager:latest
restart: unless-stopped
ports:
- "8443:8443"
- "127.0.0.1:8080:8080"
volumes:
- ./data:/data
- ./certs:/certs:ro
environment:
- RM_DATA_DIR=/data
- RM_LISTEN=:8443
- RM_LISTEN=:8080
- RM_BASE_URL=https://restic.lab.example
- RM_TLS_CERT=/certs/fullchain.pem
- RM_TLS_KEY=/certs/privkey.pem
- RM_SECRET_KEY_FILE=/data/secret.key
# - RM_TRUSTED_PROXY=10.0.0.0/8 # set when fronted by a reverse proxy
- RM_TRUSTED_PROXY=172.16.0.0/12 # CIDR of your reverse proxy
```
Reference Caddy snippet (operator's own Caddyfile, outside this repo):
```
restic.lab.example {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
}
```
Caddy provisions and renews the cert; the agent's `cert_pin_sha256`
pins **Caddy's** leaf cert (that's what the agent actually sees).
`RM_LISTEN` is the source of truth for the server's bind address. The
`8443:8443` mapping above is just the matching default; if you change
`RM_LISTEN` to e.g. `:9443`, change the right-hand side of the port
mapping to match.
`8080:8080` mapping above is just the matching default; change both
sides together if you pick a different port.
> ⚠️ Never expose `RM_LISTEN` directly on a public interface — the
> server has no TLS, no rate limiting, and no DDoS protection. That
> all belongs in the proxy.
### 10.2 Restic REST server (Unraid)