web: /healthz is answered only to local probes (audit I6)
Security audit 2026-10-17, finding I6 (low), stage 3. /healthz is registered outside auth and every proxy fragment passed it through: the live server answered "ok" to the internet, and "unhealthy: postfix" would have been a free tell. Now handleHealth answers only a request with no X-Forwarded-For (nor Forwarded) from loopback or a private address: the image's HEALTHCHECK (127.0.0.1), the host through the loopback-published port (the Docker gateway), a monitor on the Docker network. Every supported proxy appends X-Forwarded-For, so through it the endpoint is 404. The plan named "trusted-proxy peers"; that would have let the internet in again through the trusted proxy, hence this shape. The e2e's readiness probe is the host's, and stays answered. Test: TestHealthzAnswersOnlyLocalProbes. Docs: guide § Container health, architecture.md; CHANGELOG; plan. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,7 +63,7 @@ The panel runs `postfix-reload` through supervisord's XML-RPC interface and
|
||||
waits for its exit status (`internal/supervisor`, `RunOnce`); the program
|
||||
rests EXITED after a run.
|
||||
|
||||
**Liveness:** `GET /healthz` (unauthenticated) returns 200 when panel - whose
|
||||
**Liveness:** `GET /healthz` (unauthenticated; answered only to a request with no `X-Forwarded-For` from loopback or a private address, else 404) returns 200 when panel - whose
|
||||
roles include the DKIM signer - and postfix are RUNNING
|
||||
(`internal/health`, `Liveness`); Docker `HEALTHCHECK` uses the same probe (its
|
||||
90-second start period covers postfix's STARTING).
|
||||
|
||||
+7
-4
@@ -819,15 +819,18 @@ again whole and loses the keys of domains added under 2.8.x.
|
||||
### Container health
|
||||
|
||||
The image declares a Docker `HEALTHCHECK` that probes `GET /healthz` on port
|
||||
8080 (unauthenticated). It returns `200 ok` when the panel - whose roles
|
||||
8080 (unauthenticated, and answered only to a request that did not come
|
||||
through a reverse proxy - no `X-Forwarded-For` - from loopback or a private
|
||||
address; anyone else gets `404`). It returns `200 ok` when the panel - whose roles
|
||||
include the DKIM signer - and Postfix are both `RUNNING` under supervisord;
|
||||
otherwise `503 unhealthy`. This
|
||||
catches a dead mail path that would still leave the HTTP server up, but it
|
||||
does **not** verify TLS certificates, DNS records, or end-to-end delivery —
|
||||
use the authenticated [Overview and Health](#overview-and-health) pages for
|
||||
that. External monitoring
|
||||
can use the same endpoint through the reverse proxy if you expose it, or poll
|
||||
`docker inspect` health state on the host.
|
||||
that. External monitoring runs on the host - `curl -fsS
|
||||
http://127.0.0.1:8080/healthz` - or polls `docker inspect` health state;
|
||||
through the reverse proxy the endpoint is not found, so its answer tells a
|
||||
stranger nothing.
|
||||
|
||||
### Server-level DNS (PTR/rDNS)
|
||||
|
||||
|
||||
@@ -790,9 +790,12 @@ the argument.
|
||||
- [x] **A3.** After five unsolicited reset links in a day, one mail saying
|
||||
further requests are ignored for 24 h, then silence. — **Opus**, 1.5 h.
|
||||
*Done 2026-10-17:* TestResetLinksPauseForADayAfterFive.
|
||||
- [ ] **I6.** `/healthz` answered only to loopback and trusted-proxy peers;
|
||||
- [x] **I6.** `/healthz` answered only to loopback and trusted-proxy peers;
|
||||
the guide's "Container health" says the proxy may expose it on purpose
|
||||
with the fragment line. — **Sonnet**, 0.5 h.
|
||||
with the fragment line. — **Sonnet**, 0.5 h. *Done 2026-10-17,* in another
|
||||
shape: answered to a request with no `X-Forwarded-For` from loopback or a
|
||||
private address - a trusted proxy's peer would have let the internet in
|
||||
again through that proxy; TestHealthzAnswersOnlyLocalProbes.
|
||||
|
||||
### Stage 4 — resources and imports (~5 h)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user