KEY-WP-0039: probe registration packet and attended negative-probe script

T01 done: railiance-platform agreed the case list. The packet adds an
otherwise-valid expiry client and pins audience on the claim probes so each
token fails only the claim under test. The script prints one refused boolean
and refusal class per case, revokes any accepted token, and never counts a
transport failure as a refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 779409@bnt-lap001
Assistant-Session: c658ca06-19b5-468d-be64-2985dc20bfd3
This commit is contained in:
tegwick 2026-10-10 13:18:24 +02:00
parent 01b318db87
commit bae9f85160
4 changed files with 440 additions and 3 deletions

View file

@ -4,7 +4,7 @@ type: workplan
title: "Negative-check tokens for the secrets-engine OpenBao JWT role"
domain: infotech
repo: key-cape
status: proposed
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
@ -47,7 +47,7 @@ from `secrets-engine` in exactly one property each.
```task
id: KEY-WP-0039-T01
status: todo
status: done
priority: medium
state_hub_task_id: "4d06b059-3768-53bb-83a4-a9a71c0850b8"
```
@ -76,6 +76,25 @@ The expired-JWT case reuses any probe client: request a token, wait past its
Acceptance: railiance-platform agrees the packet covers every refusal that
CCR-2026-0038 names.
2026-10-10: railiance-platform agreed that the case list covers every
CCR-2026-0038 claim negative. The remaining CCR checks (capabilities-self deny, no
provider fallback) are theirs and secrets-engine's. Packet written at
`docs/openbao-negative-probe-clients.yaml`. It departs from the agreed list in two
ways, both sent back for re-agreement:
- **Expired case.** A token from another probe already fails a claim, so its
refusal does not show that expiry was checked. It needs a sixth client,
`openbao-probe-expiry`, identical to `secrets-engine-openbao` in every claim. That
makes it a second credential able to log in as secrets-engine for the session.
OpenBao's `expiration_leeway` (150s by default) also extends the wait.
- **Audience pin.** A service token's `aud` is its `clientId` unless `audience` is
set. Every probe except the aud probe therefore pins
`audience: secrets-engine-openbao`, so it fails only on its own claim.
Condition (a), no OpenBao custody, holds. The values still have to reach KeyCape's
environment, because KeyCape resolves `secretRef` at startup. The packet uses a
transient Kubernetes Secret that is deleted in the same session.
```task
id: KEY-WP-0039-T02
status: wait
@ -87,7 +106,16 @@ state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
### T02 — Register the probe clients, run the attended probe, then remove them
Write the attended script, which prints booleans only. Register the clients
Write the attended script, which prints booleans only.
2026-10-10: script written at `scripts/openbao_negative_probe.py`, with tests in
`scripts/test_openbao_negative_probe.py`. It meets condition (b): one refused
boolean per case plus the refusal class (`claim_mismatch`, `expired`,
`not_service_principal`), and no token, secret, claim value or OpenBao error text.
A transport failure or an unexpected response is reported as `inconclusive`, never
as a refusal. A token that OpenBao accepts is revoked at once and fails the run.
Still waiting: railiance-platform's re-agreement on the two departures above, and
an attended registry session with net-kingdom. Register the clients
through the normal registration path, hand the run to railiance-platform, record
a value-free receipt in `docs/evidence/`, and deregister the clients afterwards.