Assistant: codex Assistant-Model: gpt-6-astra Assistant-Session: 01a140c4-354a-7823-8f0d-8f717397f786
8.8 KiB
Reaching the access-engine pin
The supported way for this engine to reach flex-auth-secrets-engine, and the
reason the address is enforced in code rather than left to configuration.
Never the Service DNS name
SECRETS_ENGINE_PDP_URL=http://flex-auth-secrets-engine.flex-auth.svc.cluster.local:8080
is refused by decision_check.require_supported_pdp_address. From a workstation
that name does not fail — it resolves, to the wrong host:
$ getent hosts flex-auth-secrets-engine.flex-auth.svc.cluster.local
80.158.43.29 ...svc.cluster.local.ad.binect.de
$ getent hosts this-service-does-not-exist.flex-auth.svc.cluster.local
80.158.43.29 (identical — proves suffix expansion, not a record)
resolv.conf carries search ad.binect.de, a wildcard zone, so every
*.svc.cluster.local name answers with one unrelated public address. Sending
there ships the CheckRequest body and our bearer token to a third party.
As of source ff1a13d (2026-10-16), this consumer requires the producer's
detached Ed25519 signature using independently admitted local keys and exact
signed evaluator/mode/policy pin. The complete CLI wheel-plus-helper bundle and
strict task handoff are implemented in source. Native signer custody, key/pin
admission, installed placement and actual consumer receipts remain unaccepted;
source enforcement does not establish those facts. See
the scoped native preparation.
Historical 2026-10-12 finding: prior consumer source did not verify producer signatures, so a forger could reproduce public request digests. The literal loopback address restriction limited the destination and did not prove responder identity. That source defect is repaired; FLEX-DEC-2026-010's historical warning that an absent PDP and a lying responder require different controls remains relevant. The independently admitted native trust and rollout obligations in SECRETS-WP-0014 T02/T03 remain open.
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.
Historical transport procedure and current admission gate
The following transport example records the earlier supported access shape; it
is incomplete for current native use without independently admitted signing trust,
the reviewed CLI bundle and exact owner configuration. It grants no token mint,
installation or live action. 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.
# 1. Target the pod by name, not the Service.
POD=$(kubectl -n flex-auth get pods \
-l app.kubernetes.io/name=flex-auth-secrets-engine \
-o jsonpath='{.items[0].metadata.name}')
kubectl -n flex-auth port-forward "pod/$POD" 18080:8080 --address 127.0.0.1 &
# 2. Fresh bounded caller token per session. Audience MUST be flex-auth;
# caller auth is `enforce` as of Helm revision 3.
umask 077
kubectl -n secrets-engine create token secrets-engine \
--audience flex-auth --duration 10m > "$PDP_TOKEN"
chmod 600 "$PDP_TOKEN" # outside any Git worktree
# 3. Point the engine at the forward.
export SECRETS_ENGINE_PDP_URL=http://127.0.0.1:18080
export SECRETS_ENGINE_PDP_TOKEN_FILE="$PDP_TOKEN"
export SECRETS_ENGINE_AUTHORIZATION_POLICY_PACKAGE=secrets-engine.catalog-lane.lifecycle
export SECRETS_ENGINE_AUTHORIZATION_POLICY_VERSION=v2
Shred the token file and stop the forward when the session ends. The token is ten minutes; it is a caller credential, not an authorization.
ServiceAccount secrets-engine/secrets-engine has automountServiceAccountToken: false and no role bindings — it exists to be a caller identity and nothing else.
Pin v2, never v1
v1 shipped and was deployed with no input.tenant rule at all; a rotate under
tenant:coulomb returned allow. v2 supersedes rather than amends it so the
change is visible in the version string. Current consumer configuration and signed
trust must explicitly pin the admitted v2 artifact; the validator does not impose
a universal hard-coded package/version default. A v1 configuration is not admitted
by this proposal. See docs/tenant-alignment.md.
The digest join, and why it is not a digest comparison
Proved live on 2026-09-07, fixed the same day.
The evaluator normalizes before hashing: the request tenant is copied onto
subject and resource, and a registry hit copies type, tenant and
selected attributes onto the refs. So binding.request_digest is over
material we did not send and cannot reproduce, and the validator's original
byte-equality check against our unenriched request rejected every real allow.
flex-auth's canonical-request-digest.md already published the consumer rule,
and the fix follows it rather than inventing one:
A consumer that re-hashes the original unenriched request will not match a decision that turned on registry attributes. Compare structured
bindingfields to the proposed action, and treatrequest_digestas the evaluator's statement of what it hashed. To recompute independently, hash the same normalized tuple the binding carries.
The bullets in earlier revisions described the September structured-enrichment
check. Current source has since moved to the producer's submitted-request replay
contract: validate_decision_envelope validates the exact submitted digest,
opaque enriched digest shape, approval-free correspondence, policy pin and
lifetime on an authenticated envelope. It does not independently reconstruct the
full enriched tuple or native five-field claim in the generic CLI path.
The separate strict task_execution_handoff additionally recomputes the evaluated
and approval-free digests, verifies exact descriptor/context/native claim joins
(including the served native digest), and binds signed enforced caller provenance.
That source seam is not an execute-ccr CLI command and does not admit the proposed
service caller mapping or enable either task policy's runtime false gate.
The lesson worth keeping: the replay fixtures could not catch this, because
_request_from() rebuilds the request from the binding, so the tests hashed the
evaluator's output and compared it to the evaluator's output. Only a real
request through this access path exposed it.
Mandatory responder authentication (WP14 source preparation)
Every serving authorize_action path requires SECRETS_ENGINE_PDP_TRUST_FILE
and SECRETS_ENGINE_PDP_VERIFIER. These fail closed before approval/PDP credential
access when a PDP path is configured. Trust must be an absolute regular JSON file,
not group/world writable, admitted independently of the responder or its keys
endpoint. Neither a loopback URL nor installing source admits production keys.
See verifier/README.md for build, source pin/update and installation requirements.
Trust schema secrets-engine.pdp-trust.v1 requires exactly schema, evaluator,
mode, policy_package, policy_version, policy_package_digest, and keys
(a map of signer kid to base64url-unpadded Ed25519 public key). The producer has
no separate issuer or instance identifier: issuer binding is the admitted signer
plus signed provenance evaluator/mode; the exact policy coordinate and artifact
digest identify the admitted pin. Deployment key custody must scope the signer to
that responder; shared signing keys cannot prove a distinct pod identity.
Rotation is an explicit reviewed admission update: admit old/new keys for a bounded overlap, switch producer signer, verify the actual consumer, then remove the old kid and prove refusal. Keys are reloaded each call; no endpoint discovery, cached key fallback, unsigned exception, or automatic admission exists. These source checks do not constitute Platform key admission or native rollout acceptance.