ops-warden/workplans/WARDEN-WP-0031-policy-caller-identity.md
codex 186fa99f58
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
fix(workplans): adopt ADR-007 derived identifiers for unregistered records
These workplans exist only in the retired local hub. Their random pre-ADR-007
identifiers are refused by C-06 as stale references, so they cannot be
registered. Deriving from the canonical record id takes no identity from
anything: central does not hold them and the old ids die with the cache.

Records central already holds were deliberately left untouched.

Refs CUST-WP-0068-T06

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 2583210@bnt-lap001
Assistant-Session: f2bff2d5-e9b2-4338-92ca-10282a927006
2026-08-25 20:16:12 +02:00

6.9 KiB

id type title domain repo status owner topic_slug planning_priority depends_on_workplans related_workplans created updated state_hub_workstream_id
WARDEN-WP-0031 workplan Calling-side identity for flex-auth, so policy.enabled can flip infotech ops-warden finished ops-warden netkingdom P1
WARDEN-WP-0007
WARDEN-WP-0009
2026-08-19 2026-08-19 739bad25-2345-5f4f-aaa3-cc4cd8c71f6c

WARDEN-WP-0031 — Calling-side identity for flex-auth

flex-auth shipped flex-auth-ops-warden (FLEX-WP-0016 T01/T02): an independently rollable in-cluster pin carrying ops-warden's production registry and policy package, on digest sha256:138aa347…, at flex-auth-ops-warden.flex-auth.svc.cluster.local:8080.

It runs callerAuth.mode: warn, and it says why in its own logs:

caller authentication warning: caller is not authenticated

src/warden/policy.py posted /v1/check with no Authorization header. flex-auth authenticates the caller with a Kubernetes TokenReview and binds resource.system: ops-warden to system:serviceaccount:ops-warden:ops-warden; an unauthenticated caller can only be served in warn. So the pin cannot enforce, and per ADHOC-2026-08-17-T01 — policy.enabled must not flip anywhere while /v1/check still answers unauthenticated callers — policy.enabled stays false. The gap is ours, not flex-auth's, and this workplan closes it.

Warn is also not A2 evidence: a request that succeeds because failures are downgraded proves nothing about the enforcing path.

Ownership

Concern Owner
The pin, its digest, callerAuth.mode flex-auth
Sending a caller identity on /v1/check ops-warden (this workplan)
Choosing the token source on a given host ops-warden operator
policy.enabled: true in warden.yaml ops-warden operator

Design note — fail closed on identity too

When a caller token is configured but cannot be obtained, check_sign_policy raises under fail_closed rather than retrying unauthenticated. Falling back to an anonymous call is precisely the behaviour that keeps the pin in warn; a gate that silently degrades to the ungated path is not a gate (ADR-0004's choke point argument, applied to ops-warden as a caller).

Tasks

id: WARDEN-WP-0031-T01
status: done
priority: high
state_hub_task_id: "d3b7c701-bcdd-53f9-aa72-6f289bf5909b"

Caller identity on the outbound policy call. policy.caller_auth in warden.yaml (mode: none | file | env | command, token_path, token_env, command, audience); src/warden/caller_identity.py resolves the token at call time and never caches, logs, or echoes it (ADR-0002); both check_sign_policy and check_fetch_policy attach Authorization: Bearer …. Whitespace-bearing and empty tokens are rejected before the call, because flex-auth rejects them outright.

Done 2026-08-19. mode: none remains the default, so behaviour is unchanged until an operator opts in. Tests in tests/test_policy.py.

id: WARDEN-WP-0031-T02
status: done
priority: high
state_hub_task_id: "b77b3c80-a168-564a-9b7f-3063aefc3c2e"

Readiness gate. scripts/check_policy_caller_identity.py — read-only: config loads, mode is not none, a token is actually obtainable, and with --url a live /v1/check against a port-forward of the warn pin. Prints the token's length and a truncated SHA-256 fingerprint only, so its output is safe to paste into a handoff. Exit 0 ready / 1 not ready / 2 bad input. Distinguishes 401 (token not accepted — audience or binding) from 403 (authenticated but not allowed to represent system: ops-warden).

Done 2026-08-19.

id: WARDEN-WP-0031-T03
status: done
priority: medium
state_hub_task_id: "4245155e-6c71-5425-b574-11f61e1d4461"

Docs. examples/warden.production.example.yaml gains the caller_auth block with both realistic sources, and its flex_auth_url is corrected — the example pointed at flex-auth.flex-auth.svc.cluster.local, a Service that does not exist. wiki/PolicyGatedSigning.md gains the caller-identity section and the flip sequence.

Done 2026-08-19.

id: WARDEN-WP-0031-T04
status: done
priority: high
state_hub_task_id: "f3834af7-2a08-51dd-bf31-8ce8550de699"

Pick the token source and prove it against the warn pin. Operator work on the real host: kubectl create token (workstation) or a projected token (in-cluster PEP), then python scripts/check_policy_caller_identity.py --url http://127.0.0.1:19090 against a port-forward. Expect effect=allow for agt-state-hub-bridge while the warn log stops printing caller authentication warning — the absence of that line, not the allow, is the evidence.

Done 2026-08-19. Source is mode: commandkubectl create token ops-warden -n ops-warden --audience flex-auth --duration 10m against the railiance01 cluster (tunnel k3s-api-railiance01, local 16444; 16443 was CoulombCore's k3s, a different cluster, which is why a system:masters cert 401s there — not a port collision, as first reported). deploy/kubernetes/caller-identity.yaml creates the Namespace and ServiceAccount the binding names — no RBAC, automountServiceAccountToken: false; it exists only to be TokenReviewed. Gate exits 0 live: HTTP 200, effect=allow, decision:f3f7c88f9585582a, and the pin's caller authentication warning count held at 4 across two authenticated runs. Evidence: history/2026-08-19-flex-auth-caller-identity-evidence.md.

id: WARDEN-WP-0031-T05
status: cancel
priority: high
state_hub_task_id: "3f6dc609-89db-52eb-a6f9-d2fd271be821"

Sequence the flip. Only after T04: tell flex-auth to set callerAuth.mode: enforce on flex-auth-ops-warden (their FLEX-WP-0016 T03), re-run the gate against the enforcing pin, and only then set policy.enabled: true with fail_closed: true. Flipping before enforce buys nothing; flipping before T04 401s every warden sign.

Deferred 2026-08-19 under ADR-0006 — not blocked, decided against for now.

flex-auth did enforce (FLEX-WP-0016 T03, Helm rev 2): the pin runs --caller-auth-mode enforce, the gate exits 0 against it (HTTP 200, effect=allow, decision:f3f7c88f9585582a), and an anonymous /v1/check is 401. Everything needed to flip was in place.

It was not flipped. policy.enabled is a single repo-wide boolean, and with fail_closed: true it makes flex-auth a hard dependency of every warden sign — including the certs the ops-bridge tunnels depend on, one of which carries the policy call itself. Uniform enforcement across an estate being actively rebuilt would harden the access needed to perform the rebuild. Enforcement belongs to a zone, not to the repo; that is ADR-0006, and the model is WARDEN-WP-0032. This task resumes as WARDEN-WP-0032-T05.

Also shipped while proving the flip: the gate URL is now a managed ops-bridge tunnel flex-auth-ops-warden-railiance01 (-L 19090:10.43.1.165:8080) instead of a hand-run kubectl port-forward.