diff --git a/SCOPE.md b/SCOPE.md index 529463b..e6e926b 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -483,6 +483,7 @@ keywords: [access, credential, secret, npm, token, api-key, openbao, key-cape, l | `wiki/CredentialRouting.md` | Which subsystem for each credential need | | `wiki/WorkloadSecurityPosture.md` | Secret-store posture, workload maturity, and blocker triage | | `registry/routing/catalog.yaml` | Machine-readable routing pointer catalog | +| `tenancy.yaml` | Declared tenancy posture (`I1 A1 E0 P n/a R n/a V0`) and why each axis sits where it does | | `wiki/NetKingdomSecurityMap.md` | Platform security component map | | `examples/warden.production.example.yaml` | Production warden.yaml template | | `wiki/PolicyGatedSigning.md` | flex-auth opt-in gate + registry rollout | diff --git a/tenancy.yaml b/tenancy.yaml new file mode 100644 index 0000000..289b21d --- /dev/null +++ b/tenancy.yaml @@ -0,0 +1,149 @@ +# ops-warden tenancy posture declaration +# Framework: net-kingdom/canon/standards/tenancy-posture_v0.1.md draft-9 +# Conformance rule (§6): accuracy, not altitude. Nothing here is aspirational. +# Validate: python3 ~/net-kingdom/tools/tenancy-posture/validate.py tenancy.yaml + +schema_version: "0.1" +framework: netkingdom-tenancy-posture +service: ops-warden +role: ssh-certificate-authority + +tenancy: + current: + I: 1 + A: 1 + E: 0 + P: "n/a" + R: "n/a" + V: 0 + implemented: + A: 3 + target: + I: 1 + A: 3 + E: 0 + P: "n/a" + R: "n/a" + V: 1 + reviewed: "2026-08-19" + review_due: "2027-02-18" + service_class: interactive + permanent: [P, R] + + gap: + I: >- + ops-warden has a tenant notion — `policy.tenant` in warden.yaml, and the + tenant/platform path split introduced by WP-0028 — but it is a static + configuration constant, not a claim verified on an inbound call. `warden` + is a CLI invoked by an operator or agent; its caller is an OS user and no + token is presented to it. Subject id comes from `WARDEN_POLICY_SUBJECT` + or falls back to the actor name, which is §4.1's "taken from the request" + case exactly. I1. + + Not declared permanent. `warden desk` (WP-0029) already performs an OIDC + login against key-cape, so a verified inbound identity is reachable + rather than structurally excluded. It is simply not built, and I2 would + require it on the signing path, not just the desk. + A: >- + There is a single choke point on the signing path — inventory membership, + actor type, and the TTL ceiling — but it binds an *actor* to principals, + not a request to the tenants it may act for. Tenant context is a + constant, so "bound once, centrally" (§4.2 A2) would be true only in a + trivial sense that overclaims. A1. + + `implemented: A3` is the honest separate fact: delegation to flex-auth as + PDP is built (`src/warden/policy.py`, `check_sign_policy`) and was + verified live on 2026-08-19 against the enforcing `flex-auth-ops-warden` + pin — `decision:f3f7c88f9585582a`, with an anonymous `/v1/check` + returning 401. It is not `current` because `policy.enabled` is false, and + it is false by decision rather than by blocker: `ADR-0006` scopes + enforcement to security zones, which `zone-engine` is defining + (`ZONE-WP-0001`). Evidence: + `history/2026-08-19-flex-auth-caller-identity-evidence.md`. + + A4 is not a target here. The AuthZEN interface question belongs to + flex-auth as the decision point; ops-warden would follow it, not lead it. + E: >- + E0 is accurate and is not a defect to remediate. ops-warden holds no + tenant-partitioned data: `registry/routing/catalog.yaml` is a pointer + layer carrying no secret values (`ADR-0001`, CI-enforced) and no `tenant` + field on any entry; local state (`signatures.log`, `audit.jsonl`, + `access-audit.log`) is operator-scoped and keyed by actor, not tenant. + + The tenant boundaries ops-warden *routes* to — `tenants/binky/...` versus + `platform/workloads/...` — are enforced by OpenBao policy, which is the + owner's control, not ops-warden's. Claiming E1 on the strength of someone + else's enforcement is the overclaim §6 prohibits. Under `ADR-0002` + ops-warden is a transparent conduit and takes no custody, so it has no + tenant data to key. Raising E would mean acquiring data it is out of + scope to hold. + P: >- + n/a and permanent. No primary datastore: state is files under + `~/.local/state/warden` on the invoking operator's machine, and + §3.3 scopes P to the primary datastore. Custody of secrets is explicitly + out of scope (`SCOPE.md`, `ADR-0002`), so ops-warden will not acquire a + tenant-bearing substrate. + R: >- + n/a and permanent, on the same ground as P: no tenant data at rest. + + Named honestly rather than hidden behind the n/a: ops-warden *does* keep + local operator records indefinitely — `audit.jsonl` is append-only by + design (WP-0022) and `signatures.log` has no retention position. They are + metadata-only and guarded against secret material, and they are not + tenant data, so they do not move this axis. But "no declared retention + for local audit" is a real gap on a different axis than this file grades, + and is recorded here so it is not lost. + V: >- + V0. `warden sign` depends synchronously on OpenBao at + `https://bao.coulomb.social` (railiance01) and, once enabled, on the + flex-auth pin reached through the ops-bridge tunnel + `flex-auth-ops-warden-railiance01`. §4.6.1 takes the minimum across that + path, and none of it has an exercised recovery objective. + + A `backend: local` CA exists in the code and is covered by tests, but it + has never been exercised as a production degraded mode, and §13 does not + accept "the code path exists" as V evidence. Target V1 means documenting + and actually rehearsing recovery of the signing path — not adding + redundancy, which the substrate cannot currently support: `reef-railiance` + is single-node with a shared control plane, and under Decision 4.6.1 that + caps V for everything bound to it (see NK-WP-0027). + +# zones: (reserved, tenancy-posture_v0.1 Decision 5.6) +# +# Deliberately absent. The security-zone standard is being drafted by +# zone-engine (ZONE-WP-0001) and no zone vocabulary exists yet, so any value +# here would be fabricated — the failure §6 exists to prevent. ops-warden is +# the first intended consumer and will populate this key once the sibling +# standard defines it (WARDEN-WP-0032-T03). + +evidence: + A: >- + Built and verified live 2026-08-19 against the enforcing + `flex-auth-ops-warden` pin: `decision:f3f7c88f9585582a`, anonymous + `/v1/check` 401. Record: + `history/2026-08-19-flex-auth-caller-identity-evidence.md`. Re-establish on + demand with `scripts/check_policy_caller_identity.py`. Why it is implemented + and not current: `docs/adr/ADR-0006-enforcement-is-zone-scoped.md`. + E: >- + `docs/adr/ADR-0002-conduit-not-broker.md` and `ADR-0001` (catalog is a + pointer layer, CI-enforced) — why no tenant-keyed data exists to enforce on. + P: >- + No datastore. State is operator-local files under `~/.local/state/warden`. + V: >- + Production signing path verified 2026-06-17 + (`history/2026-06-17-openbao-production-verify.md`). No recovery exercise + exists for that path, which is why this is V0 and not V1. + +notes: + - >- + ops-warden issues short-lived SSH certificates and routes every other + credential need to its owner. + - >- + Five of six axes are low because it deliberately holds nothing. The low + grades on E, P and R are the intended consequence of ADR-0002 and ADR-0005, + not deferred work, and raising them would mean acquiring data ops-warden is + out of scope to hold. + - >- + The two axes with real movement are A — built, deferred by ADR-0006 pending + the zone model — and V, which needs a rehearsed recovery and is capped by + the substrate until NK-WP-0027 lands.