Files
ops-warden/workplans/WARDEN-WP-0014-operator-access-assist.md
tegwick 1c3d1b4d52 feat(WARDEN-WP-0014): T4 — key-cape login orchestration lane
Adds a lane: secret|login field to RouteEntry. The login lane is an
interactive auth bootstrap: it skips the caller-auth precheck (no token
yet — that's the point) and the secret-read gate (it establishes the
identity the gate needs), runs the owner's login command interactively
as the caller via inherited stdio, and rejects --exec. The token stays
in the caller's own store; warden never captures it (G2 holds). Audited
as action: login. key-cape-oidc-login populated as the reference login
entry. Advisory proxy hint updated now that T3 has shipped.

172 passed, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:31:55 +02:00

213 lines
9.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: WARDEN-WP-0014
type: workplan
title: "Operator Access Assist — warden access front door"
domain: infotech
repo: ops-warden
status: active
owner: codex
topic_slug: custodian
planning_priority: high
planning_order: 14
created: "2026-06-27"
updated: "2026-06-27"
state_hub_workstream_id: "3c30b2ed-6ede-4b95-a438-fde6da6f6633"
---
# WARDEN-WP-0014 — Operator Access Assist (`warden access`)
**Scope:** Make ops-warden the consistent operator-facing front door for **every**
NetKingdom security/access need — not just the SSH lane. Add a `warden access`
command surface that (a) advises: emits the auth method, path, policy context, and
exact command skeleton for any credential need, and (b) **proxies**: transparently
fetches the value from the owning subsystem *as the caller* and streams it to the
operator's destination, **without ops-warden ever persisting, caching, or logging
the secret, and without ops-warden holding any standing privileged credential.**
Centralize **knowledge and policy** in ops-warden; leave **custody and execution
detail** in the owning subsystems (OpenBao, key-cape, flex-auth). ops-warden becomes
a transparent, policy-gated, audited conduit — `vault exec` / `op run` shaped — never
a standing secret broker.
**Charter note:** This extends the WP-0010 routing charter from a *pointer layer*
("who owns it") to an *assist layer* ("here is exactly how to get it, gated and
audited"). It does **not** move custody into ops-warden. See the three non-negotiable
guardrails below — they are the line between a transparent conduit (sanctioned) and a
standing broker/honeypot (forbidden).
**Out of scope:**
- ops-warden holding a long-lived OpenBao/secret-read token of its own.
- Persisting, caching, or logging any secret **value** anywhere (disk, log, hub, git).
- Creating OpenBao paths or policies (coordinate with railiance-platform / flex-auth).
- Restating an owner's procedure as prose in the catalog (reference canon, don't copy).
- Identity/MFA implementation (key-cape owns it; we orchestrate its CLI only).
**Depends on:** WP-0010 (charter + catalog schema), WP-0011 (`warden route` CLI),
WP-0007/0009 (flex-auth policy gate — reused as the fetch-path gate).
**Status:** `proposed` — awaiting Bernd's review before implementation.
---
## Three non-negotiable guardrails (acceptance-blocking)
These are the design invariants that keep proxy mode safe. Any task that violates one
is rejected regardless of convenience.
1. **Operator identity, never warden's.** `--fetch`/`--exec` authenticate as the
*caller* (their OIDC/OpenBao token, the agent's own auth). ops-warden carries **no**
standing secret-read credential. If the caller has no valid auth, the command fails
with a routing pointer to the auth step — it does not fall back to a warden token.
2. **Transit only — no persistence, no logging of values.** The secret flows
subsystem → caller destination (env of a child process, or stdout) via a streamed
`exec`. ops-warden must not buffer it to disk, cache it, echo it to logs, or write
it to the State Hub. Audit records **metadata only** (who, need id, owner path, time,
policy decision id) — never the value.
3. **Policy gate on the fetch path.** `--fetch`/`--exec` run the flex-auth check
**before** proxying (reusing the WP-0007 gate). When `policy.enabled: false`, fetch
is **advisory-only** by default and requires an explicit `--no-policy` acknowledgement
to proxy ungated — surfaced loudly in output and audit.
---
## Phasing decision (default — adjust in review)
OpenBao lane **first** (covers the immediate npm/API/DB need), key-cape/login lane in
a later task within the same WP. Rationale: OpenBao KV is the highest-frequency operator
need and the one this conversation surfaced; login flows are a thinner orchestration of
an interactive tool and lower risk to defer.
---
## Tasks
### T1 — Catalog schema: structured handoff fields
```task
id: WARDEN-WP-0014-T01
status: done
priority: high
state_hub_task_id: "abb0e722-6524-4224-8638-6ee1573ed3e0"
```
- [x] Extend `registry/routing/catalog.yaml` entry schema with optional structured
handoff fields for non-SSH lanes: `auth_method`, `path_template`,
`fetch_command`, `exec_capable` (bool), `policy_ref`. (`RouteEntry` +
`_parse_entry`; `has_handoff` helper.)
- [x] Fields are **structured pointers/templates**, not prose restatements — each
sits alongside the owner's `canon_ref` for the authoritative procedure (no drift).
- [x] Populate for `openbao-api-key` (covers the coulomb_social npm shape: keyword
`npm_auth_token` added) as the reference example; `draft` entries untouched.
- [x] Validation: `_assert_no_secret_material` rejects known token prefixes and
high-entropy runs in any handoff field; `exec_capable` requires `fetch_command`.
Tests in `tests/test_routing.py` (handoff parse, real-catalog, secret-leak
matrix, placeholder-accepted).
### T2 — `warden access` advisory surface
```task
id: WARDEN-WP-0014-T02
status: done
priority: high
state_hub_task_id: "c1497263-7124-459f-b63a-d0c0c7005c86"
```
- [x] `warden access <need> [--domain X] [--json]` — resolves via the same matcher as
`warden route find` and renders the **structured handoff**: owner, auth method,
path template, command skeleton, policy ref + gate status, proxy hint, and the
`<…>` owner-confirmed-name note. (`warden/access.py` pure module + `access`
command in `cli.py`.)
- [x] Advisory is the **default** behavior (no value fetched); SSH lane points at
`warden sign`; routed lanes end with "warden advises, the owner vends".
- [x] `--json` output for agentic operators — stable, secret-free shape
(`handoff` block + `next_action`); `--domain` substitutes `<domain>` only.
- [x] Tests: `tests/test_access.py` (expansion, gate status, advisory/SSH/JSON/no-match).
### T3 — OpenBao proxy lane (`--fetch` / `--exec`)
```task
id: WARDEN-WP-0014-T03
status: done
priority: high
state_hub_task_id: "6d3eb0e4-309c-4065-893e-6c4053fb0db2"
```
- [x] `warden access <need> --fetch` — policy-gate (G3) → run the owning tool
(`bao kv get ...`) **as the caller** with **inherited stdout** → value streams to
stdout and never enters warden's memory (`proxy_fetch`). No buffering, no log.
- [x] `warden access <need> --exec -- <cmd>` — runs the child with the secret injected
into *its* env only (`proxy_exec`); value never lands in the caller's shell env;
`--field` names the env var (e.g. `NPM_AUTH_TOKEN`).
- [x] Guardrails G1G3 in code (`warden/proxy.py`, `_access_proxy` in `cli.py`):
G1 caller token only (no warden credential; `caller_auth_present`); G2 transit-only
(inherit-stdout fetch; no disk/log write); G3 `check_fetch_policy` before any exec,
`--no-policy` required to proxy ungated. `tests/test_proxy.py` asserts all three,
plus `resolve_fetch_command` refuses unresolved `<…>` placeholders. Live smoke
against a fake `bao` confirmed gate-refusal, stream, exec-inject, and a
secret-free audit log.
- [x] Metadata-only audit per call (`write_audit``state_dir/access-audit.log`).
### T4 — key-cape / login orchestration lane
```task
id: WARDEN-WP-0014-T04
status: done
priority: medium
state_hub_task_id: "481997e4-193d-4724-84a6-61cbc2940153"
```
- [x] Extend `warden access` to orchestrate the key-cape/Keycloak OIDC login flow
under the same advisory/proxy split. New `lane: secret|login` field on
`RouteEntry`; `key-cape-oidc-login` populated as a `login` lane entry.
- [x] Login lane semantics: no caller-auth precheck (you have no token yet) and no
secret-read gate (it bootstraps the identity the gate needs); runs interactively
as the caller via inherited stdio; `--exec` rejected. Token stays in the caller's
own store — warden never captures it (G2 holds). Audited as `action: login`.
- [x] Tests in `tests/test_proxy.py` (runs without token/ack, rejects --exec, real
catalog lane, invalid-lane rejection). Live fake-`bao login` smoke confirmed.
### T5 — Docs, security model, and INTENT/SCOPE alignment
```task
id: WARDEN-WP-0014-T05
status: todo
priority: medium
state_hub_task_id: "a5eb616e-4edf-42db-a4fb-bf296cdb92bc"
```
- [ ] `wiki/OperatorAccessAssist.md` — the `warden access` contract, the conduit-vs-broker
boundary, and the three guardrails as a security model statement.
- [ ] Update `wiki/AccessRouting.md` (issue/route/**assist** roles), `CredentialRouting.md`,
and the `credential-routing.md` agent rule (new anti-pattern: "warden as standing
broker" is forbidden; transparent `--fetch` is sanctioned).
- [ ] SCOPE/INTENT: record the charter extension from pointer-layer to assist-layer and
bump the maturity vector (A4 → A5 candidate on Availability).
- [ ] `history/2026-06-27-operator-access-assist-charter.md` — decision record for the
proxy-mode choice and its guardrails.
---
## Acceptance
- `warden access <need>` advises for any catalog need; `--fetch`/`--exec` proxy the
OpenBao lane end to end against a real KV path.
- All three guardrails hold under test: **no** secret value touches disk/log/hub/git;
ops-warden holds **no** standing secret-read credential; the policy gate runs **before**
every fetch.
- Catalog carries structured handoff fields that reference (never restate) owner canon.
- Docs state the conduit-vs-broker boundary explicitly; the agent rule forbids the
broker pattern.
- No secret material anywhere in code, catalog, docs, logs, or tests.
---
## See also
- `WARDEN-WP-0010` (routing charter), `WARDEN-WP-0011` (`warden route` CLI)
- `WARDEN-WP-0007` / `WARDEN-WP-0009` (flex-auth policy gate — reused as fetch gate)
- `wiki/AccessRouting.md`, `wiki/CredentialRouting.md`, `wiki/PolicyGatedSigning.md`
- `.claude/rules/credential-routing.md`
- `history/2026-06-24-intent-scope-gap-analysis.md`