docs(WARDEN-WP-0014): T5 — assist-layer docs, security model, INTENT/SCOPE

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-06-27 17:35:57 +02:00
parent 1c3d1b4d52
commit 5bbb791f21
7 changed files with 253 additions and 37 deletions

View File

@@ -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 <need> --fetch` |
| `warden login` (as an identity owner) | ops-warden does not establish identity | key-cape / Keycloak; to run the login *as yourself*, `warden access <login need> --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`.
---

View File

@@ -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 <need>` | Renders the owner, auth method, path, command skeleton, policy gate |
| **Assist (proxy)** | `exec_capable` lanes (OpenBao, login) | `warden access <need> --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 <p> --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