fix: confine PDP checks to the admitted local transport
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6-astra
Assistant-Session: 01a12d13-27f2-7163-b8c0-f682af02772c
This commit is contained in:
tegwick 2026-10-12 08:27:37 +02:00
parent 0db6adef69
commit d379a3dfba
6 changed files with 268 additions and 50 deletions

View file

@ -23,21 +23,40 @@ $ getent hosts this-service-does-not-exist.flex-auth.svc.cluster.local
`*.svc.cluster.local` name answers with one unrelated public address. Sending
there ships the CheckRequest body and our bearer token to a third party.
`flex-auth.decision-record.v1` carries **no signature**, and every digest in it
is either sent by us or published in flex-auth's repo, so a forger reproduces
all three exactly. Digests establish integrity of the binding, never
`flex-auth.decision-record.v1` supports a detached Ed25519 signature on the
producer side (FLEX-WP-0024), but this consumer does not yet verify it against
independently admitted keys. Without that verification, every binding digest
is either sent by us or published in flex-auth's repo, so a forger can
reproduce those digests. Digests establish integrity of the binding, never
authenticity of the source. As flex-auth put it in `FLEX-DEC-2026-010`:
> Fail-closed protects against a PDP that is ABSENT, not against one that LIES.
Until detached signatures ship (`FLEX-WP-0024`), the address shape is the only
thing standing in for responder authenticity. That is why it is enforced.
The literal loopback address restriction limits the destination; it does not
prove responder identity. The operator must verify the cluster and named pod
behind the port-forward. Signed consumer enforcement, independently admitted
verification keys, rotation and downgrade tests remain open in SECRETS-WP-0014
and Custodian G13. Producer support alone does not close that gap.
The Check client disables automatic redirects, environment/system proxies and
the process-global urllib opener. A 3xx response fails closed without contacting
the redirect destination. URLs must be HTTP(S) bare origins with a literal
loopback IP and optional nonzero port: no hostname (including `localhost`),
userinfo, path prefix, query, fragment or whitespace/control characters. Existing
`localhost` configuration must use `127.0.0.1` or `[::1]`. Invalid configuration
is rejected before reading the bearer file. Responses are limited to 512 KiB;
oversized, invalid UTF-8 and non-object JSON bodies fail closed. These rules also
apply when the approval flow invokes the PDP client.
Regression tests use disposable local HTTP servers and synthetic credentials;
they do not demonstrate identity or signature verification on the native pin.
## The supported path
Loopback `kubectl port-forward` to the named pod. It resolves no DNS name,
targets one pod explicitly, and rides the Kubernetes API server's TLS with our
cluster credentials — so it authenticates the responder transitively. Note this
Use literal loopback `kubectl port-forward` to the admitted named pod after
verifying the cluster identity and pod identity. That path rides the Kubernetes
API server's TLS with the operator's cluster credentials; the client cannot infer
that binding from its local URL. Note this
is the exact reverse of the caller direction, where port-forward bypasses the
NetworkPolicy (`FLEX-WP-0023`); the two properties point opposite ways and
summarising either as "the network protects it" gets one backwards.