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

170 lines
6.9 KiB
Markdown

---
id: WARDEN-WP-0031
type: workplan
title: "Calling-side identity for flex-auth, so policy.enabled can flip"
domain: infotech
repo: ops-warden
status: finished
owner: ops-warden
topic_slug: netkingdom
planning_priority: P1
depends_on_workplans:
- WARDEN-WP-0007
related_workplans:
- WARDEN-WP-0009
created: "2026-08-19"
updated: "2026-08-19"
state_hub_workstream_id: "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
```task
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`.
```task
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.
```task
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.
```task
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: command``kubectl 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`.
```task
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`.