feat(config): SEC-013 — advisory WARN on external sslmode=disable

Acquisition-audit SEC-013 closure (Sprint 2 ACQ, 2026-05-16).

Add a post-Validate advisory WARN (NOT fail-closed) that fires when
`CERTCTL_DATABASE_URL` parses as a Postgres URL with
`sslmode=disable` AND the host is outside the local safelist.

The advisory exists because the legitimate compose / Helm topology
genuinely uses sslmode=disable over the Docker bridge — failing
closed would break the production-shaped quickstart — but pointing
CERTCTL_DATABASE_URL at a managed-Postgres host (RDS / Cloud SQL /
Azure Database) without flipping sslmode to verify-full puts the
entire control plane's Postgres traffic on the wire in cleartext.

Safelist (silenced):
  - localhost, 127.0.0.1, ::1
  - postgres (compose default service name)
  - certctl-postgres (compose / Helm service name)
  - *.svc.cluster.local (K8s in-cluster service-name convention)

Anything else → `slog.Warn` with structured `host=` + `sslmode=`
fields plus a pointer to docs/operator/database-tls.md for the
verify-full upgrade procedure.

Tests:
  - TestWarnExternalSslmodeDisable_FiresOnExternalHost
  - TestWarnExternalSslmodeDisable_QuietForLocalSafelist (6 subtests)
  - TestWarnExternalSslmodeDisable_QuietWithoutDisable (3 subtests)
  - TestWarnExternalSslmodeDisable_QuietOnUnparseableOrEmpty (3 subtests)

Docs: docs/operator/security.md gains a Postgres transport
encryption subsection covering both SEC-013 (this commit) and
SEC-014 (loopback host-port bind, prior commit); the deep procedure
remains at docs/operator/database-tls.md.
This commit is contained in:
shankar0123
2026-05-16 17:12:58 +00:00
parent 7e2481b225
commit 578ac4ec68
3 changed files with 225 additions and 1 deletions
+49 -1
View File
@@ -58,7 +58,55 @@ For certificates issued to systems where revocation correctness matters:
## Postgres transport encryption
See [docs/database-tls.md](database-tls.md).
**Audit references:** SEC-013 (advisory) and SEC-014 (host-port bind),
both closed in Sprint 2 of the 2026-Q2 acquisition audit
(2026-05-16).
The full upgrade procedure (sslmode flags, CA bundle paths, Helm chart
values, AWS RDS / Google Cloud SQL / Azure Database notes) lives at
[docs/operator/database-tls.md](database-tls.md). The summary of the
two operator-visible defenses certctl ships:
### SEC-014 — Postgres host port is loopback-only
`deploy/docker-compose.yml` and `deploy/docker-compose.test.yml` both
publish Postgres on `127.0.0.1:5432:5432` rather than `5432:5432`.
The default Docker port-binding behavior is to bind to `0.0.0.0`,
which exposes Postgres on every interface of the host — including any
public-facing NICs the operator did not realize were attached. The
loopback bind closes that footgun without breaking the
certctl-server hop (which goes over the `certctl-network` Docker
bridge, not over the host port).
Operators who genuinely need to reach Postgres from another host —
e.g. a separate metrics box running `postgres_exporter` — should
either (1) attach that host into the same Docker network, (2) tunnel
through SSH (`ssh -L`), or (3) re-publish the port with explicit
`bind:` configuration and a documented network-layer access control.
Loosening the loopback bind without one of those is a regression.
### SEC-013 — advisory WARN on external `sslmode=disable`
`internal/config/config.go::Validate` emits an `slog.Warn` (NOT a
fail-closed error) when `CERTCTL_DATABASE_URL` parses as a Postgres
URL with `sslmode=disable` AND the host is outside the local
safelist (`localhost` / `127.0.0.1` / `::1` / `postgres` /
`certctl-postgres` / `*.svc.cluster.local`). The advisory exists
because the legitimate compose / Helm topology genuinely uses
`sslmode=disable` over the Docker bridge — failing closed would
break the production-shaped quickstart — but pointing
`CERTCTL_DATABASE_URL` at a managed-Postgres host (RDS, Cloud SQL,
Azure Database) without flipping `sslmode` to `verify-full` puts
the entire control plane's Postgres traffic on the wire in
cleartext. The WARN surfaces that landmine on every boot so the
operator notices it in the journal even if the rest of the boot
sequence looks healthy.
To clear the WARN: set `CERTCTL_DATABASE_URL` to a URL with
`sslmode=verify-full` and `sslrootcert=<ca-bundle-path>`. The full
procedure (CA-bundle materialization, Helm chart values, secret-
manager wiring) is in
[docs/operator/database-tls.md](database-tls.md).
## Encryption at rest