BREAKING CHANGE: PASSWORD is gone and AUTH and CERTRESOLVER are required. The server's compose.yaml and .env must be updated in the same deploy — the new image ignores PASSWORD, and the old one refuses to start without it. Authelia now sits in front of Traefik, so the app was asking for a second password at the same door. Two prompts, and the weaker of the two was the one holding a single shared secret with no sessions, no MFA and no revocation. Deleting it is the whole change: Authelia already does this properly, once, for every service on the host. Gone: auth(), challenge(), the whole of throttle.go and its tests, and golang.org/x/time with them. routes() returns the bare mux, /healthz is an ordinary route on it, and the smoke script drops sixty -u flags. Roughly 230 lines removed and nothing written to replace them. What holds the app up now, both asserted in compose.yaml: - The router names the Authelia middleware through AUTH. Traefik takes a router out of service when its middleware does not resolve, so a typo or an unset variable fails shut rather than serving the app open. - The container still publishes no ports, so the proxy is the only thing that can reach it. Publishing 8080 would now bypass authentication outright, not merely TLS — the comment there says so. certresolver replaces the bare tls=true, parameterised as CERTRESOLVER: the server had been carrying that label by hand since the first deploy. Naming a resolver implies tls=true, so it stays one label. TestAuth and TestHealthzSkipsAuth are replaced by one test asserting every route answers without credentials — a 401 from here would now mean auth had crept back in.
This commit is contained in:
@@ -25,12 +25,12 @@ polished, it may be released as FOSS under MIT.
|
||||
- No grocery list generation (possible future add-on).
|
||||
- No per-recipe ingredient tracking — meals are just names.
|
||||
- No calendar/scheduling with times, reminders, or calendar exports.
|
||||
- No user accounts, per-person profiles, or permissions. A single shared
|
||||
password gates the whole app (§9).
|
||||
- No user accounts, per-person profiles, or permissions *in the app*.
|
||||
Authentication is the reverse proxy's job (§9).
|
||||
- No nutrition tracking, calorie counting, or dietary-goal optimization.
|
||||
- No mobile-native apps. Web only (mobile-friendly responsive is enough).
|
||||
- No per-user accounts or sessions. The app *is* reachable from the internet
|
||||
(§9, §10), gated by a single shared password over TLS.
|
||||
- No per-user accounts or sessions in the app. It *is* reachable from the
|
||||
internet (§9, §10), behind Authelia at the proxy.
|
||||
|
||||
## 4. Delivery stages
|
||||
|
||||
@@ -72,9 +72,9 @@ weighting to be meaningful (a few weeks of logged meals).
|
||||
|
||||
## 5. Users
|
||||
|
||||
A single household. One shared instance, no per-person accounts. Anyone on the
|
||||
home network who knows the shared password can open the app and interact with
|
||||
it.
|
||||
A single household. One shared instance, no per-person accounts. Everyone who
|
||||
gets past Authelia sees and edits the same log; the app draws no distinction
|
||||
between them.
|
||||
|
||||
The interface is written in **Finnish** — every user of this instance is a
|
||||
Finnish speaker, so there is no i18n layer and no language switcher. Strings
|
||||
@@ -350,21 +350,21 @@ build and no asset bundler.
|
||||
to UTC would shift logged dinners to the wrong calendar day. `time/tzdata`
|
||||
is imported because the runtime image carries no zoneinfo. All date logic
|
||||
uses that location explicitly and never `time.Local`.
|
||||
- **Auth**: HTTP Basic with one shared household password read from
|
||||
`PASSWORD`; the username is ignored. Compared using
|
||||
`subtle.ConstantTimeCompare` over SHA-256 digests so neither the value nor
|
||||
its length leaks through timing. `/healthz` is the only route outside auth.
|
||||
- **Exposure**: the app is served on a public hostname behind Traefik, which
|
||||
terminates TLS, so Basic credentials are encrypted in transit. A shared
|
||||
password is therefore the only thing between the internet and the app, and
|
||||
it is guarded by a per-address rate limiter: five wrong guesses, then one
|
||||
per ten seconds, answered with `429`. Only requests that actually present
|
||||
a wrong password spend the allowance — a request with no `Authorization`
|
||||
header is the normal browser handshake that opens every session.
|
||||
`X-Forwarded-For` is trusted only when the connection arrived from a
|
||||
private address, so a direct client cannot forge a new identity per
|
||||
attempt. None of this substitutes for a strong password; it only removes
|
||||
brute force as a practical route.
|
||||
- **Auth**: none in the app. Every route is served unauthenticated, because
|
||||
the only client that can reach the app is Traefik, which forwards each
|
||||
request to **Authelia** first. Sessions, brute-force protection and
|
||||
multi-factor are configured there once for every service on the host.
|
||||
Deliberately not reimplemented per app: the earlier in-app HTTP Basic layer
|
||||
meant two prompts for one door, and the weaker of the two was the one
|
||||
holding a shared password.
|
||||
- **Exposure**: served on a public hostname behind Traefik, which terminates
|
||||
TLS. Two invariants carry the whole security model, and both are asserted
|
||||
in `compose.yaml`. The router names the Authelia middleware through `AUTH`
|
||||
— unset or misspelt, Traefik takes the router out of service, so a typo
|
||||
fails shut. And the container publishes no ports, so it is reachable only
|
||||
over the shared proxy network; publishing `8080` would expose an
|
||||
unauthenticated plaintext copy on the host. `/healthz` returns only the
|
||||
version and is safe to bypass in Authelia for monitoring.
|
||||
- **Containers**: built with Podman in development, run under Docker Compose
|
||||
in production. Images are OCI, so one image works with both engines.
|
||||
|
||||
@@ -406,8 +406,10 @@ the server and run with Docker Compose.
|
||||
`.env.example`):
|
||||
Names carry no application prefix: the container namespaces them already.
|
||||
- `REPO` and `TAG` — image coordinates.
|
||||
- `PASSWORD` — the shared password. Required; the app refuses to start
|
||||
without it.
|
||||
- `AUTH` — the Traefik middleware that authenticates the app, e.g.
|
||||
`authelia@docker`. Required; it is the app's only access control.
|
||||
- `HOST` and `CERTRESOLVER` — the hostname Traefik matches on and the
|
||||
resolver that issues its certificate.
|
||||
- `DB` — database file path, default `./data/foodster.db`. The directory is
|
||||
created on startup if missing.
|
||||
- `ENV` — anything but `prod` is prefixed to the browser tab title, so a
|
||||
@@ -422,8 +424,9 @@ the server and run with Docker Compose.
|
||||
doing so would put an unencrypted copy of the app on the host, bypassing
|
||||
the proxy. The hostname lives in `.env` rather than `compose.yaml`, so no
|
||||
infrastructure detail is committed.
|
||||
- **Health**: `GET /healthz` returns the build version and is exempt from
|
||||
auth. There is no Docker `HEALTHCHECK` directive, because a `scratch` image
|
||||
- **Health**: `GET /healthz` returns the build version and nothing else, so it
|
||||
is safe to exempt in Authelia. There is no Docker `HEALTHCHECK` directive,
|
||||
because a `scratch` image
|
||||
has no shell to run one and `restart: unless-stopped` already covers a dead
|
||||
process. Adding one would mean giving the binary a `-healthcheck` flag that
|
||||
calls its own endpoint.
|
||||
|
||||
Reference in New Issue
Block a user