risk-nexus accepted the offer to match their convention rather than grow a second one, and published it: 14d critical/high, 30d medium, 60d low, nothing auto-closing on staleness alone. Their preference — point warden route gaps at those windows and the two registers agree without a shared mechanism — is better than a joint tool. blocker_stale_days() now maps lane risk onto those windows. A flat 14 would have been wrong in both directions: too aggressive for a low-risk pointer, and it treated an admin PAT lane the same as one. ungraded takes the shortest window, not the longest. ADR-0007 makes an absent grade a defect and ADR-0008 makes a grade cover the whole path, so a lane nobody graded is the one whose blocker deserves least trust. Encoding that as 60 days would have been the fail-open default this repo already fixed once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
286 lines
14 KiB
Markdown
286 lines
14 KiB
Markdown
# Access Routing — what ops-warden answers
|
||
|
||
Date: 2026-06-18
|
||
|
||
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 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
|
||
**role and boundary**.
|
||
|
||
---
|
||
|
||
## Issue vs route
|
||
|
||
| Need | Subsystem | ops-warden role | Who acts |
|
||
| --- | --- | --- | --- |
|
||
| SSH cert for host/ops access (`adm`/`agt`/`atm`) | **ops-warden** | **Issue** (`warden sign`) | ops-warden signs; worker uses cert |
|
||
| API key / DB cred / dynamic lease | OpenBao | Route — point at path | Worker calls OpenBao |
|
||
| "May I perform action X?" | flex-auth (+ Topaz PDP) | Route — point at policy | Worker/PEP calls flex-auth |
|
||
| Login / OIDC token / MFA | key-cape / Keycloak | Route — point at IAM Profile | Worker authenticates |
|
||
| Object-storage STS / S3 creds | net-kingdom + flex-auth + OpenBao | Route — point at vending path | Worker follows NK-WP-0007 |
|
||
| SSH tunnel / port forward | ops-bridge | Route — supply `cert_command` | ops-bridge opens tunnel |
|
||
| Host principal / force-command | railiance-infra | Route — point at Ansible | infra deploys host |
|
||
| OpenBao cluster init / unseal | railiance-platform | Route — point at ceremony | platform operates |
|
||
|
||
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)
|
||
|
||
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` (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 |
|
||
|
||
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`.
|
||
|
||
---
|
||
|
||
## Interim custodianship
|
||
|
||
ops-warden **works with, and never replaces or duplicates**, the NetKingdom
|
||
components that own identity, custody, authorization, and tenant/user lifecycle
|
||
(`INTENT.md` §9). Covering a gap is legitimate. Silently becoming the owner of
|
||
that gap is not.
|
||
|
||
The only lane ops-warden executes with its own authority is **SSH issuance**
|
||
(`ssh-cert-host-access`). Every other execution position — including every
|
||
`warden access` proxy — is **interim by default**. A catalog entry without a
|
||
`delegation:` block is treated as interim with an unknown owner: absence is a
|
||
question, not a settlement.
|
||
|
||
| `delegation.mode` | Meaning |
|
||
| --- | --- |
|
||
| `permanent` | ops-warden is the designed owner of this front door (SSH only, today) |
|
||
| `native` | the intended owner already fronts it; ops-warden routes (and may keep a proxy as fallback) |
|
||
| `interim` | ops-warden covers the gap until `intended_owner` ships the missing front door named in `blocked_on` |
|
||
|
||
Delegation targets — the components that should own a front door once they have
|
||
one:
|
||
|
||
| Target | What they should front |
|
||
| --- | --- |
|
||
| **secrets-engine** | owner-native secret-exec (`secrets-engine exec --catalog <id>`) |
|
||
| **tenant-engine** | tenant/client secret custody and tenant-lane front door |
|
||
| **user-engine** | end-user / account-lifecycle secrets that belong with user identity |
|
||
| **railiance-platform** | OpenBao cluster, credential broker, platform workload procedure |
|
||
| **flex-auth** | authorization decisions (already native — ops-warden only points) |
|
||
| **key-cape** | identity login and client-credential protocol (OIDC, `client_secret_basic`) |
|
||
|
||
Query the register with `warden route gaps`. An interim lane is retired by
|
||
setting `exec_owner` / `exec_command` (the WP-0019 pattern) and flipping
|
||
`delegation.mode` to `native` once the owner's front door is proven. Do not
|
||
delete a working proxy on the way.
|
||
|
||
---
|
||
|
||
## Routing lookup CLI (`warden route`)
|
||
|
||
Agents and operators query the pointer catalog directly instead of re-deriving
|
||
routing from wiki prose. The command group is **read-only** — it never calls
|
||
OpenBao, flex-auth, key-cape, or any other subsystem, and never returns secret
|
||
material.
|
||
|
||
```bash
|
||
warden route list [--json] [--all] [--tag <keyword>] # active-only unless --all
|
||
warden route list --stale [--stale-days 90] [--all] [--json] # past review cadence
|
||
warden route gaps [--json] [--all] # interim register (owner + blocker)
|
||
warden route show <id> [--json] # owner + pointers; SSH adds steps
|
||
warden route find "<free text need>" [--json] [--all] # rank by keyword overlap
|
||
```
|
||
|
||
Agent-oriented examples:
|
||
|
||
```bash
|
||
# "I need an API key" — find the owner, get a pointer, act there yourself
|
||
warden route find "openrouter api key" --json
|
||
warden route show openbao-api-key --json
|
||
# → {"warden_executes": false, "next_action": "next action on `railiance-platform` — see `wiki/CredentialRouting.md#routing-table`"}
|
||
|
||
# The one lane ops-warden executes: SSH. `show` appends the authored steps + cert pattern.
|
||
warden route show ssh-cert-host-access --json
|
||
# → {"warden_executes": true, "cert_command": "warden sign <actor> --pubkey <path>", "steps": [...]}
|
||
```
|
||
|
||
`show` on a routed (non-SSH) need always ends with **"next action on
|
||
`<owner_repo>` — see `<wiki_ref>`"** and never implies ops-warden performed
|
||
anything. Draft scenarios (owner path not yet shipped) are hidden unless `--all`.
|
||
|
||
---
|
||
|
||
## Audience notes
|
||
|
||
- **Human operators** read this page and `CredentialRouting.md` to choose the
|
||
right subsystem, then follow that subsystem's own docs.
|
||
- **Agents / CI** read the machine-readable routing catalog
|
||
(`registry/routing/catalog.yaml`) via `warden route` (above) so routing does
|
||
not have to be re-derived from wiki prose each session.
|
||
- **Same truth, two shapes:** humans read the wiki; agents read the catalog. The
|
||
catalog references wiki sections by anchor so the two cannot drift apart — a
|
||
test (`tests/test_routing.py`) fails CI if any `wiki_ref` anchor stops resolving.
|
||
|
||
---
|
||
|
||
## How this stays aligned
|
||
|
||
NetKingdom security architecture is canonical in `net-kingdom`. ops-warden tracks
|
||
it: when canon changes, the wiki section is updated and the catalog pointer
|
||
(`wiki_ref` + `canon_ref`) follows. ops-warden never overrides canon and never
|
||
silently forks it.
|
||
|
||
Report drift via a custodian workplan or a State Hub message to `ops-warden`.
|
||
|
||
---
|
||
|
||
## Drift review cadence
|
||
|
||
Every catalog entry carries a `reviewed:` date (`YYYY-MM-DD`) — the last time an
|
||
ops-warden steward confirmed the pointer still matches net-kingdom canon and the
|
||
owner repo's shipped path.
|
||
|
||
| Cadence | Action |
|
||
| --- | --- |
|
||
| **Quarterly** (default 90 days) | Run `warden route list --stale` — reconcile every listed entry against canon |
|
||
| **On canon change** | When net-kingdom security docs change, review affected `canon_ref` entries immediately |
|
||
| **On owner ship** | When an owning repo merges a new OpenBao path or playbook, promote `draft` → `active` and bump `reviewed` |
|
||
| **On agent confusion** | If `warden route find` misses a common query, add `need_keywords` or a playbook — do not restate owner procedure in the catalog |
|
||
| **Fortnightly** (default 14 days) | Run `warden route gaps` — re-check each `blocked_on` against the intended owner; flip to `native` when their front door exists |
|
||
|
||
### Two cadences, because they are two different claims
|
||
|
||
A catalog pointer and an interim blocker both carry a `reviewed:` date, and for a
|
||
while they shared one 90-day threshold. They should not.
|
||
|
||
| Claim | Question | Default | Where |
|
||
| --- | --- | --- | --- |
|
||
| Pointer freshness | Is this still the right owner and page? | **90 days** | `warden route list --stale` |
|
||
| Interim blocker | Has the intended owner answered / can they front this yet? | **risk-scaled, 14–60 days** | `warden route gaps` |
|
||
|
||
The blocker window scales with what the lane holds, matching `risk-nexus`'s stall
|
||
windows (`docs/method/check-procedure.md`) so the two registers agree without a
|
||
shared tool:
|
||
|
||
| Lane `risk` | Window | risk-nexus equivalent |
|
||
| --- | --- | --- |
|
||
| `high`, `ungraded` | **14 days** | critical / high |
|
||
| `standard` | **30 days** | medium |
|
||
| `low`, `accepted` | **60 days** | low |
|
||
|
||
`ungraded` takes the *shortest* window, not the longest. `ADR-0007` makes an
|
||
absent grade a defect and `ADR-0008` makes a grade cover the whole path — a lane
|
||
nobody has graded is the one whose blocker deserves least trust.
|
||
|
||
A pointer genuinely is a quarterly question. A blocker is not: it is a claim
|
||
about another repo's state at a date, and this estate invalidates those in days.
|
||
`RISK-F-0001` invalidated an ops-warden blocker in one. The `secrets-engine`
|
||
lanes carried one for ten while it was answerable from that repo's source the
|
||
whole time. A `FLEX-WP-0007` blocker was repeated by two repos for about seven
|
||
weeks after that workplan read `finished`.
|
||
|
||
Sharing 90 days did not make the blocker check loose, it made it **inert**: the
|
||
delegation register was created 2026-08-15, so the threshold could not have fired
|
||
before November and never had.
|
||
|
||
### Reviewed is not verified
|
||
|
||
The more important half. A `reviewed:` date records when someone last *touched*
|
||
the entry, which looks identical to re-checking it. So every interim lane also
|
||
carries `verified:`, saying how the claim was established:
|
||
|
||
| Value | Meaning | Counts as verification |
|
||
| --- | --- | --- |
|
||
| `owner-confirmed` | the intended owner stated the blocker's status | **yes** |
|
||
| `source-read` | re-derived from the owner's code, canon, or a CCR | **yes** |
|
||
| `asked-and-waiting` | a question is outstanding | **no** |
|
||
| `unverified` | carried forward without a check | **no** |
|
||
|
||
`asked-and-waiting` deliberately does not reset the clock. A lane asked today
|
||
reads as reviewed today, and that is precisely how a blocker stays fresh-looking
|
||
while nobody answers it. `warden route gaps` flags such a lane as stale at zero
|
||
days old.
|
||
|
||
**Re-check the blocker; do not bump the date.** Bumping `reviewed` without
|
||
re-establishing the claim is the failure this section exists to prevent, and it
|
||
is cheap to avoid — most of these are answerable by reading the owner's repo.
|
||
|
||
```bash
|
||
warden route gaps # risk-scaled cadence, plus unverified lanes
|
||
warden route gaps --stale-days 30 # flat override for every lane
|
||
warden route gaps --fail-on-stale # exit 3 — for a cron job or a gate
|
||
```
|
||
|
||
### Stale check (operators and agents)
|
||
|
||
```bash
|
||
# Entries not reviewed in the last 90 days (default threshold)
|
||
warden route list --stale
|
||
|
||
# Include draft scenarios in the stale report
|
||
warden route list --stale --all
|
||
|
||
# Custom threshold (e.g. monthly review)
|
||
warden route list --stale --stale-days 30 --json
|
||
```
|
||
|
||
For each stale entry:
|
||
|
||
1. Open `canon_ref` in net-kingdom — confirm ownership and vocabulary unchanged.
|
||
2. Open `wiki_ref` in this repo — update the playbook section if canon moved.
|
||
3. Confirm the owner path still exists (anti-stale rule: unshipped paths stay `draft`).
|
||
4. Bump `reviewed:` in `registry/routing/catalog.yaml` to today's date.
|
||
5. Run `uv run pytest tests/test_routing.py` — anchor resolution must still pass.
|
||
|
||
CI enforces structural drift (every `wiki_ref` anchor resolves; no-double-source
|
||
rule). The quarterly cadence catches **semantic** drift CI cannot detect — canon
|
||
moved but anchors still resolve.
|
||
|
||
---
|
||
|
||
## See also
|
||
|
||
- `CredentialRouting.md` — worker decision tree and routing table
|
||
- `NetKingdomSecurityMap.md` — component literacy
|
||
- `INTENT.md` — steward mission ("issue SSH, route the rest"); §9 interim custodianship
|
||
- `workplans/WARDEN-WP-0030-delegation-register.md` — delegation register
|
||
- `workplans/WARDEN-WP-0010-access-routing-charter.md` — charter + no-double-source rule
|
||
- `net-kingdom/docs/platform-identity-security-architecture.md` — platform canon
|