Commit Graph

3 Commits

Author SHA1 Message Date
umbra audio
35a8dde07c Add Apache-2.0 LICENSE, NOTICE, and SPDX headers 2026-10-18 19:01:06 -07:00
bdm
5fc136dc49 Take the admin password from a secrets file instead of an env var
The password now lives in CADPRox_Secrets (mode 600, git-ignored) and is
mounted read-only into the container, rather than being read from the
compose file. Two reasons:

  - the password contains '#', which YAML reads as the start of a comment,
    so putting it in docker-compose.yml was never going to work;
  - anything in the compose file is visible in `docker compose config` and
    `docker inspect`, and would be easy to commit by accident.

read_secrets_file() accepts either a bare password on one line, which is
the common case, or a key=value file so the same file can hold more than
one secret later. A bare password containing '=' is not mangled, because
the key/value parse only wins when it recognises one of its own keys.

Also detect a bind mount whose source is missing. Docker creates a
directory in that case, which would otherwise fail confusingly and
quietly leave the app with no password.

The port stays CADPROX_PORT in the compose file and is deliberately not
added to ports:. Publishing a port needs bridge networking, which cannot
reach Caddy's admin API on 127.0.0.1:2019, and Docker discards a ports:
entry under host mode with only a warning - so it would read as a mapping
that is not there. Documented in the README instead.
2026-09-28 08:36:04 -07:00
bdm
6d94fc5eb1 Add cadprox: web UI for the Caddy reverse proxy config
Manages one Caddyfile entry per subdomain (URL -> IP:Port, plus optional
HTTP Basic Auth) and applies changes with a hot-reload through the Caddy
admin API, so an edit is live in ~0.3s with no restart and no certificate
churn.

Safety properties:
  - every change is validated by the real Caddy binary before it is written
  - a rejected reload restores the previous Caddyfile
  - site blocks containing directives cadprox does not model are preserved
    byte-for-byte, shown read-only, and never rewritten
  - the Caddyfile is the only source of state, so a hand-edited file cannot
    silently diverge from what the UI shows
  - password hashes never leave the server; the session cookie is HttpOnly
    and SameSite=Strict, and mutating requests need a custom header

app.py is a single standard-library file. The image is built on caddy:2.11.4
so validation, adaptation and hashing use the same binary that serves
traffic, which is why basic-auth hashes are bcrypt: Caddy's argon2id hashes
load fine but its basic-auth provider only verifies bcrypt.

Tests: 28 unit tests (Caddy-backed ones skip without the binary) and a 32
assertion end-to-end script that runs against a throwaway Caddy.
2026-09-28 07:54:14 -07:00