WARDEN-WP-0031 T04: prove ops-warden's caller identity against the live pin
flex-auth's binding names system:serviceaccount:ops-warden:ops-warden, and that ServiceAccount did not exist. deploy/kubernetes/caller-identity.yaml creates it plus its namespace — no RBAC, automount off; it is never used to call the Kubernetes API, only to be TokenReviewed. Applied to the railiance01 cluster. Operator warden.yaml now uses caller_auth mode: command (kubectl create token, audience flex-auth, 10m). Gate exits 0 live against a port-forward of the pin: HTTP 200, effect=allow, decision:f3f7c88f9585582a. The evidence is not that allow — warn allows anonymous callers too. It is that the pin's "caller authentication warning" count held at 4 across two authenticated runs. That is the ADHOC-2026-08-17-T01 condition. Also gives the readiness probe a structurally complete context, so a deny means the policy said no rather than the probe being malformed. policy.enabled stays false. T05 waits on flex-auth setting callerAuth.mode: enforce (their FLEX-WP-0016 T03). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9b9bfe565a
commit
654c05dece
5 changed files with 166 additions and 3 deletions
|
|
@ -27,6 +27,7 @@
|
||||||
| workplan | WARDEN-WP-0028 | finished | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
|
| workplan | WARDEN-WP-0028 | finished | — | workplans/WARDEN-WP-0028-tenant-secret-custody.md |
|
||||||
| workplan | WARDEN-WP-0029 | finished | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
|
| workplan | WARDEN-WP-0029 | finished | — | workplans/WARDEN-WP-0029-policy-front-door-and-founder-surface.md |
|
||||||
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
| workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
||||||
|
| workplan | WARDEN-WP-0031 | active | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
||||||
| task | ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
|
| task | ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md |
|
||||||
| task | ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md |
|
| task | ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md |
|
||||||
| task | ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md |
|
| task | ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md |
|
||||||
|
|
@ -108,3 +109,8 @@
|
||||||
| task | WARDEN-WP-0030-T03 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
| task | WARDEN-WP-0030-T03 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
||||||
| task | WARDEN-WP-0030-T04 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
| task | WARDEN-WP-0030-T04 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
||||||
| task | WARDEN-WP-0030-T05 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
| task | WARDEN-WP-0030-T05 | done | — | workplans/WARDEN-WP-0030-delegation-register.md |
|
||||||
|
| task | WARDEN-WP-0031-T01 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
||||||
|
| task | WARDEN-WP-0031-T02 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
||||||
|
| task | WARDEN-WP-0031-T03 | done | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
||||||
|
| task | WARDEN-WP-0031-T04 | wait | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
||||||
|
| task | WARDEN-WP-0031-T05 | wait | — | workplans/WARDEN-WP-0031-policy-caller-identity.md |
|
||||||
|
|
|
||||||
39
deploy/kubernetes/caller-identity.yaml
Normal file
39
deploy/kubernetes/caller-identity.yaml
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
# ops-warden's calling identity for flex-auth (WARDEN-WP-0031 T04).
|
||||||
|
#
|
||||||
|
# flex-auth's `flex-auth-ops-warden` pin binds `resource.system: ops-warden` to
|
||||||
|
# the principal `system:serviceaccount:ops-warden:ops-warden` and TokenReviews
|
||||||
|
# the caller's bearer token with audience `flex-auth` (FLEX-WP-0016).
|
||||||
|
#
|
||||||
|
# This ServiceAccount is the subject of that binding. It holds no RBAC at all —
|
||||||
|
# it is never used to talk to the Kubernetes API, only to be *reviewed* by it.
|
||||||
|
# A workstation `warden sign` mints a short-lived bound token against it:
|
||||||
|
#
|
||||||
|
# kubectl create token ops-warden -n ops-warden \
|
||||||
|
# --audience flex-auth --duration 10m
|
||||||
|
#
|
||||||
|
# Boundary note: cluster resources are railiance-platform's to own. This
|
||||||
|
# manifest lives here because the identity is ops-warden's and flex-auth's
|
||||||
|
# binding names it; railiance-platform should adopt it into the cluster's own
|
||||||
|
# manifests, at which point this file becomes the record of what was applied
|
||||||
|
# rather than the source of truth (ADR-0003 — cover the gap, name the owner).
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Namespace
|
||||||
|
metadata:
|
||||||
|
name: ops-warden
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/managed-by: ops-warden
|
||||||
|
netkingdom.coulomb.social/purpose: caller-identity
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: ops-warden
|
||||||
|
namespace: ops-warden
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/managed-by: ops-warden
|
||||||
|
annotations:
|
||||||
|
netkingdom.coulomb.social/bound-by: >-
|
||||||
|
flex-auth-ops-warden callerAuth binding
|
||||||
|
ops-warden=system:serviceaccount:ops-warden:ops-warden (FLEX-WP-0016)
|
||||||
|
netkingdom.coulomb.social/workplan: WARDEN-WP-0031
|
||||||
|
automountServiceAccountToken: false
|
||||||
83
history/2026-08-19-flex-auth-caller-identity-evidence.md
Normal file
83
history/2026-08-19-flex-auth-caller-identity-evidence.md
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
# flex-auth caller identity — live evidence (WARDEN-WP-0031 T04)
|
||||||
|
|
||||||
|
**Date:** 2026-08-19
|
||||||
|
**Pin:** `flex-auth-ops-warden`, railiance01 cluster, namespace `flex-auth`,
|
||||||
|
Service `flex-auth-ops-warden:8080`, digest `sha256:138aa347…`, running
|
||||||
|
`--caller-auth-mode warn --caller-kubernetes-url https://10.43.0.1
|
||||||
|
--caller-binding ops-warden=system:serviceaccount:ops-warden:ops-warden`
|
||||||
|
(read off the live Deployment, matching FLEX-WP-0016 T02).
|
||||||
|
|
||||||
|
Reached from the workstation by port-forward; the tunnel `k3s-api-railiance01`
|
||||||
|
(local `16444`) carries the API. Note `~/.kube/config` / `config-hosteurope`
|
||||||
|
points at `16443`, which two bridge tunnels claim
|
||||||
|
(`k3s-api-coulombcore` and `k3s-api-haskelseed`) — that collision is why a
|
||||||
|
`system:masters` cert returns `Unauthorized` there. Use
|
||||||
|
`--kubeconfig ~/.kube/config-railiance01`.
|
||||||
|
|
||||||
|
## Baseline — before
|
||||||
|
|
||||||
|
Unauthenticated `POST /v1/check` was **served**, and the pin logged:
|
||||||
|
|
||||||
|
```
|
||||||
|
caller authentication warning: caller is not authenticated
|
||||||
|
```
|
||||||
|
|
||||||
|
That is the whole reason `policy.enabled` could not flip: warn mode answers
|
||||||
|
anonymous callers, so nothing about the enforcing path was ever exercised.
|
||||||
|
|
||||||
|
## What was created
|
||||||
|
|
||||||
|
`deploy/kubernetes/caller-identity.yaml` — Namespace `ops-warden` and
|
||||||
|
ServiceAccount `ops-warden/ops-warden`, `automountServiceAccountToken: false`,
|
||||||
|
**no RBAC of any kind**. It is never used to call the Kubernetes API; it exists
|
||||||
|
only to be the subject of flex-auth's TokenReview. Applied 2026-08-19.
|
||||||
|
|
||||||
|
Cluster resources are railiance-platform's to own — this is an ADR-0003 interim
|
||||||
|
cover, and the manifest names that owner in its header.
|
||||||
|
|
||||||
|
## Token source
|
||||||
|
|
||||||
|
`policy.caller_auth.mode: command` in `~/.config/warden/warden.yaml`:
|
||||||
|
|
||||||
|
```
|
||||||
|
kubectl --kubeconfig ~/.kube/config-railiance01 create token ops-warden \
|
||||||
|
-n ops-warden --audience flex-auth --duration 10m
|
||||||
|
```
|
||||||
|
|
||||||
|
Audience `flex-auth` is required: `internal/callerauth/tokenreview.go` sends
|
||||||
|
`spec.audiences: ["flex-auth"]` and rejects an identity whose audiences do not
|
||||||
|
contain it. 900-char bound token, 10 minute TTL, minted per call, never stored.
|
||||||
|
|
||||||
|
## Evidence
|
||||||
|
|
||||||
|
```
|
||||||
|
$ python3 scripts/check_policy_caller_identity.py --url http://127.0.0.1:19090
|
||||||
|
✓ warden.yaml: loaded; policy.enabled=false
|
||||||
|
✓ caller_auth.mode: command
|
||||||
|
✓ caller token: obtained, 900 chars, sha256:e50da3ec6769
|
||||||
|
✓ live /v1/check: HTTP 200, effect=allow, decision=decision:f3f7c88f9585582a
|
||||||
|
READY
|
||||||
|
```
|
||||||
|
|
||||||
|
The decisive check is not that allow — warn would have allowed an anonymous
|
||||||
|
caller too. It is the warning count:
|
||||||
|
|
||||||
|
```
|
||||||
|
warnings before: 4
|
||||||
|
warnings after 2 authenticated gate runs: 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The pin authenticated the caller and had nothing to warn about. That is the
|
||||||
|
condition ADHOC-2026-08-17-T01 required before `policy.enabled` may flip
|
||||||
|
anywhere.
|
||||||
|
|
||||||
|
## What is still open
|
||||||
|
|
||||||
|
`policy.enabled` stays **false**. The remaining sequence (T05) is flex-auth's
|
||||||
|
move first: `callerAuth.mode: enforce` on this pin (their FLEX-WP-0016 T03),
|
||||||
|
re-run the gate against the enforcing pin, then `policy.enabled: true` with
|
||||||
|
`fail_closed: true`, then an end-to-end `warden sign` — which additionally needs
|
||||||
|
a scoped `VAULT_TOKEN` via `ops-warden-warden-sign-token`.
|
||||||
|
|
||||||
|
Flipping before enforce buys nothing; flipping before this task would have
|
||||||
|
401'd every `warden sign`.
|
||||||
|
|
@ -97,7 +97,11 @@ def run_checks(config_path: Optional[Path], url: Optional[str]) -> List[Check]:
|
||||||
import httpx # local import: the offline checks above must not need it
|
import httpx # local import: the offline checks above must not need it
|
||||||
|
|
||||||
probe = {
|
probe = {
|
||||||
"subject": {"id": "readiness", "type": "agt", "tenant": policy.tenant},
|
"subject": {
|
||||||
|
"id": "agt-state-hub-bridge",
|
||||||
|
"type": "agt",
|
||||||
|
"tenant": policy.tenant,
|
||||||
|
},
|
||||||
"action": "sign",
|
"action": "sign",
|
||||||
"resource": {
|
"resource": {
|
||||||
"id": "ssh-cert:actor/agt-state-hub-bridge",
|
"id": "ssh-cert:actor/agt-state-hub-bridge",
|
||||||
|
|
@ -105,7 +109,17 @@ def run_checks(config_path: Optional[Path], url: Optional[str]) -> List[Check]:
|
||||||
"system": policy.system,
|
"system": policy.system,
|
||||||
"tenant": policy.tenant,
|
"tenant": policy.tenant,
|
||||||
},
|
},
|
||||||
"context": {"readiness_probe": True},
|
"context": {
|
||||||
|
# A structurally complete context, so a deny means the policy said
|
||||||
|
# no — not that the probe was malformed. What is under test here is
|
||||||
|
# the caller identity, and that is answered by the HTTP status.
|
||||||
|
"actor_name": "agt-state-hub-bridge",
|
||||||
|
"actor_type": "agt",
|
||||||
|
"principals": ["agt-task-bridge"],
|
||||||
|
"ttl_hours": 24,
|
||||||
|
"pubkey_fingerprint": "sha256:" + "0" * 64,
|
||||||
|
"readiness_probe": True,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
try:
|
try:
|
||||||
response = httpx.post(
|
response = httpx.post(
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ related_workplans:
|
||||||
- WARDEN-WP-0009
|
- WARDEN-WP-0009
|
||||||
created: "2026-08-19"
|
created: "2026-08-19"
|
||||||
updated: "2026-08-19"
|
updated: "2026-08-19"
|
||||||
|
state_hub_workstream_id: "7284a398-d529-4fe3-871e-e2505064be5e"
|
||||||
---
|
---
|
||||||
|
|
||||||
# WARDEN-WP-0031 — Calling-side identity for flex-auth
|
# WARDEN-WP-0031 — Calling-side identity for flex-auth
|
||||||
|
|
@ -63,6 +64,7 @@ point argument, applied to ops-warden as a caller).
|
||||||
id: WARDEN-WP-0031-T01
|
id: WARDEN-WP-0031-T01
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "cd65c834-214a-4bbb-92e4-262f124fc79d"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Caller identity on the outbound policy call.** `policy.caller_auth` in
|
**Caller identity on the outbound policy call.** `policy.caller_auth` in
|
||||||
|
|
@ -80,6 +82,7 @@ until an operator opts in. Tests in `tests/test_policy.py`.
|
||||||
id: WARDEN-WP-0031-T02
|
id: WARDEN-WP-0031-T02
|
||||||
status: done
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "9abdeb78-bbbb-4ff5-b3d7-5646116cf68b"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Readiness gate.** `scripts/check_policy_caller_identity.py` — read-only:
|
**Readiness gate.** `scripts/check_policy_caller_identity.py` — read-only:
|
||||||
|
|
@ -96,6 +99,7 @@ Done 2026-08-19.
|
||||||
id: WARDEN-WP-0031-T03
|
id: WARDEN-WP-0031-T03
|
||||||
status: done
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
|
state_hub_task_id: "d3ff2ae1-2dac-45d0-b229-8b23c27c3972"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Docs.** `examples/warden.production.example.yaml` gains the `caller_auth`
|
**Docs.** `examples/warden.production.example.yaml` gains the `caller_auth`
|
||||||
|
|
@ -108,8 +112,9 @@ Done 2026-08-19.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: WARDEN-WP-0031-T04
|
id: WARDEN-WP-0031-T04
|
||||||
status: wait
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "3d9d1a05-adea-4dcb-b71b-bfb2235aa198"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Pick the token source and prove it against the warn pin.** Operator work on
|
**Pick the token source and prove it against the warn pin.** Operator work on
|
||||||
|
|
@ -120,10 +125,22 @@ against a port-forward. Expect `effect=allow` for `agt-state-hub-bridge` while
|
||||||
the warn log stops printing `caller authentication warning` — the absence of
|
the warn log stops printing `caller authentication warning` — the absence of
|
||||||
that line, not the allow, is the evidence.
|
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`; note `16443` is claimed by
|
||||||
|
two tunnels at once, which is why a `system:masters` cert 401s there).
|
||||||
|
`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
|
```task
|
||||||
id: WARDEN-WP-0031-T05
|
id: WARDEN-WP-0031-T05
|
||||||
status: wait
|
status: wait
|
||||||
priority: high
|
priority: high
|
||||||
|
state_hub_task_id: "8b8e68c4-587d-490d-a98f-840aaff0fdb8"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Sequence the flip.** Only after T04: tell flex-auth to set
|
**Sequence the flip.** Only after T04: tell flex-auth to set
|
||||||
|
|
@ -131,3 +148,7 @@ priority: high
|
||||||
re-run the gate against the enforcing pin, and only then set
|
re-run the gate against the enforcing pin, and only then set
|
||||||
`policy.enabled: true` with `fail_closed: true`. Flipping before enforce buys
|
`policy.enabled: true` with `fail_closed: true`. Flipping before enforce buys
|
||||||
nothing; flipping before T04 401s every `warden sign`.
|
nothing; flipping before T04 401s every `warden sign`.
|
||||||
|
|
||||||
|
Asked flex-auth 2026-08-19 with the T04 evidence. Waiting on their enforce.
|
||||||
|
The closing `warden sign` also needs a scoped `VAULT_TOKEN` via
|
||||||
|
`ops-warden-warden-sign-token` (operator authenticates to OpenBao).
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue