From 5bbb791f213bb020f4971c92b773953beb4eddc3 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 27 Jun 2026 17:35:57 +0200 Subject: [PATCH] =?UTF-8?q?docs(WARDEN-WP-0014):=20T5=20=E2=80=94=20assist?= =?UTF-8?q?-layer=20docs,=20security=20model,=20INTENT/SCOPE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - wiki/OperatorAccessAssist.md: warden access contract, conduit-vs-broker boundary, the three guardrails + catalog secret guard, lane semantics. - AccessRouting.md: issue/route/assist roles; reconciled the anti-pattern table so the transparent conduit no longer contradicts it. - credential-routing.md rule: added warden access + "standing broker forbidden, transparent --fetch sanctioned" anti-pattern. - INTENT.md: pointer→assist charter extension. SCOPE.md: implemented list + Getting Oriented + maturity A4→A5 (Availability). - history decision record for the proxy-mode choice and guardrails. WP-0014 finished (T1–T5). 172 passed, lint clean. Co-Authored-By: Claude Opus 4.8 --- .claude/rules/credential-routing.md | 14 ++- INTENT.md | 19 +-- SCOPE.md | 10 +- ...26-06-27-operator-access-assist-charter.md | 68 +++++++++++ wiki/AccessRouting.md | 47 +++++--- wiki/OperatorAccessAssist.md | 109 ++++++++++++++++++ .../WARDEN-WP-0014-operator-access-assist.md | 23 ++-- 7 files changed, 253 insertions(+), 37 deletions(-) create mode 100644 history/2026-06-27-operator-access-assist-charter.md create mode 100644 wiki/OperatorAccessAssist.md diff --git a/.claude/rules/credential-routing.md b/.claude/rules/credential-routing.md index a2164b8..4ef5546 100644 --- a/.claude/rules/credential-routing.md +++ b/.claude/rules/credential-routing.md @@ -11,10 +11,16 @@ other credential need belongs to another subsystem. **Do not** message ### Lookup (do this first) ```bash -warden route find "" --json -warden route show --json +warden route find "" --json # who owns it (pointer) +warden access "" --json # how to get it (handoff) ``` +`warden access` is the operator front door (WARDEN-WP-0014): it renders the owner, +auth method, path template, command skeleton, and policy-gate status for any need. +For `exec_capable` lanes it can **proxy the fetch as you** (`--fetch`/`--exec`) — it +runs the owner's tool with **your** identity and streams the value to you; ops-warden +never holds, caches, or logs the value. See `wiki/OperatorAccessAssist.md`. + Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`). | Agent runtime | How to orient | @@ -39,6 +45,10 @@ Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run wa - `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc. - Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist - Pasting secrets into Git, State Hub, workplans, logs, or chat +- Treating `warden access --fetch` as a *secret store*. It is a transparent conduit + using **your** identity — it holds nothing. ops-warden as a **standing broker** + (its own secret-read token, a cache of fetched values) is forbidden; runtime secret + custody stays in OpenBao, authorization in flex-auth. ### Other capabilities (reuse-surface) diff --git a/INTENT.md b/INTENT.md index 506063c..c0d7439 100644 --- a/INTENT.md +++ b/INTENT.md @@ -46,10 +46,14 @@ owns one lane and points at the rest: 1. **Know** the NetKingdom security model — identity, authorization, secrets, SSH access, tunnels, bootstrap custody, and tenant/platform boundaries. -2. **Route** workers to the correct subsystem for each credential type instead - of becoming a universal secret vending machine — through the wiki and a - machine-readable routing catalog that *points at* the owner's docs rather than - restating them. +2. **Route, and assist.** Point workers to the correct subsystem for each credential + type instead of becoming a universal secret vending machine — through the wiki and + a machine-readable routing catalog that *points at* the owner's docs rather than + restating them. Beyond pointing, **assist**: the `warden access` front door renders + the exact auth method, path, and command for any need and — for `exec_capable` + lanes — proxies the fetch *as the caller* (a transparent, policy-gated, audited + conduit that holds, caches, and logs **nothing**). This is the assist layer, not a + broker: custody stays in OpenBao, authorization in flex-auth. 3. **Align** runbooks, wiki, inventory patterns, and scorecard checks with NetKingdom canon as the platform evolves (OpenBao-first, flex-auth policy, key-cape IAM Profile, railiance deployment layers). @@ -169,9 +173,10 @@ ops-warden (issue SSH; route the rest) +-- Tunnel only? --------------------> ops-bridge + cert_command ``` -Today the steward role is primarily documentation, runbooks, and the implemented -SSH CLI. The machine-readable routing catalog and `warden route` lookup, plus -policy-gated issuance, are intentional follow-ups, not current promises. +The steward role spans documentation, runbooks, the SSH CLI, the machine-readable +routing catalog with `warden route` lookup, policy-gated issuance, and — since +WARDEN-WP-0014 — the `warden access` assist layer that advises and (for `exec_capable` +lanes) proxies non-SSH fetches as the caller without holding the value. --- diff --git a/SCOPE.md b/SCOPE.md index e1ca546..26b3923 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -68,12 +68,12 @@ Gap analysis: `history/2026-06-24-intent-scope-gap-analysis.md` (current); | NetKingdom evolution reflected in docs | Met | | Non-SSH secrets stay out of ops-warden | Met | -**Maturity vector:** `D5 / A4 / C4 / R3` (Discovery / Availability / Completeness / Reliability) +**Maturity vector:** `D5 / A5 / C4 / R3` (Discovery / Availability / Completeness / Reliability) | Dimension | Level | Meaning today | | --- | --- | --- | | D5 | Discovery | Routing wiki + security map + pointer catalog + NK canon cross-links | -| A4 | Availability | CLI + `warden route` + opt-in policy gate + agent `--json` lookup | +| A5 | Availability | CLI + `warden route` + `warden access` advisory & proxy front door + opt-in policy gate + agent `--json` | | C4 | Completeness | SSH lane prod-verified; policy gate + registry smoke shipped; prod flip waits flex-auth deploy | | R3 | Reliability | Live OpenBao sign evidence on Railiance | @@ -108,6 +108,9 @@ for the rest. `registry/flex-auth/production_registry_snapshot.json`) - Policy gate smoke runner (`scripts/policy_gate_production_smoke.sh`) - `warden route` lookup CLI (`list`/`show`/`find`, `--json`) over the pointer catalog +- `warden access` operator front door (WP-0014): advisory handoff for any need, and a + transparent, policy-gated, audited **proxy** (`--fetch`/`--exec`) for `exec_capable` + lanes (OpenBao secret reads, key-cape login) — caller identity, value never held - `warden issue` and `ops-ssh-wrapper` (local backend; vault uses sign-only) - Runbooks for OpenBao config and Inter-Hub bootstrap SSH envelope @@ -262,7 +265,8 @@ keywords: [ssh, certificate, ca, credential, warden, ops-warden, pki, openbao, v | --- | --- | | `INTENT.md` | Why ops-warden exists and where it is going | | `SCOPE.md` | What is implemented today (this file) | -| `wiki/AccessRouting.md` | What ops-warden issues vs routes (role and boundary) | +| `wiki/AccessRouting.md` | What ops-warden issues vs routes vs assists (role and boundary) | +| `wiki/OperatorAccessAssist.md` | `warden access` front door + conduit-vs-broker boundary + guardrails | | `wiki/CredentialRouting.md` | Which subsystem for each credential need | | `registry/routing/catalog.yaml` | Machine-readable routing pointer catalog | | `wiki/NetKingdomSecurityMap.md` | Platform security component map | diff --git a/history/2026-06-27-operator-access-assist-charter.md b/history/2026-06-27-operator-access-assist-charter.md new file mode 100644 index 0000000..26717a4 --- /dev/null +++ b/history/2026-06-27-operator-access-assist-charter.md @@ -0,0 +1,68 @@ +# Operator Access Assist — charter decision record + +Date: 2026-06-27 +Workplan: WARDEN-WP-0014 +Status: shipped (T1–T5) + +## Context + +A routine question — "do we have an NPM_AUTH_TOKEN for coulomb in OpenBao, and how do +I ask ops-warden for it?" — exposed a gap. ops-warden's honest answer was *"not my +lane; go read a wiki and talk to railiance-platform."* Correct per the model, but a +**pointer, not assistance**. The `warden route` catalog named the owner and stopped. + +Bernd's framing: ops-warden should be the *consistent operator front door for all +NetKingdom security operations* — centralize the **knowledge and policy**, while the +specialized subsystems keep the **detail and custody**. Make security consistent and +efficient for human and agentic operators without ops-warden becoming a secret store. + +## Decision + +Extend the routing charter from a **pointer layer** to an **assist layer**: a +`warden access` front door that (a) advises — renders the exact auth method, path, +command skeleton, and policy-gate status for any need — and (b) for `exec_capable` +lanes, **proxies** the fetch *as the caller*. + +Proxy mode was chosen explicitly (over advisory-only) for operational convenience, +**on the condition** that it is built as a transparent conduit, not a standing broker. + +## The boundary that keeps it sound + +`net-kingdom/docs/responsibility-map.md` already constrains ops-warden: it *"must not +become a universal secret broker — runtime secrets remain OpenBao; authorization +remains flex-auth."* The assist layer presses on this line; three guardrails hold it: + +- **G1 — caller identity, never warden's.** Proxy runs the owner's tool with the + caller's own environment; ops-warden injects no token and holds no standing + secret-read credential. +- **G2 — transit only.** `--fetch` inherits stdout (never piped), so the value never + enters warden's memory or any log; `--exec` injects into a child env only; audit is + metadata only. The catalog `_assert_no_secret_material` guard keeps values out of the + git-tracked catalog. +- **G3 — policy gate before fetch.** flex-auth `check_fetch_policy` runs before any + secret-lane fetch; with `policy.enabled: false` the proxy refuses unless `--no-policy` + acknowledges proxying ungated. + +A `lane: secret|login` distinction lets interactive auth bootstrap (key-cape OIDC) +skip the caller-auth precheck and secret-read gate it cannot satisfy. + +## What this is NOT + +- Not secret custody — OpenBao still holds the values. +- Not authorization — flex-auth still decides; ops-warden only gates its own proxy. +- Not identity — key-cape still establishes it; the login lane just runs the flow as + the caller. + +## Follow-on + +This conversation also surfaced the **Secret Lifecycle Tiering** idea (dev→test→prod +posture ladder, the "fake bao" contract-double pattern generalized). Captured as +**WARDEN-WP-0015** (proposed): policy authored to net-kingdom canon, ops-warden as +conformance steward (author + checks, not enforcement). + +## References + +- `wiki/OperatorAccessAssist.md` — the contract + guardrails +- `src/warden/access.py`, `src/warden/proxy.py`, `_access_proxy` in `cli.py` +- `tests/test_access.py`, `tests/test_proxy.py` +- `workplans/WARDEN-WP-0014-operator-access-assist.md` diff --git a/wiki/AccessRouting.md b/wiki/AccessRouting.md index 9f72594..26ada9c 100644 --- a/wiki/AccessRouting.md +++ b/wiki/AccessRouting.md @@ -2,16 +2,21 @@ Date: 2026-06-18 -ops-warden **issues short-lived SSH certificates** and **routes every other -credential need to the subsystem that owns it.** This page states that role -plainly so it cannot be misread as a desk that wraps the platform. +ops-warden **issues short-lived SSH certificates**, **routes every other credential +need to the subsystem that owns it**, and **assists** with obtaining it through the +`warden access` front door. This page states that role plainly so it cannot be +misread as a desk that wraps the platform. - **What ops-warden executes:** the SSH certificate lane only (`warden sign`, `cert_command`, `ops-ssh-wrapper`). - **What ops-warden answers:** *where* a credential need belongs and *who owns it* — pointing at the owner's docs, never restating their procedure. -- **What ops-warden never does:** vend API keys, log you in, decide policy, open - tunnels, or deploy hosts. +- **What ops-warden assists with:** `warden access` renders the exact auth/path/command + for any need and, for `exec_capable` lanes, **proxies the fetch as the caller** — a + transparent, policy-gated, audited conduit that holds, caches, and logs nothing. +- **What ops-warden never does:** *own* a secret store, *establish* identity, *decide* + policy, open tunnels, or deploy hosts. The assist conduit uses **your** identity and + owns none of these. See `OperatorAccessAssist.md`. For the worker-facing decision tree see `CredentialRouting.md`; for component literacy see `NetKingdomSecurityMap.md`. This page is the steward's statement of @@ -36,24 +41,38 @@ Only the first row is something ops-warden **executes**. Every other row is a **pointer**: ops-warden names the owner and the doc, and the worker acts on the owning system directly. +**Assist layer (`warden access`).** For routed rows, ops-warden goes beyond the +pointer: it renders the exact auth method, path template, and command, and — where the +catalog marks a lane `exec_capable` (today: OpenBao secret reads, key-cape login) — +**proxies the call as the caller**. This does not change ownership: the secret stays in +OpenBao, the decision stays in flex-auth, the identity stays in key-cape. ops-warden is +a transparent conduit using the caller's identity, never a custodian of the value. The +boundary that keeps this sound is in `OperatorAccessAssist.md#the-conduit-vs-broker-boundary`. + --- ## Anti-patterns (not coming to ops-warden) -These commands do **not** exist and will **not** be added — they belong to other -subsystems. If you find yourself wanting one, you are on the wrong desk: +ops-warden does not **own** custody, identity, authorization, or transport — those +belong to other subsystems. The assist layer (`warden access`) may *proxy* a call as +the caller, but it never becomes the owner. Don't reach for a command that implies +ownership: | Tempting command | Why it's wrong | Right path | | --- | --- | --- | -| `warden secret` / `warden bao` | ops-warden does not store or vend secrets | OpenBao | -| `warden login` | ops-warden does not establish identity | key-cape / Keycloak | -| `warden policy` | ops-warden does not decide authorization | flex-auth | +| `warden secret` / `warden bao` (as a store/vend) | ops-warden owns no secret store and vends nothing | OpenBao; to obtain *as yourself*, `warden access --fetch` | +| `warden login` (as an identity owner) | ops-warden does not establish identity | key-cape / Keycloak; to run the login *as yourself*, `warden access --fetch` (login lane) | +| `warden policy` (as a decision) | ops-warden does not decide authorization | flex-auth makes the call; ops-warden only gates its own proxy on it | | `warden tunnel` | ops-warden does not manage transport | ops-bridge | -ops-warden authors step-by-step procedure for exactly one lane — SSH issuance — -because it owns it. For everything else it carries a **pointer**, not a fork of -the owner's runbook. See the no-double-source rule in -`workplans/WARDEN-WP-0010-access-routing-charter.md`. +The distinction: a **standing broker** (warden's own secret-read token, a cache of +values) is forbidden; a **transparent conduit** (`warden access --fetch`, caller's +identity, nothing retained) is sanctioned. ops-warden authors step-by-step procedure +for exactly one lane — SSH issuance — because it owns it. For everything else it +carries a **pointer** (and, for `exec_capable` lanes, a conduit), not a fork of the +owner's runbook. See the no-double-source rule in +`workplans/WARDEN-WP-0010-access-routing-charter.md` and the conduit-vs-broker +boundary in `OperatorAccessAssist.md`. --- diff --git a/wiki/OperatorAccessAssist.md b/wiki/OperatorAccessAssist.md new file mode 100644 index 0000000..baaa915 --- /dev/null +++ b/wiki/OperatorAccessAssist.md @@ -0,0 +1,109 @@ +# Operator Access Assist — `warden access` + +> The operator front door for **every** NetKingdom credential need. ops-warden +> issues the SSH lane directly and **assists** with the rest: it tells you exactly +> how to obtain a credential and — for `exec_capable` lanes — proxies the fetch +> *as you*, without ever holding, persisting, or logging the value. + +Shipped in WARDEN-WP-0014. This extends the 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 secret custody into ops-warden. + +--- + +## Three roles, one front door + +| Role | Lane | Command | What ops-warden does | +| --- | --- | --- | --- | +| **Issue** | SSH cert (`adm`/`agt`/`atm`) | `warden access ssh…` → `warden sign` | Executes — signs the cert | +| **Assist (advise)** | any credential need | `warden access ` | Renders the owner, auth method, path, command skeleton, policy gate | +| **Assist (proxy)** | `exec_capable` lanes (OpenBao, login) | `warden access --fetch / --exec` | Runs the owner's tool **as the caller**; value never touches warden | + +```console +# advisory — works with no config; never fetches a value +$ warden access "npm token" --domain coulomb_social +# proxy a secret read as the caller (gated + audited); value streams to stdout +$ warden access "npm token" --domain coulomb_social --field NPM_AUTH_TOKEN --path

