INTENT.md: the pep_stance gap and the three phase-4 write contacts are marked accepted under the founder's plan-approval exception (GOVERN @ estate, 2026-09-21, review 2026-12-21). They stay declared; no stance map is published. kubernetes-plane-apply records the readiness-tiered change gate and states that phase 4 does not enforce it yet. MASON-WP-0006 plans the readiness-tier check. It waits on a founder answer for the whitehat plane, which has no readiness_state and so defaults to production. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 63291@bnt-lap001 Assistant-Session: 8bd77868-ca68-4f49-bb1e-d539ecc0d703
435 lines
22 KiB
Markdown
435 lines
22 KiB
Markdown
---
|
|
# NetKingdom security layer declaration (net-kingdom security-layer-model, §11).
|
|
#
|
|
# This frontmatter IS ops-mason's declaration, stated in its own voice. The
|
|
# 2026-08-28 review note in the body, the §4 catalog row, and correspondence
|
|
# from gate-house and access-engine are statements ABOUT ops-mason and are not
|
|
# the declaration (§11). INTENT.md governs; no layer.yaml sidecar is written
|
|
# (GH-DEC-2026-017 §1). No standard version appears anywhere in this block
|
|
# (GH-DEC-2026-017 §5, A12 r2 per GH-DEC-2026-020): the layer is a standing
|
|
# property of the repository.
|
|
#
|
|
# Worked out from ops-mason's own character against §3, not copied from §4:
|
|
# every construction plan is drafted and self-reviewed by an agent and gated by
|
|
# a founder decision -- judgment, not a deterministic function of input state.
|
|
# Its artifacts are construction plans, executive summaries and build records
|
|
# (specifications, decisions, tasks). That is Staff (§3.4). It holds no state
|
|
# another layer reads at runtime: what it builds lives in OpenBao and the
|
|
# cluster, its audit log is its own. Phase 4 causes protected side effects --
|
|
# it writes OpenBao policies and auth roles, mints and delivers AppRole
|
|
# secret_ids, and applies Kubernetes objects -- so it is PEP-shaped (§6.4).
|
|
# This agrees with §4's row. The finding is below the layer line: the shipped
|
|
# executors contact Tooling directly, which the 2026-08-28 review note says
|
|
# ops-mason does not do. Those contacts are declared here as §5.3 gaps --
|
|
# tracked non-conformance, not a claim of conformance.
|
|
layer: Staff
|
|
role: pep-shaped
|
|
repository: ops-mason
|
|
declared_by: INTENT.md
|
|
declared_at: "2026-09-21"
|
|
ruling: GH-DEC-2026-017
|
|
|
|
# §6.4 obligation 3 / §13.1 -- DECLARED GAP, not a published map. ops-mason's
|
|
# stance is undecided rather than merely unpublished: its builds are gated by a
|
|
# founder approval of a construction plan, not by an access-engine decision
|
|
# record, so there is no engine outage for a stance to govern until ops-mason
|
|
# decides whether its build path consults access-engine at all. Publishing a
|
|
# fail-closed map today would describe behaviour the code does not have.
|
|
#
|
|
# 2026-09-21: the founder, exercising GOVERN @ estate, settled the open option:
|
|
# phase-4 builds remain founder-approval-gated only (activation=APPROVED by
|
|
# plan.is_approved()). That is a founder-accepted exception to §6.4, not
|
|
# conformance and not a change to the standard. The gap stays declared; it is
|
|
# now accepted rather than undecided. No stance map is published: the code does
|
|
# not consult access-engine, so a map describing access-engine gating would
|
|
# describe behaviour ops-mason does not have.
|
|
pep_stance:
|
|
status: declared-gap
|
|
gap_state: accepted
|
|
path: null
|
|
blocked_on: >-
|
|
Phase-4 builds do not consult access-engine (§6.4 obligation 1). The
|
|
founder accepted founder plan approval (activation=APPROVED) as the gate
|
|
instead, so there is no engine outage for a stance map to govern.
|
|
accepted_exception: &plan-approval-gate
|
|
decision: the-custodian/docs/ops-mason-plan-approval-gate-decision.md
|
|
decided_by: "Bernd Worsch (founder), GOVERN @ estate"
|
|
decided_at: "2026-09-21"
|
|
gate: "activation=APPROVED: founder approval of the construction plan (plan.is_approved())"
|
|
covers: >-
|
|
The phase-4 writes declared here at ops-mason@0ff263a: OpenBao policies,
|
|
auth roles on auth/approle and auth/kubernetes, AppRole secret_id
|
|
creation, and kubectl apply. A new class of protected change is not
|
|
covered until the founder accepts it.
|
|
holds_while: "Changes stay structure-only; no secret value is read, written or logged."
|
|
conformance_report: accepted-exception
|
|
review: "2026-12-21"
|
|
review: "2026-12-21"
|
|
|
|
# §11: every direct Tooling contact, one entry each. Empty would be a claim.
|
|
tooling_contacts:
|
|
- id: openbao-structure-build
|
|
shape: "5.3"
|
|
module: src/ops_mason/executor.py
|
|
symbol: build_approle_kv_lane, build_kubernetes_kv_lane
|
|
tooling: OpenBao
|
|
operation: "bao policy write; bao write auth/approle/role/*, auth/kubernetes/role/*"
|
|
write: true
|
|
capability: "Create policies and auth roles for an approved construction plan (structure only, never secret values)"
|
|
intended_owner: secrets-engine
|
|
blocked_on: >-
|
|
No engine exposes a policy / auth-role construction surface; ops-mason
|
|
runs the bao CLI under a scoped, time-boxed ops-mason-build token.
|
|
review: "2026-12-21"
|
|
gap_state: accepted
|
|
accepted_exception: *plan-approval-gate
|
|
- id: openbao-approle-credential-delivery
|
|
shape: "5.3"
|
|
module: src/ops_mason/executor.py
|
|
symbol: build_approle_kv_lane
|
|
tooling: OpenBao
|
|
operation: "bao read auth/approle/role/*/role-id; bao write -f auth/approle/role/*/secret-id"
|
|
write: true
|
|
capability: "Mint and deliver an AppRole role_id/secret_id to the consumer, write-only, never logged"
|
|
intended_owner: secrets-engine
|
|
blocked_on: >-
|
|
No engine exposes credential issuance for AppRole lanes; secrets-engine
|
|
owns credential lifecycle but fronts no issuance API today.
|
|
review: "2026-12-21"
|
|
gap_state: accepted
|
|
accepted_exception: *plan-approval-gate
|
|
- id: openbao-custody-metadata-read
|
|
shape: "5.1"
|
|
module: scripts/custody-inventory.py
|
|
symbol: bao
|
|
tooling: OpenBao
|
|
operation: "bao kv list; bao kv metadata get"
|
|
write: false
|
|
capability: "Inventory credential paths and their descriptions without reading any value"
|
|
intended_owner: secrets-engine
|
|
blocked_on: "No engine exposes a custody inventory query."
|
|
review: "2026-12-21"
|
|
- id: kubernetes-plane-apply
|
|
shape: "5.3"
|
|
module: src/ops_mason/kubernetes_plane.py
|
|
symbol: preflight, verify, apply
|
|
tooling: Kubernetes
|
|
operation: "kubectl get / auth can-i / apply (server dry-run, then apply) of an allowlisted, digest-pinned bundle"
|
|
write: true
|
|
capability: "Build small source-pinned security planes (namespaces, service accounts, network policies)"
|
|
intended_owner: null
|
|
blocked_on: >-
|
|
Kubernetes is not catalogued in §4, so no engine is named as its owner.
|
|
ops-mason reads the cluster as Tooling by character (§3.2) and declares
|
|
the contact rather than treating it as out of scope; raised with
|
|
gate-house as a catalog question.
|
|
review: "2026-12-21"
|
|
gap_state: accepted
|
|
accepted_exception: *plan-approval-gate
|
|
# 2026-09-21, founder (GOVERN @ estate): no engine owns this contact. The
|
|
# gate on ADMINISTER @ realm:kubernetes/railiance01 is a quality gate, not
|
|
# an authorization decision, tiered by the target's railiance-master
|
|
# ADR-0006 readiness_state. The owner question above is answered: the
|
|
# Kubernetes API stays a Tooling contact owned by rail-kubernetes.
|
|
change_gate:
|
|
decision: the-custodian/docs/kubernetes-change-gate-decision.md
|
|
decided_by: "Bernd Worsch (founder), GOVERN @ estate"
|
|
decided_at: "2026-09-21"
|
|
engine_owner: none
|
|
tooling_owner: rail-kubernetes
|
|
tiers:
|
|
- readiness_state: [declared, installed, verified]
|
|
path: "direct ADMINISTER @ realm:kubernetes by ops-mason"
|
|
activation: APPROVED
|
|
evidence_boundary: target-audited
|
|
- readiness_state: [production-approved]
|
|
path: "CONSTRUCT @ manifest repository, reconciled by ArgoCD (railiance-platform)"
|
|
activation: APPROVED
|
|
evidence_boundary: external-audited
|
|
- readiness_state: [production-approved]
|
|
path: "direct ADMINISTER, emergency only; recorded and reconciled back into the manifest repository"
|
|
activation: BREAK_GLASS
|
|
evidence_boundary: target-audited
|
|
no_readiness_state: production-tier
|
|
transition:
|
|
target: rapp-policy-nexus
|
|
until: "2026-12-21"
|
|
rule: "Direct ADMINISTER under activation=APPROVED, each change recorded as production-tier, until ArgoCD onboarding."
|
|
relies_on_limits: "One expected namespace per plan; Pod and Secret kinds refused; no data or stringData. Widening them is a new decision."
|
|
enforcement: "Not yet in code: phase 4 does not check readiness_state. Planned in workplans/MASON-WP-0006-readiness-tier-check.md."
|
|
- id: bao-session-grant
|
|
shape: "5.2"
|
|
module: scripts/bao-session.sh
|
|
symbol: login, grant, revoke
|
|
tooling: OpenBao
|
|
operation: "Operator OIDC login; mint a scoped, bounded token under the operator's own session"
|
|
write: true
|
|
capability: "Hand an agent session a task-scoped ops-mason-build token instead of the operator's session"
|
|
supplied_authority: none
|
|
note: >-
|
|
Runs under the operator's own authority and narrows it; ops-mason holds
|
|
no standing credential of its own.
|
|
|
|
non_tooling_clients:
|
|
- module: ops-warden registry/routing/catalog.yaml (git contribution)
|
|
target: ops-warden
|
|
rationale: "A proposed catalog entry is a repository commit, not a runtime call."
|
|
- module: State Hub (session protocol)
|
|
target: state-hub
|
|
rationale: "Not catalogued in §4; coordination only, no security state."
|
|
---
|
|
|
|
# INTENT
|
|
|
|
> **NetKingdom layering review — 2026-08-28.** This repository's role was reviewed
|
|
> against the NetKingdom IT-security layer model: **Taxonomy → Tooling → Engines →
|
|
> Staff**, layered by determinism and by the kind of artifact each layer produces.
|
|
> Findings and the argument behind them:
|
|
> `gate-house/history/2026-08-28-security-layer-model-and-gate-house-recut.md`.
|
|
> The model is `net-kingdom/canon/standards/security-layer-model_v0.1.md` (proposed),
|
|
> ratified by `gate-house/decisions/decisions.md` GH-DEC-2026-001.
|
|
>
|
|
> The layer rule that binds every repository: **Staff never touches tooling
|
|
> directly. It acts only through engine APIs.**
|
|
>
|
|
> **This repository is Staff — interactive, non-deterministic; builds and tears down.** Add the layer label and the Staff invariant: ops-mason acts through engine APIs, never against tooling directly. Record the demarcation this repository is half of: **ops-mason and ops-warden own access routes and lanes — how a worker reaches a host; access-engine owns access rules — whether they may.** The perimeter doctrine ops-mason builds to is gate-house's; the building is ops-mason's.
|
|
>
|
|
> *This note records what should change. The body below is not yet adapted.*
|
|
|
|
> This file captures **why this repository exists**, the **direction it is
|
|
> moving toward**, and the **kind of system it is meant to become**. It is
|
|
> intentionally aspirational and stable, not a description of current
|
|
> implementation.
|
|
|
|
---
|
|
|
|
## One-liner
|
|
|
|
**The builder of NetKingdom security infrastructure — creates, changes,
|
|
maintains, and tears down access routes, credentials, tokens, and policies
|
|
so that ops-warden always has something real to route to.**
|
|
|
|
---
|
|
|
|
## Why this exists
|
|
|
|
ops-warden is deliberately narrow: it issues short-lived SSH certificates and
|
|
**routes** every other credential need to the subsystem that owns it — it
|
|
"owns no secret store and vends nothing" (`ops-warden/wiki/AccessRouting.md`).
|
|
That's the right shape for a front door, but it leaves a real gap: **something
|
|
has to actually build the AppRole, the policy, the KV secret path, the
|
|
catalog entry** that ops-warden then points workers at. Today that gap is
|
|
filled ad hoc — a founder spends "~10 minutes" hand-running `bao write`
|
|
commands per new lane (see `binky-control/integrations/executor-worker-secrets.md`
|
|
for a representative example: a new AppRole, a new policy, delivered
|
|
role_id/secret_id files, a catalog entry, repeated by hand every time a new
|
|
consumer needs a credential).
|
|
|
|
ops-mason exists to turn that ad hoc founder mechanic into a **reviewed,
|
|
repeatable construction process** — so new security infrastructure gets
|
|
built consistently, against what already exists, with a human decision point
|
|
before anything is actually applied, instead of bespoke one-off provisioning
|
|
every time.
|
|
|
|
---
|
|
|
|
## The Mission
|
|
|
|
ops-mason **builds** NetKingdom security infrastructure — OpenBao AppRoles,
|
|
policies, KV secret paths, and their ops-warden catalog entries — as defined
|
|
by architecture documents (`net-kingdom`) and by real consumer demand (a
|
|
repo/workload that needs a credential lane). It does not decide *whether*
|
|
access should exist — that's an architecture or founder decision — it
|
|
figures out *how* to build what's already been decided on, and does the
|
|
build once approved.
|
|
|
|
### The four-phase process
|
|
|
|
1. **Construction plan.** Given an access demand (a consumer, a credential
|
|
type, a scope) and the concept behind it, draft what needs to be created,
|
|
changed, or torn down to satisfy it — respecting, extending, or compacting
|
|
existing structure rather than defaulting to "create something new" every
|
|
time. Check first: does an existing policy/path/lane already cover this
|
|
(the way `rein-aharness`'s mail-triage reuses the existing
|
|
`llm-connect-provider-secrets` lane instead of getting its own)? Does
|
|
satisfying this demand let two existing near-duplicate lanes be merged
|
|
instead of adding a third?
|
|
2. **Review and optimize.** Check the draft plan against what's already
|
|
provisioned (no redundant policies, no orphaned roles), consistency with
|
|
existing naming/TTL/scoping conventions, and ease of use for the
|
|
consumer. This is a self-review pass before a human ever sees the plan —
|
|
the plan that reaches phase 3 should already be the best version of
|
|
itself, not a first draft.
|
|
3. **Executive summary — the decision gate.** Render what the plan actually
|
|
grants: which roles get which access, to what, for how long, blast radius
|
|
if the credential leaks, and what it costs to reverse. This is the one
|
|
mandatory human checkpoint, and it exists for exactly the reason
|
|
ops-warden's own design principle names: *"the founder is escalated to,
|
|
never tasked with mechanics"* — ops-mason does the mechanical work in
|
|
phases 1, 2, and 4; phase 3 is the founder's one decision, made in policy
|
|
terms (approve, reject, or send back to phase 1 with feedback), not asked
|
|
to review raw `bao` commands.
|
|
4. **Build.** Once approved, execute the plan — create/modify/tear down the
|
|
AppRole, policy, and secret path *structure*, then propose the matching
|
|
ops-warden catalog entry. Not before phase 3 approval, ever, for anything
|
|
that grants or widens access. (Tear-down of something already flagged for
|
|
removal, or a dry-run/plan-only mode, does not require the same gate —
|
|
see Design Principles.)
|
|
|
|
**ops-mason never touches secret values, including its own builds.** It
|
|
creates the empty KV path, the policy, and the AppRole — structure only.
|
|
The actual secret value (an API key, a password) goes in through
|
|
ops-warden's existing **paste-once provision** desk
|
|
(`ops-warden/src/warden/desk.py`'s `paste_once_provision` act): a
|
|
localhost-only web form where the founder pastes the value once, written
|
|
directly via `bao kv put` and never shown in a terminal, chat, or audit
|
|
log. Phase 4 ends with "structure built, ready for paste-once
|
|
provisioning" — it does not end with a live credential.
|
|
|
|
---
|
|
|
|
## Responsibility boundary
|
|
|
|
### ops-mason owns
|
|
|
|
- Drafting and executing construction plans for new/changed/retired OpenBao
|
|
AppRoles, policies, and KV secret paths (structure — never secret values,
|
|
see phase 4 above)
|
|
- Consistency review against existing NetKingdom credential structure —
|
|
reuse over duplication, compaction over sprawl
|
|
- The executive-summary format that makes a plan decidable at a glance
|
|
- Proposing the matching entry in ops-warden's routing catalog
|
|
(`ops-warden/registry/routing/catalog.yaml`) for what it builds
|
|
- Its own audit trail of what it built, when, and under which approved plan
|
|
|
|
**Catalog entries are pointer-shaped, same rule ops-warden enforces on
|
|
every non-SSH entry (the catalog's own "no-double-source rule",
|
|
`ops-warden/workplans/WARDEN-WP-0010-access-routing-charter.md`) —
|
|
corrected here 2026-07-27 after checking real catalog entries, not just
|
|
the header comment:** `id`/`title`/`need_keywords`/`owner_repo`/
|
|
`subsystem`/`wiki_ref`/`canon_ref`/`reviewed`/`status`, always
|
|
`warden_executes: false` (ops-mason built the lane, it does not execute
|
|
the runtime credential fetch — the consumer's own code does, the way
|
|
`rein-openweights/credentials.py` does today). Fields like
|
|
`auth_method`/`path_template`/`fetch_command`/`rotation.steps` **are**
|
|
expected on non-SSH entries — e.g. `agent-harness-binky-mail-approle`
|
|
carries all of them despite `warden_executes: false`. What's actually
|
|
reserved for `warden_executes: true` (ops-warden's own SSH lane) is a
|
|
bare **top-level** `steps:` + `cert_command:` pair — that specific
|
|
shape, not procedural detail in general. A companion wiki playbook
|
|
(`wiki/playbooks/<id>.md`, `## Worker checklist` section) is the norm,
|
|
same as every other lane in the catalog — not a violation of
|
|
no-double-source as long as it doesn't restate a *different owner's*
|
|
procedure.
|
|
|
|
Landing a catalog entry is a normal git contribution to the `ops-warden`
|
|
repo (a commit/PR touching `registry/routing/catalog.yaml`), subject to
|
|
its own CI (`tests/test_routing.py` — anchor resolution) and its stale-review
|
|
cadence (new entries typically start `status: draft` until the built lane
|
|
is verified end-to-end, then promoted to `active` — same as any other
|
|
owner-ship promotion in that catalog). ops-mason does not have, and does
|
|
not need, some separate live registration API into ops-warden.
|
|
|
|
### ops-mason does not own
|
|
|
|
| Need | Owner | ops-mason's relationship |
|
|
|---|---|---|
|
|
| Whether access *should* exist at all | Architecture docs / founder | ops-mason builds what's decided, does not decide policy from nothing |
|
|
| Runtime authorization ("may actor X do Y") | flex-auth | ops-mason provisions the policy; flex-auth evaluates it at runtime |
|
|
| Identity, MFA, human auth | key-cape / Keycloak | Out of scope entirely |
|
|
| Routing consumers to the right lane | ops-warden | ops-mason feeds ops-warden's catalog; does not replace `warden access`/`warden route` |
|
|
| SSH certificate issuance | ops-warden | Untouched — ops-mason works in OpenBao AppRole/policy/KV space, not the SSH CA lane |
|
|
| OpenBao cluster init/unseal, platform deploy | railiance-platform | Out of scope — ops-mason operates within an already-running OpenBao |
|
|
| Holding or logging secret values | Nobody, ever | Same invariant as ops-warden: values live in OpenBao and process env only |
|
|
| Delivering the actual secret value into a path ops-mason built | ops-warden (`paste_once_provision` desk) | ops-mason builds the empty structure; the founder pastes the value once through ops-warden's existing local web form, never through ops-mason |
|
|
|
|
**ops-mason is not a second secrets manager and not a policy-decision engine.**
|
|
It is the construction crew for a foundation ops-warden and every credential
|
|
consumer stands on.
|
|
|
|
---
|
|
|
|
## Design principles
|
|
|
|
1. **Reuse before creation.** Every construction plan's first move is
|
|
checking whether existing structure already covers the demand. New
|
|
infrastructure is the fallback, not the default.
|
|
2. **Compaction is in scope, not just addition.** ops-mason may propose
|
|
merging or retiring redundant lanes as part of a construction plan, not
|
|
only adding new ones.
|
|
3. **One mandatory human gate, not a gate on everything.** Anything that
|
|
grants or widens access stops at the phase-3 executive summary. Read-only
|
|
plan drafts, dry runs, and tear-down of something already approved for
|
|
removal do not need to re-litigate the same decision.
|
|
4. **Mechanics stay with the agent; decisions stay with the founder.**
|
|
Directly extends ops-warden's own principle 7. The executive summary is
|
|
written so the founder never has to read a `bao` command to make the
|
|
call.
|
|
5. **Nothing built stays invisible.** Every successful build is followed by
|
|
a proposed catalog entry to `ops-warden` in the same phase-4 action
|
|
(pointer-only, `status: draft` until verified end-to-end) — there should
|
|
be no infrastructure ops-mason built that isn't at least a draft entry
|
|
away from `warden route find` surfacing it.
|
|
6. **Posture-aware, like ops-warden.** The organization is in build phase
|
|
(one founder-operator, pre-revenue) — construction plans should reflect
|
|
current posture (see `ops-warden/wiki/WorkloadSecurityPosture.md`) and
|
|
tighten as posture graduates, not assume production-tier rigor on day one
|
|
or dev-tier looseness forever.
|
|
|
|
---
|
|
|
|
## Relationship to the rest of NetKingdom
|
|
|
|
```text
|
|
net-kingdom — owns the canonical security architecture
|
|
|
|
|
v
|
|
ops-mason — builds the infrastructure that architecture calls for
|
|
| (AppRoles, policies, KV paths, catalog entries)
|
|
v
|
|
ops-warden — routes consumers to what ops-mason built
|
|
|
|
|
v
|
|
OpenBao / flex-auth / key-cape — hold custody, evaluate policy, verify identity
|
|
```
|
|
|
|
NetKingdom defines what the security model should look like. ops-mason
|
|
builds the concrete lanes that satisfy it. ops-warden is the front door that
|
|
points consumers at those lanes once built. Confusing ops-mason's builder
|
|
role with ops-warden's router role — or with OpenBao's custody role —
|
|
recreates exactly the "wrong subsystem for the credential need" confusion
|
|
ops-warden itself exists to prevent.
|
|
|
|
---
|
|
|
|
## Non-goals
|
|
|
|
- Being a second OpenBao, a second ops-warden, or a policy-decision engine
|
|
- Deciding, unprompted, that new access should exist — that's an
|
|
architecture/founder call, ops-mason builds what's already decided
|
|
- Applying anything that grants or widens access without a phase-3 approved
|
|
plan
|
|
- Touching secret values at all, even transiently — structure only; values
|
|
go in via ops-warden's `paste_once_provision` desk, not through ops-mason
|
|
- Owning OpenBao cluster lifecycle, flex-auth runtime, or key-cape identity
|
|
|
|
---
|
|
|
|
## Success criteria
|
|
|
|
ops-mason is succeeding when:
|
|
|
|
1. A new credential lane can go from "a consumer needs this" to "built,
|
|
catalogued, and routable via ops-warden" without a founder hand-running
|
|
`bao` commands.
|
|
2. Every construction plan is checked against existing structure before it's
|
|
proposed — duplicate/near-duplicate lanes are the exception, not routine.
|
|
3. The founder's only touchpoints are the phase-3 decision (plain terms:
|
|
who gets what, for how long, what it costs to undo) and, if the lane
|
|
needs a live value, one paste through ops-warden's existing desk —
|
|
never asked to review or run raw provisioning mechanics.
|
|
4. Every ops-mason build ships with a proposed ops-warden catalog entry —
|
|
no undocumented lanes, even in `draft` status.
|
|
5. Tearing down access is as routine and reviewed a process as creating it —
|
|
sprawl doesn't accumulate because removal is just as supported as
|
|
construction.
|