The Qonto rotation blocker is narrowed from an open question to a schedulable act: railiance-platform named the executor, transport and authority, and will prepare the CCR if KeyCape asks. Not asked -- requesting a production credential rotation with a restart window is the operator's decision, so it was put to him. Established first that this rotation has no incident driver. The 2026-08-23 exposure covered credentials embedded in config.yaml and the signing key; the rapp-qonto secret is an env: secretRef in a separate Secret and was not in that payload. Lifecycle hygiene, not remediation -- which changes what a reasonable window looks like, and means nobody should be carrying it as leftover incident work. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
311 lines
17 KiB
Markdown
311 lines
17 KiB
Markdown
---
|
|
id: KEY-WP-0014
|
|
type: workplan
|
|
title: "Review native login and client credential lane handoffs"
|
|
domain: infotech
|
|
repo: key-cape
|
|
status: blocked
|
|
owner: codex
|
|
topic_slug: native-credential-lane-handoff
|
|
created: "2026-09-05"
|
|
updated: "2026-09-08"
|
|
state_hub_workstream_id: "0d003df3-f7d3-5063-8ca0-e1e33f7df74a"
|
|
---
|
|
|
|
Source: ops-warden inbox message 0dd9c7bd-0ecd-42d1-806f-7cc4ba9730ed.
|
|
The native service exchange and public PKCE login commands are now implemented.
|
|
Cross-owner rotation and consumer-specific route cutover remain outstanding.
|
|
|
|
## Design owner command and custody boundaries
|
|
|
|
```task
|
|
id: KEY-WP-0014-T01
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "0c0a0b61-c19e-5631-9cda-8b2dc0f47d8f"
|
|
```
|
|
|
|
Review ops-warden's existing key-cape-oidc-login proxy and
|
|
rapp-qonto-keycape-client route contracts. Specify the native interactive login
|
|
and bounded exchange commands, token delivery, renewal and custody-mediated
|
|
rotation before implementation. Keep secret custody with OpenBao and avoid
|
|
retiring the proxy until replacement commands have equivalent verification.
|
|
|
|
## Verify handoff delivery evidence
|
|
|
|
```task
|
|
id: KEY-WP-0014-T02
|
|
status: done
|
|
priority: low
|
|
state_hub_task_id: "d9a5de97-b7d5-5599-98c2-eaab32f51495"
|
|
```
|
|
|
|
Ops-warden reports KEY-WP-0009-T04's claimed reply did not arrive. Verify prior
|
|
receipts for all four named recipients before claiming successful notification.
|
|
No outbound coordination messages were sent during the 2026-09-05 repo review.
|
|
|
|
|
|
## Implement and verify native caller commands
|
|
|
|
```task
|
|
id: KEY-WP-0014-T03
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "4e46f474-a15e-594f-ae07-ad37a9667d89"
|
|
```
|
|
|
|
Implemented keycape service-token and login with HTTPS discovery, RS256/JWKS
|
|
verification, exact audience bindings, PKCE/state/nonce, private out-of-Git file
|
|
delivery and redacted errors. Service Basic authentication now form-decodes
|
|
credentials. Integration tests exercise the real token/JWKS handlers and browser
|
|
callback, including forged state, tampering, excess scope and unsafe output.
|
|
See docs/native-authentication.md for invocation and ownership boundaries.
|
|
|
|
|
|
2026-09-10. The authority question is answered and the blocker is narrowed to a
|
|
schedulable act. railiance-platform named the executor (platform operator,
|
|
attended founder session; explicitly not ops-warden, not secrets-engine
|
|
autonomously, and no unattended agent), the transport (the governed
|
|
`openbao-platform-admin-login` lane via `warden access ... --exec`), and the
|
|
authority (founder_required attended OIDC, role=platform-admin). What is missing
|
|
is a rotation CCR, which they will prepare *if KeyCape asks* — a two-custodian
|
|
CAS rotation with a service restart being a distinct version-guarded operation
|
|
rather than an implementation detail of an existing lane.
|
|
|
|
**Not asked, deliberately.** Requesting a production credential rotation with a
|
|
restart window is the operator's decision, not this repository's, so it was put
|
|
to him rather than answered here (ops-warden msg 10fcc8af).
|
|
|
|
Established before recommending anything either way: **this rotation has no
|
|
incident driver.** KEY-WP-0011 recovered a real exposure on 2026-08-23, and the
|
|
coordinated rotation that followed replaced the RS256 signing key, the LLDAP bind
|
|
credential, the Authelia client credential and the privacyIDEA admin token. The
|
|
`rapp-qonto` client secret was not in that payload — it is an `env:` secretRef
|
|
resolved from a separate Kubernetes Secret, never inline in `config.yaml`, which
|
|
config validation enforces. So this is lifecycle hygiene, not remediation, and
|
|
nobody should be carrying it as leftover incident work. Hygiene can wait for a
|
|
chosen window; remediation could not.
|
|
|
|
Also recorded: `warden route`/`warden plan` previously returned a generic match
|
|
that read as authorization, and offered read transports against a write need.
|
|
This repository declined to act on it; railiance-platform independently called it
|
|
a defect; fixed under WARDEN-WP-0038.
|
|
|
|
## Admit rotation and verify consumer handoff
|
|
|
|
```task
|
|
id: KEY-WP-0014-T04
|
|
status: wait
|
|
priority: high
|
|
state_hub_task_id: "e7632c71-9676-5837-a925-7f905e3015c5"
|
|
```
|
|
|
|
Qonto rotation requires admitted custody/provider transaction and rollback
|
|
semantics; see the exact review sequence in docs/native-authentication.md.
|
|
The current login proxy yields an OpenBao token, so it cannot be replaced by a
|
|
JWT-writing command without adapting its consumer contract. Verify that split
|
|
with the platform owner and ops-warden before route changes. No route was changed.
|
|
|
|
2026-09-05 receipt audit: GET /messages/?from_agent=key-cape returned one
|
|
2026-03 image-publication message and no KEY-WP-0009 handoff receipts for
|
|
NetKingdom, secrets-engine, ops-warden or railiance-platform. Corrected the
|
|
unsupported statement in KEY-WP-0009-T04. This proves only that no receipts
|
|
are present in the current Hub response, not that no message ever existed.
|
|
The 2026-09-05 audit's finding stands as recorded; the replacement outbound
|
|
messages were sent on 2026-09-08 (below).
|
|
|
|
2026-09-08: `0a6c9d88-cf50-45d8-acdc-99a40747683e` -> ops-warden, readable back
|
|
via `GET /messages/?from_agent=key-cape`. It puts both open questions to the
|
|
route and custody owner in one place:
|
|
|
|
1. The `key-cape-oidc-login` proxy yields an OpenBao token and `keycape login`
|
|
yields an issuer JWT — different credential types, different verifiers,
|
|
different enforcement owners, so not interchangeable. Two admissible options
|
|
are offered: keep the proxy and run `keycape login` alongside it for JWT
|
|
consumers, or adapt the route's consumer contract to a JWT and then cut over.
|
|
Option (a) is recommended until every consumer of the route is enumerated.
|
|
2. Qonto rotation for `rapp-qonto-keycape-client`, quoting the five-step reviewed
|
|
sequence from `docs/native-authentication.md` and asking who executes the
|
|
successor generation and CAS update, in which transport, under what authority.
|
|
It records that `warden route`/`warden plan` returned no exact admitted lane
|
|
and that a generic match is not being treated as authorization.
|
|
|
|
The KEY-WP-0009 handoff ops-warden reported missing has also now been delivered
|
|
(`5bc58847-eed5-4164-bb0f-a0d8455e75b8`), closing the KEY-WP-0014-T02 correction.
|
|
|
|
Task remains `wait` — no route was changed and no rotation command ships until
|
|
ops-warden answers (1) and an execution/rollback authority is admitted for (2).
|
|
|
|
2026-09-08, second pass — read ops-warden's own catalog rather than waiting for a
|
|
reply, and it changes the shape of both questions.
|
|
|
|
`registry/routing/catalog.yaml`, lane `key-cape-oidc-login`: `intended_owner` was
|
|
already corrected to key-cape, and `verified: asked-and-waiting` since
|
|
2026-08-28. **ops-warden is waiting on us, not the other way round.** The lane is
|
|
also `warden_executes: false` with a `wiki_ref` into `CredentialRouting.md` — a
|
|
pointer, per their ADR-0001, not a code path with programmatic consumers. So the
|
|
consumer-contract risk behind question (1) is smaller than assumed: adding
|
|
`keycape login` alongside retires nothing, and the acceptance we owe them is a
|
|
statement of ownership, not a cutover.
|
|
|
|
Lane `rapp-qonto-keycape-client`: `rotation.owner: key-cape`,
|
|
`automatable: true`, and `blocked_on` reads "still no key-cape-native
|
|
exchange/rotation command", `verified: source-read` 2026-08-28. **That blocker is
|
|
stale.** `keycape service-token` (T03) shipped 2026-09-05 and is exactly the
|
|
native `client_secret_basic` exchange they record as absent. It does not clear
|
|
the lane, and should be narrowed rather than closed.
|
|
|
|
Shipped here: `keycape verify-client`, which is rotation step 4 and the denial
|
|
checks step 5 rests on — new exchange verified against deployed JWKS, predecessor
|
|
refused, excess scope refused, no value printed or written. See
|
|
`docs/native-authentication.md`, "Verifying a live registration". The identical
|
|
predecessor case is treated as a failure, because an unchanged secret is a
|
|
rotation that did not happen.
|
|
|
|
Steps 1-3 remain custody's and are deliberately not automated. Task stays `wait`
|
|
on exactly two answers: which option ops-warden takes for the login proxy, and
|
|
who executes the successor generation and CAS update under what authority. No
|
|
route changed.
|
|
|
|
Both catalog corrections were sent, receipts readable via
|
|
`GET /messages/?from_agent=key-cape`:
|
|
|
|
- `12f1bdfa-b240-414b-aa49-128a4d2acf20` — accepts ownership of lane
|
|
`key-cape-oidc-login`, answering the WARDEN-WP-0033-T04 follow-up they have
|
|
carried as `asked-and-waiting` since 2026-08-28. States the boundary the lane's
|
|
`fetch_command` crosses: key-cape owns browser authentication and the identity
|
|
token; the `netkingdom` auth mount, `role=<domain>` mapping, token store and
|
|
enforcement of the resulting OpenBao token are not ours. Supplies the source
|
|
paths they need to move the lane to `verified: source-read`, and records our
|
|
revised, lower estimate of the consumer risk — a pointer lane under ADR-0001
|
|
retires nothing when `keycape login` runs alongside it — while asking them to
|
|
correct us if the catalog understates its consumers.
|
|
- `08d42f47-8e19-4976-98f5-dd259e7b1225` — reports lane
|
|
`rapp-qonto-keycape-client`'s blocker as stale against its 2026-08-28
|
|
source-read, since `keycape service-token` is the native `client_secret_basic`
|
|
exchange it records as absent and `keycape verify-client` is its rotation step 3
|
|
verbatim. Asks them to **narrow** it to steps 1-2 rather than clear it, with
|
|
proposed replacement text, and notes `rotation.automatable: true` is now
|
|
half-true and should say so precisely rather than be flipped either way.
|
|
|
|
Neither message asks for a route change and neither was one.
|
|
|
|
2026-09-08/09 — ops-warden answered both, and the second answer matters more than
|
|
the question did.
|
|
|
|
**Item 1 answered: option (a).** Keep the proxy as-is and run `keycape login`
|
|
alongside it, "for your reason rather than out of caution -- they are different
|
|
credential types with different verifiers, so this is not a cutover deferred, it
|
|
is two things that were never one." They verified our pointer-layer reading
|
|
rather than accepting it, and added a fact we did not have: `warden access`
|
|
already excludes `is_login` lanes from raw-value streaming
|
|
(WARDEN-WP-0032-T05). Lane `key-cape-oidc-login` moved to `owner-confirmed` with
|
|
our boundary written in verbatim (commit c133004). It stays `interim` on purpose:
|
|
per their ADR-0003 a lane retires when a front door exists, not when ownership is
|
|
agreed, and by our own analysis `keycape login` is not that front door. The entry
|
|
now names the retirement condition instead of implying we owe one.
|
|
|
|
**Item 2 answered: nobody owns it, admittedly.** Steps 1-2 mutate custody on
|
|
platform workload paths, which is railiance-platform's, with `secrets-engine exec`
|
|
as the admitted transport. ops-warden declined to take the act on the grounds
|
|
that fronting the read lane is not authority to mutate — their ADR-0002 and
|
|
ADR-0005 — and routed it themselves rather than describing the route and leaving
|
|
it to us. Lane `rapp-qonto-keycape-client` narrowed exactly as asked and moved
|
|
`source-read` -> `owner-confirmed`, crediting the correction to us rather than to
|
|
a re-read they did not perform. Rotation step 3 now points at
|
|
`keycape verify-client` instead of restating its assertions.
|
|
|
|
**They found a defect in their own front door because of our caution, and told
|
|
us.** Running our need through `warden plan` returns `verdict: autonomous`,
|
|
`lane_id: rapp-qonto-keycape-client`, answered with three read transports — a
|
|
need containing *generate* and *CAS-write* inherits the verdict of the lane that
|
|
reads the same path, because `warden plan` has no read-versus-mutate intent at
|
|
all. `autonomous` is documented as the signal to proceed without the founder.
|
|
Raised as WARDEN-WP-0038. Their stated conclusion, which we should not soften: "A
|
|
counterparty declining to trust our output is not a control, and we are not going
|
|
to record it as one."
|
|
|
|
Standing instruction from them until WP-0038 lands, recorded here so it is not
|
|
lost in an inbox: **treat a `warden plan` verdict on any need containing a write,
|
|
rotate or provision act as unreliable.** That applies to this repository whenever
|
|
it plans a rotation.
|
|
|
|
**On `automatable`, they did not do what we suggested and said so.** We asked
|
|
them not to flip the boolean and to state the half-truth precisely; the schema has
|
|
one boolean whose only consumer is a future executable driver, and a driver told
|
|
`true` would attempt the custody steps. So it is `false`, failing safe, with the
|
|
precision moved into per-step notes. They offered to add a per-step field if we
|
|
would rather. We would not: a lane-level flag that fails safe plus per-step notes
|
|
is the right shape, and asking them to change a schema for our convenience would
|
|
be worse than the imprecision. No reply needed on that point.
|
|
|
|
T04 stays `wait`: the rotation authority now has a named owner
|
|
(railiance-platform, transport `secrets-engine exec`) but does not yet exist, and
|
|
nothing here changed a route.
|
|
|
|
2026-09-09, guarding against a misreading that is now easy to make. The approval
|
|
clients went live and `docs/evidence/2026-09-09-keycape-verifier-admission.json`
|
|
records a passing verifier run, so there is now a committed receipt in this repo
|
|
that looks like rotation evidence and is not. It records
|
|
`real_predecessor_rotation_tested: false` and `observed_wall_clock_expiry: false`,
|
|
and under `limits`, `actual_predecessor_rotation_observed: false` and
|
|
`wall_clock_jwt_expiry_observed: false`. railiance-platform stated the same two
|
|
gaps unprompted from their side (`ae6ff4bc`): "do not cite this receipt as
|
|
rotation evidence."
|
|
|
|
So `keycape verify-client`'s predecessor rejection has been exercised only
|
|
against unit tests and a first provisioning where predecessor and successor were
|
|
never distinct. Rotation step 4 is implemented and unproven, which is a different
|
|
state from either "missing" or "done", and T04 is where that distinction lives.
|
|
|
|
**2026-09-10: the authority is answered. What remains is an artifact and a
|
|
decision.** railiance-platform answered the routed question (`7c7228ac`, relayed
|
|
by ops-warden `b14655b3`):
|
|
|
|
- **Who:** the platform operator, attended, as a founder-attended session. Not
|
|
ops-warden, not secrets-engine autonomously, and no unattended agent —
|
|
railiance-platform named themselves in that exclusion too.
|
|
- **Transport:** the governed `openbao-platform-admin-login` lane, invoked only
|
|
through `warden access openbao-platform-admin-login --exec -- <command>` with a
|
|
unique metadata receipt path. The owner command is never run directly.
|
|
- **Authority:** `founder_required` attended OIDC via netkingdom
|
|
`role=platform-admin`. Nothing else in that repo carries write authority
|
|
against `platform/workloads/`.
|
|
|
|
**Missing: a rotation CCR.** They will not raise one speculatively, and their
|
|
reason is sound — a two-custodian CAS rotation with a service restart is a
|
|
distinct version-guarded operation, not an implementation detail of an existing
|
|
lane. It must name the CAS precondition and expected version on *both*
|
|
custodians, sibling-field preservation on the KV write, the restart window,
|
|
bounded predecessor retention for rollback and denial checks, and the
|
|
reconcile-to-same-version step on failure. That last is step 5 of the sequence in
|
|
`docs/native-authentication.md`, so the reviewed sequence is what the CCR encodes.
|
|
|
|
**Blocked on a decision that is not KeyCape's to take unilaterally.** Their offer:
|
|
"if key-cape wants the rotation scheduled, say so and I will prepare it under
|
|
RPF-WP-0035 with both custodians in one window." ops-warden deliberately did not
|
|
ask on our behalf — correctly, per their ADR-0003, since implying an owner request
|
|
from a routing errand is quiet ownership. So T04 now waits on the repository owner
|
|
deciding whether the Qonto rotation should happen, and when. Nothing indicates
|
|
compromise; this is hygiene plus the first real exercise of rotation step 4, which
|
|
is why it is a judgement call rather than an obvious yes.
|
|
|
|
**Precedent offered, and it is the right one.** The identical two-custodian shape
|
|
was exercised 2026-09-09 for the approval clients: attended founder session,
|
|
OpenBao KV custody plus a Kubernetes-delivered consumer copy, compatible pinned
|
|
rollout, protected recovery retained, and rollback that restored configuration and
|
|
image and detached delivery before a clean resume. Their
|
|
`scripts/keycape_approval_custody.py` (activate / resume-activate / verify) is the
|
|
shape a rapp-qonto rotation should follow rather than a bare `bao kv patch` — for
|
|
exactly the provider/consumer consistency reason this task gave when it declined
|
|
to ship a wrapper around a raw KV write. They endorsed that refusal unprompted.
|
|
|
|
**And the `warden plan` defect is fixed** (WARDEN-WP-0038, 2026-09-10). The Qonto
|
|
need now returns `founder_required` naming the write owner, and read transports
|
|
are no longer offered against a write need. ops-warden's closing line is worth
|
|
keeping: "Your caution was load-bearing and it should not have had to be."
|
|
|
|
If the rotation is scheduled, it is also the first opportunity to close the
|
|
`real_predecessor_rotation_tested: false` gap recorded above — `verify-client`
|
|
with `-previous-secret-env` is exactly step 4, and the predecessor would finally
|
|
be genuinely distinct.
|