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.
This commit is contained in:
67
README.md
67
README.md
@@ -25,22 +25,55 @@ on an edit keeps the credential that is already stored.
|
||||
```bash
|
||||
cd /home/bdm/cadprox
|
||||
docker compose up -d --build
|
||||
docker compose logs -f cadprox # first boot prints the generated password
|
||||
```
|
||||
|
||||
The UI is then on `http://<host>:8090`. Sign in as `admin` with the password
|
||||
from the first boot log, or set `CADPROX_ADMIN_PASSWORD` in
|
||||
`docker-compose.yml` to choose your own.
|
||||
The UI is then on `http://<host>:8090`. Sign in as `admin`.
|
||||
|
||||
To pin the password permanently, generate one and drop it in a file that only
|
||||
you can read, then reference it from the compose file:
|
||||
The password comes from `CADPRox_Secrets` in this directory, which is mounted
|
||||
read-only into the container and never written into `docker-compose.yml`:
|
||||
|
||||
```bash
|
||||
printf 'change-me-at-least-8-chars' > ~/.cadprox-password && chmod 600 ~/.cadprox-password
|
||||
# then in docker-compose.yml:
|
||||
# CADPROX_ADMIN_PASSWORD_FILE: /run/secrets/cadprox-password
|
||||
cat CADPRox_Secrets # the admin password, one line, mode 600
|
||||
```
|
||||
|
||||
That file is in `.gitignore`. It is deliberately kept out of the compose file
|
||||
because the password contains a `#`, which YAML would read as the start of a
|
||||
comment, and because anything in the compose file is visible in
|
||||
`docker compose config` and `docker inspect`.
|
||||
|
||||
`CADPRox_Secrets` accepts either shape:
|
||||
|
||||
```
|
||||
your-own-secret-here <- bare password, the common case
|
||||
```
|
||||
|
||||
```
|
||||
# comment lines and blank lines are ignored
|
||||
CADPROX_ADMIN_PASSWORD=some-secret-here <- key=value, for holding more later
|
||||
```
|
||||
|
||||
If neither key is recognised, the whole file is treated as the password, so a
|
||||
bare password that happens to contain `=` still works.
|
||||
|
||||
To change the password, edit the file and `docker compose up -d --build`. The
|
||||
old one stops working immediately. If the file is missing, Docker silently
|
||||
creates a *directory* in its place; cadprox detects that and refuses to start
|
||||
rather than coming up with no password.
|
||||
|
||||
## Why the port is not in the `docker ps` output
|
||||
|
||||
`docker ps` shows a `PORTS` column only for published ports, and publishing a
|
||||
port requires bridge networking. Bridge networking cannot reach Caddy's admin
|
||||
API, which binds to `127.0.0.1:2019` on the host loopback. Sharing the host
|
||||
network is therefore what makes the no-restart hot-reload possible, and host
|
||||
networking is exactly the mode that shows no ports.
|
||||
|
||||
Docker does not reject a `ports:` entry alongside `network_mode: host`; it
|
||||
prints `WARNING: Published ports are discarded when using host network mode`
|
||||
and carries on, which would look like a mapping that is not there. The port is
|
||||
therefore set as `CADPROX_PORT: "8090"` in the compose file, which is where it
|
||||
is actually authoritative, and is left out of `ports:` on purpose.
|
||||
|
||||
## How a change is applied
|
||||
|
||||
1. The current Caddyfile is parsed into structured entries.
|
||||
@@ -70,11 +103,14 @@ listed as read-only rather than being lost.
|
||||
**Changes are validated, then rolled back.** Nothing is written until Caddy has
|
||||
accepted the rendered text, and a failed reload restores the previous file.
|
||||
|
||||
**Credentials.** The admin password is compared with `hmac.compare_digest` and
|
||||
stored as a scrypt hash. A session is a random token in an `HttpOnly`,
|
||||
`SameSite=Strict` cookie. Mutating requests also require an `X-Cadprox` header,
|
||||
which blocks cross-origin form posts. The Basic Auth password for a subdomain is
|
||||
hashed by Caddy itself and the hash is never returned to the browser.
|
||||
**Credentials.** The admin password is compared with `hmac.compare_digest`. With
|
||||
`CADPRox_Secrets` in place it is read from that file on every start; if that is
|
||||
removed, cadprox falls back to generating a random password on first boot and
|
||||
recording its scrypt hash in `data/admin.json`. A session is a random token in
|
||||
an `HttpOnly`, `SameSite=Strict` cookie. Mutating requests also require an
|
||||
`X-Cadprox` header, which blocks cross-origin form posts. The Basic Auth password
|
||||
for a subdomain is hashed by Caddy itself and the hash is never returned to the
|
||||
browser.
|
||||
|
||||
**Hashes are bcrypt, on purpose.** `caddy hash-password` can emit argon2id, and
|
||||
that hash loads without complaint — but Caddy 2.11's HTTP Basic Auth provider
|
||||
@@ -98,7 +134,8 @@ cadprox/
|
||||
app.py the whole application, standard library only
|
||||
Dockerfile caddy:2.11.4 + python3
|
||||
docker-compose.yml host networking so the admin API on 2019 is reachable
|
||||
data/ audit log and the generated admin credential (git-ignored)
|
||||
CADPRox_Secrets the admin password, mode 600 (git-ignored)
|
||||
data/ audit log (git-ignored)
|
||||
tests/test_app.py unit tests, including Caddy-backed parse/render tests
|
||||
tests/e2e.sh end-to-end test against a throwaway Caddy
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user