Files
ops-warden/workplans/WARDEN-WP-0014-operator-access-assist.md
tegwick 2c513864bc feat(WARDEN-WP-0014): T2 — warden access advisory front door
Adds `warden access <need> [--domain X] [--json]`: resolves a credential
need against the routing catalog and renders the structured handoff
(owner, auth method, path template, command skeleton, policy gate
status, proxy hint). SSH lane points at `warden sign`; routed lanes end
"warden advises, the owner vends". New pure warden/access.py module
(expand_handoff, policy_gate_status) reused by the T3 proxy lane. JSON
output is stable and secret-free. tests/test_access.py added.

157 passed, lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:13:51 +02:00

201 lines
8.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: todo
priority: high
state_hub_task_id: "6d3eb0e4-309c-4065-893e-6c4053fb0db2"
```
- [ ] `warden access <need> --fetch` — policy-gate (G3) → `exec` the owning tool
(`bao kv get ...`) **as the caller** → stream value to stdout. No buffering, no log.
- [ ] `warden access <need> --exec -- <cmd>` — run a child command with the secret
injected into *its* env only (à la `op run`); value never lands in the caller's
shell history or persistent env.
- [ ] Enforce guardrails G1G3 in code; unit + integration tests assert: no value on
disk, no value in logs, no standing warden credential, gate runs before fetch.
- [ ] Audit event (metadata only) written per fetch — reuse the signatures-log pattern.
### T4 — key-cape / login orchestration lane
```task
id: WARDEN-WP-0014-T04
status: todo
priority: medium
state_hub_task_id: "481997e4-193d-4724-84a6-61cbc2940153"
```
- [ ] Extend `warden access` to orchestrate the key-cape/Keycloak OIDC login flow
(interactive tool hand-off) under the same advisory/proxy split.
- [ ] Login lane respects G1G3; no token caching by warden.
### 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`