--fetch +# run a child command with the secret in its env only (à la `op run`) +$ warden access "npm token" --field NPM_AUTH_TOKEN --exec -- npm publish +# interactive login (login lane): no token required, no secret-read gate +$ warden access "login oidc" --domain coulomb_social --fetch +``` + +`--json` gives a stable, secret-free shape for agentic operators. + +--- + +## The conduit-vs-broker boundary (the security model) + +There are two very different things "secret transits warden" can mean. One is +sanctioned; the other is forbidden by the NetKingdom responsibility model +(`net-kingdom/docs/responsibility-map.md`: ops-warden *"must not become a universal +secret broker — runtime secrets remain OpenBao; authorization remains flex-auth"*). + +**Sanctioned — transparent conduit.** ops-warden runs the owner's tool with the +**caller's own identity**, streams the value straight to the caller, and retains +nothing. It holds no standing credential and stores no value. This is the `vault exec` +/ `op run` shape. + +**Forbidden — standing broker.** ops-warden holding its own long-lived secret-read +token, caching fetched values, becoming a service every operator's secrets flow +through and rest in. That recreates the single high-value target the model exists to +prevent, and duplicates OpenBao. + +`warden access` is built as the first and forbids the second by construction. + +--- + +## The three guardrails (enforced in code) + +| | Guardrail | How it is enforced | +| --- | --- | --- | +| **G1** | **Caller identity, never warden's** | The proxy runs the owner's tool with the caller's own environment; ops-warden injects no token of its own. Secret lanes require the caller to already hold a credential (`caller_auth_present`), else they fail with the auth pointer. | +| **G2** | **Transit only — no persistence/logging of values** | `--fetch` runs with **inherited stdout** (never a pipe), so the value streams to the caller and never enters warden's memory. `--exec` reads the value solely to place it in a child process's env (the accepted `--exec` tradeoff) — never to disk or log. The audit record is **metadata only**. | +| **G3** | **Policy gate before fetch** | `check_fetch_policy` (flex-auth) runs before any secret-lane fetch. With `policy.enabled: false` the proxy refuses unless `--no-policy` is given to acknowledge proxying ungated. | + +The catalog side enforces a fourth, upstream guard: **handoff fields are templates, +never values.** `_assert_no_secret_material` rejects any known token prefix or +high-entropy run in a catalog handoff field, so a secret can never leak into the +git-tracked, agent-visible catalog. + +--- + +## Lanes + +Each catalog entry declares a `lane`: + +- **`secret`** (default) — read a value. Requires caller auth (G1) and runs the + flex-auth secret-read gate (G3). Value transits via inherit-stdout (`--fetch`) or + child env (`--exec`). +- **`login`** — interactive auth bootstrap (OIDC/MFA). **No** caller-auth precheck + (you have no token yet — that is the point) and **no** secret-read gate (it + establishes the identity the gate would need). Runs interactively as the caller; + `--exec` is rejected; the token lands in the caller's own store and warden never + captures it. + +--- + +## What proxying requires + +- An `exec_capable` catalog entry with a resolvable `fetch_command`. +- For `secret` lanes: the caller already authenticated (`VAULT_TOKEN`/`BAO_TOKEN` or + `~/.vault-token`) and a loadable `warden.yaml` (for policy posture + audit sink). +- All `<…>` placeholders resolved — `warden access` **refuses to run a half-templated + command** rather than guess an owner-confirmed resource name. Supply `--domain`, + `--field`, and `--path` as needed. + +Audit lands in `state_dir/access-audit.log` (JSON lines, metadata only: who, need id, +owner, domain, action, policy decision id — never a value). + +--- + +## See also + +- `wiki/AccessRouting.md` — issue / route / assist roles +- `wiki/CredentialRouting.md` — which subsystem owns each need +- `registry/routing/catalog.yaml` — handoff fields + lanes +- `wiki/PolicyGatedSigning.md` — the flex-auth gate (shared with the SSH lane) +- `.claude/rules/credential-routing.md` — agent-facing routing + anti-patterns +- `history/2026-06-27-operator-access-assist-charter.md` — the proxy-mode decision diff --git a/workplans/WARDEN-WP-0014-operator-access-assist.md b/workplans/WARDEN-WP-0014-operator-access-assist.md index f24635a..c581dac 100644 --- a/workplans/WARDEN-WP-0014-operator-access-assist.md +++ b/workplans/WARDEN-WP-0014-operator-access-assist.md @@ -4,7 +4,7 @@ type: workplan title: "Operator Access Assist — warden access front door" domain: infotech repo: ops-warden -status: active +status: finished owner: codex topic_slug: custodian planning_priority: high @@ -172,20 +172,21 @@ state_hub_task_id: "481997e4-193d-4724-84a6-61cbc2940153" ```task id: WARDEN-WP-0014-T05 -status: todo +status: done 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. +- [x] `wiki/OperatorAccessAssist.md` — the `warden access` contract, the conduit-vs-broker + boundary, and the three guardrails (+ the catalog secret-material guard) as a + security-model statement; lanes documented. +- [x] Updated `wiki/AccessRouting.md` (issue/route/**assist** roles + reconciled the + anti-patterns table so the conduit doesn't contradict it) and the + `.claude/rules/credential-routing.md` agent rule (added `warden access` + the + "standing broker forbidden, transparent `--fetch` sanctioned" anti-pattern). +- [x] SCOPE/INTENT: recorded the pointer→assist charter extension; SCOPE implemented + list + Getting Oriented updated; maturity vector A4 → **A5** on Availability. +- [x] `history/2026-06-27-operator-access-assist-charter.md` — decision record. ---