diff --git a/.claude/rules/repo-boundary.md b/.claude/rules/repo-boundary.md index 78f4b65..9b4eeda 100644 --- a/.claude/rules/repo-boundary.md +++ b/.claude/rules/repo-boundary.md @@ -1,15 +1,12 @@ ## Repo boundary -This repo owns the **secrets-engine** Lifecycle engine only (NetKingdom -security layer model v0.7: Engine / Lifecycle). It does not own: +This repo owns the **secrets-engine** workflow/interaction layer only. It does +not own: - Secret custody, policy, lease, and audit backend → OpenBao / railiance-platform -- SSH certificate issuance (Staff PEP) → ops-warden (`warden sign`) +- SSH certificate issuance → ops-warden (`warden sign`) - Tunnels and remote transport → ops-bridge -- Authorization decisions → access-engine (`flex-auth`) -- Approval objects → approval-engine -- Evidence custody and integrity → audit-core +- Authorization decisions → flex-auth - Identity and claim lifecycle → user-engine / key-cape -- Security doctrine and the layer model → gate-house / net-kingdom canon - Cross-system security boundary doc → net-kingdom/docs/ - Request history and progress index → State Hub (read model) diff --git a/.claude/rules/repo-identity.md b/.claude/rules/repo-identity.md index 2737de0..df32a41 100644 --- a/.claude/rules/repo-identity.md +++ b/.claude/rules/repo-identity.md @@ -1,4 +1,4 @@ -**Purpose:** secrets-engine is the Lifecycle engine for approved credential work: a deterministic API over OpenBao that orchestrates cataloged custody, delivery, and lifecycle actions across build, test, and production. It does not render authorization decisions. +**Purpose:** secrets-engine is a headless, multi-application, multi-tenant secrets workflow and automation layer that orchestrates approved secret custody, delivery, and lifecycle work across build, test, and production stages, with OpenBao as the initial enforcement backend. **Domain:** infotech **Repo slug:** secrets-engine diff --git a/AGENTS.md b/AGENTS.md index 85f170c..4694a14 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,7 +2,7 @@ ## Repo Identity -**Purpose:** secrets-engine is the Lifecycle engine for approved credential work: a deterministic API over OpenBao that orchestrates cataloged custody, delivery, and lifecycle actions across build, test, and production. It does not render authorization decisions. +**Purpose:** secrets-engine is a headless, multi-application, multi-tenant secrets workflow and automation layer that orchestrates approved secret custody, delivery, and lifecycle work across build, test, and production stages, with OpenBao as the initial enforcement backend. **Domain:** infotech **Repo slug:** secrets-engine @@ -166,24 +166,17 @@ Read `docs/netkingdom-security-infrastructure.md` before changing secret catalogs, OpenBao policies, auth roles, delivery modes, or ops-warden routing. Core rules: -- Layer: Engine / Lifecycle (`INTENT.md`, `layer.yaml`). PEP stance: - `pep-stance.yaml`. -- OpenBao is the custody, policy, lease, and audit backend (Tooling). -- secrets-engine is the Lifecycle API over that backend: catalog, decision - consumption, safe delivery, and non-secret evidence. It does not render - authorization decisions. -- `access-engine` (`flex-auth`) decides authorization; user-engine/key-cape own - identity and claims; `audit-core` owns evidence custody. +- OpenBao is the custody, policy, lease, and audit backend. +- secrets-engine owns workflow, catalog, decision checks, safe delivery, and + non-secret evidence. +- flex-auth decides authorization; user-engine/key-cape own identity and claims. - ops-warden routes non-SSH credential requests here and must not vend secrets. - SSH certificate issuance remains ops-warden's Staff PEP. - ops-bridge may consume scoped delivery for remote execution but must not store secret material. - info-tech-canon is the source for canonical terminology and stage/policy - concepts as they harden. gate-house owns the layer model. + concepts as they harden. - Never write raw secret values to Git, State Hub, chat, prompts, workplans, or normal logs. -- Never claim local evidence or a missing record proves occurrence or - non-occurrence. diff --git a/INTENT.md b/INTENT.md index b9af15c..12bda6b 100644 --- a/INTENT.md +++ b/INTENT.md @@ -1,78 +1,40 @@ ---- -layer: Engine -role: Lifecycle -standard: net-kingdom/canon/standards/security-layer-model_v0.7.md -companion: net-kingdom/SECURITY-COMPANION.md -declaration: layer.yaml -pep_stance: pep-stance.yaml -declared_at: "2026-08-29" ---- - # INTENT -> **Layer: Engine. Role: Lifecycle.** This is this repository's own declaration -> under the accepted NetKingdom Security Layer Model -> (`net-kingdom/canon/standards/security-layer-model_v0.7.md`) and its working -> companion (`net-kingdom/SECURITY-COMPANION.md`). The machine-readable form -> required by §11 is `layer.yaml`. A layer stated about this repository by -> another repository is not a declaration; this file, in this repository's -> voice, is. +> **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. > -> secrets-engine is a **deterministic API over OpenBao** for one modeled -> concept: the cataloged credential lane and its lifecycle. The same -> authoritative inputs — catalog entry, access-engine decision record, and -> OpenBao state — must yield the same plan, apply, delivery, and lifecycle -> result. That is why this repository is an Engine, not Staff, even though -> operators and agents invoke it at runtime. +> The layer rule that binds every repository: **Staff never touches tooling +> directly. It acts only through engine APIs.** > -> **It is not a decision point.** `access-engine` (today `flex-auth`) renders -> whether authority exists. gate-house authors the doctrine that may reach that -> decision only as a request claim or a versioned policy rule. OpenBao is -> Tooling: custody, policy enforcement, leases, and backend audit. This engine -> materializes an already-rendered decision into scoped OpenBao work. A -> credential is an artifact of authority, never its source. +> **This repository is Engine — deterministic API for a modeled concept.** Add the layer label. Credential custody, materialization, and lifecycle remain secrets-engine's. Two boundaries to make explicit: the decision that authority exists is access-engine's, and the doctrine governing when and how authority may be materialized is gate-house's. secrets-engine implements; it does not author the rules or render the decision. > -> **It is PEP-shaped.** Apply, provision, exec delivery, handoff, suspend, -> deactivate, and destroy cause protected side effects, so the four PEP -> obligations in §6.4 bind: no side effect without a decision record or a -> recorded stance; no replay of a verdict outside its binding and lifetime; -> a published unreachable-engine stance at `pep-stance.yaml`; reconstructability -> bounded by §9.6. Being PEP-shaped does not change the layer. -> -> **Proposed surfaces, not yet assented as shipped APIs.** ops-warden has -> declared its SSH-CA signing write as an engine gap with intended owner -> secrets-engine. kings-guard has proposed secret-use evidence against this -> engine. This repository **accepts both as proposed Lifecycle surfaces** and -> **declines operational SSH-lane stewardship**: `ops-warden` remains the Staff -> PEP that requests a certificate after a decision. What belongs here is the -> engine API those Staff repositories should call instead of OpenBao. Until -> those surfaces exist, the gaps stay proposed, not silently taken. -> -> **Finding on the §4 catalog cell.** The catalog row currently says this -> repository owns "credential abstraction, custody, lifecycle". Custody belongs -> to OpenBao. This engine owns the lifecycle API *over* that custody. The -> wording should not assign a Tooling property to an Engine. +> *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. -> See `SCOPE.md` for what is implemented today. ## One-liner -**secrets-engine is the Lifecycle engine for approved credential work: a -deterministic API that turns an access-engine decision into scoped OpenBao -custody, delivery, and lifecycle actions across build, test, and production.** +**secrets-engine is the secure interaction and automation layer for approved +secret custody, scoped OpenBao capability handoff, delivery, and lifecycle work +across build, test, and production, with OpenBao as the initial enforcement +backend.** ## Why This Exists Secret management is too important to be handled through scattered manual UI steps, copied commands, chat handoffs, and ad hoc token files. -OpenBao is the right Tooling for custody, policy, lease, and audit. It is not, -by itself, the right daily interaction surface for operators, agents, workloads, -and approval workflows. Direct use of OpenBao UI and CLI surfaces has already -produced avoidable friction: +OpenBao is the right place to enforce custody, policy, lease, and audit. It is +not, by itself, the right daily interaction surface for operators, agents, +workloads, and approval workflows. Direct use of OpenBao UI and CLI surfaces has +already produced avoidable friction: - approved decisions still require a human to bridge low-level OpenBao details; - agents and operators hit interface mismatches before reaching the security @@ -80,145 +42,111 @@ produced avoidable friction: - build, test, and production need different levels of privilege and ceremony; - secret values must never move through chat, Git, prompts, State Hub messages, or workplans; -- Staff repositories such as ops-warden need an Engine API for credential and - scoped-capability work, not a direct OpenBao client and not secret-vending - responsibility of their own. +- ops-warden needs a reliable front door for credential and scoped capability + use, not direct secret custody responsibility. -This repository exists to capture that complexity once, behind a stable, -deterministic, and auditable interface. +This repository exists to capture that complexity once, behind a stable and +auditable interface. ## The Mission -To provide a decision-aware Lifecycle engine that can: +To provide a decision-aware secrets workflow that can: -- consume an access-engine decision and refuse to proceed without one, or - record the application of a declared unreachable-engine stance in its place; -- translate that decision into narrowly scoped OpenBao changes or capability +- translate approved requests into narrowly scoped OpenBao changes or capability grants; - operate with distinct build, test, and production privilege layers; - deliver secrets and scoped capabilities to commands and workloads without printing or storing raw values in coordination systems; -- emit non-secret evidence under the §9.6 bound — the archive proves records - it holds were not altered or truncated after arrival, not that every event - was sent, and not that absence means non-occurrence; +- record enough non-secret evidence for review, audit, and troubleshooting; - make routine secure work fast enough that operators and agents actually use - the Engine path instead of touching OpenBao. + the secure path. ## Core Principles -### 1. Decision elsewhere, materialization here +### 1. Decision First -Secret establishment, access, rotation, and deactivation start from an -access-engine decision record naming the request it was rendered for. -This engine may automate the work after that decision; it does not render, -cache, or substitute one. Compiled catalog data that would determine an -outcome before the engine runs is still deciding, and is forbidden. +Secret establishment, access, rotation, and deactivation start from an explicit +request and decision. The engine may automate the work after approval; it does +not silently create new secret authority. -### 2. OpenBao is Tooling; this engine is the API over it +### 2. OpenBao Enforces, secrets-engine Orchestrates -OpenBao remains the vault, policy, lease, and audit backend. secrets-engine -owns the catalog, validation, delivery, scoped capability handoff, lifecycle -operations, and the operator/agent/Staff interface to those operations. Staff -must not hold a direct OpenBao client for work this engine is meant to expose. +OpenBao remains the vault, policy, lease, and audit backend. secrets-engine owns +the workflow, catalog, validation, delivery, scoped capability handoff, and +operator/agent interface. -### 3. Stage-aware privilege +### 3. Stage-aware Privilege Build, test, and production are separate security contexts. Each has its own OpenBao role, policy boundary, approval expectation, TTL limits, and delivery -rules. Stage is the current equivalent scope for the PEP stance map until -security-zone membership is consumed as a claim. +rules. -### 4. No secret values in coordination surfaces +### 4. No Secret Values in Coordination Surfaces Git, State Hub, workplans, chat, prompts, issue comments, and normal logs carry only non-secret metadata. Raw values are delivered through OpenBao, wrapped responses, local exec-time injection, or short-lived bootstrap files with strict permissions. -### 5. Least friction without broad power +### 5. Least Friction Without Broad Power -The common path should be one clear command or Engine API call. That ease must -not require handing platform-root or platform-admin power to routine -automation, and it must not mint standing credentials for agents. Agent -authority is per task, time-bounded, and attributable to the principal the -agent acts for. +The common path should be one clear command or review action. That ease must not +require handing platform-root or platform-admin power to routine automation. -### 6. Bootstrap honestly, then harden +### 6. Bootstrap Honestly, Then Harden During infrastructure setup it is acceptable to use temporary root-created OpenBao credentials stored outside repositories with mode 0600 and explicit -revocation. Those bootstrap shortcuts must be tracked as temporary, must never -become an implicit fallback, and must be replaced by narrower service identity -bound to OpenBao. +revocation. Those bootstrap shortcuts must be tracked as temporary and replaced +by narrower auth roles. -### 7. Prefer exec-time delivery +### 7. Prefer Exec-time Delivery When a workload or operator command needs a secret, the default delivery mode is -process-local injection for the duration of that command. Every such allow has -an explicit lifetime — a TTL, or a binding to a session that ends. The engine -should make this easier than copying or inspecting a secret value. +process-local injection for the duration of that command. The engine should make +this easier than copying or inspecting a secret value. -### 8. Evidence is bounded, not omniscient +### 8. Auditable and Reversible Every apply, read, delivery, lease, verification, revocation, and deactivation -emits non-secret evidence. Load-bearing events — those a control would be -unsound without — require atomic local emission and a declared cadence or -heartbeat. Attributive events should seek atomicity; where they trade it away, -the trade is declared and completeness is not claimed. Evidence belongs in -`audit-core`. Local JSONL and State Hub notes are not a substitute for that -engine. - -### 9. Adaptive systems may only tighten - -This engine may shorten a lifetime, refuse a delivery, suspend a lane, or -require a fresh decision. It must not manufacture authority, widen a grant, or -treat silence from `access-engine` as permission. +has non-secret evidence that can be reviewed later. ## What This Is secrets-engine is: -- the Lifecycle engine for cataloged credential lanes; -- a CLI today and a service API for operators, agents, CI, and Staff - repositories that must not speak OpenBao; +- a workflow layer for approved secret changes, scoped capability grants, and + access; +- a CLI and service API for operators, agents, and automation; - a typed catalog of KV secret lanes, auth-capability lanes, grants, delivery modes, and stage policies; - an OpenBao policy/auth-role applier with strict local validation; - an exec-time secret delivery and scoped capability handoff helper; -- a PEP at every protected side effect, with a published unreachable-engine - stance; -- the credential interaction surface that ops-warden routes to after a - decision. +- an audit evidence writer for State Hub and local logs; +- the credential interaction surface that ops-warden can route to. ## What This Is Not secrets-engine is not: -- a replacement for OpenBao, or the owner of custody; +- a replacement for OpenBao; - an identity provider or MFA system; -- an authorization decision engine, an approval store, or a maturity engine; +- an authorization decision engine; - an application-specific secret store; - a place to persist raw secret values outside OpenBao; - a prompt-time secret injection mechanism for LLMs; -- a bypass around review, approval, or production custody; -- the operational owner of the SSH certificate lane — that Staff PEP remains - ops-warden's; this engine may later expose the signing API it should call; -- an observation or containment system — kings-guard proposes, this engine - does not watch production, and actuation is unowned estate-wide. +- a bypass around review, approval, or production custody. ## System Boundary | Concern | Primary owner | secrets-engine responsibility | | --- | --- | --- | -| Secret custody, leases, backend audit | OpenBao (Tooling) / railiance-platform | Lifecycle API over least-privilege roles and validated paths. | -| Authorization decisions | access-engine (`flex-auth`) | Consume a decision record; never render or cache one. | -| Approval object | approval-engine | Consume an approval as an input claim; never store or mutate it. | -| Security doctrine | gate-house | Apply doctrine only as it arrives on the decision or in a versioned policy package. | +| Secret custody, leases, audit backend | OpenBao / railiance-platform | Use through least-privilege roles and validated paths. | | Human and service identity | key-cape / user-engine | Consume identity claims; do not replace identity lifecycle. | -| SSH certificate issuance (Staff PEP) | ops-warden | Route and, when the surface exists, expose the OpenBao SSH-CA API; do not take the lane. | -| Evidence custody and integrity | audit-core | Emit non-secret events under the §9.6 bound; do not become the archive. | +| Authorization decisions | flex-auth / State Hub decision model | Require and verify decisions before privileged actions. | +| SSH certificate issuance | ops-warden | Provide routed credential/capability access; do not make ops-warden vend secrets. | | Workload secret consumption | Workload repos / CI / runtime | Provide safe delivery and handoff contracts plus catalog entries. | -| Request history and progress | State Hub | Write non-secret progress pointers only; not a security state plane. | +| Request history and progress | State Hub | Write non-secret evidence and decision links only. | ## Direction of Evolution @@ -233,16 +161,10 @@ The repository should evolve through these phases: 4. **Hardening:** replace bootstrap token files with OIDC/service auth, wrapped delivery, short leases, dual control for production provisioning, and routine rotation/deactivation workflows. -5. **Layer-model conformance:** published PEP stance equals shipped behaviour; - live side effects hold an access-engine decision record or a recorded stance; - evidence classified and emitted to audit-core; proposed SSH-CA and secret-use - surfaces accepted or declined in this repository's own contract, not only in - other repositories' gap registers. -6. **Service mode:** expose a deterministic API that ops-warden, agents, CI, and - future UI surfaces can use without knowing OpenBao internals. +5. **Service mode:** expose an API that ops-warden, agents, CI, and future UI + surfaces can use without knowing OpenBao internals. ## Guiding Question **How can approved secret work become low-friction for humans and agents while -keeping raw values, OpenBao privileges, and production impact tightly bounded -to a decision this engine did not render?** +keeping raw values, OpenBao privileges, and production impact tightly bounded?** diff --git a/README.md b/README.md index cc7beda..bbf0571 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,15 @@ Headless, multi-application, multi-tenant secrets workflow and automation layer for approved secret custody, delivery, and lifecycle work across build, test, and production stages. -**Layer: Engine / Lifecycle** under the accepted NetKingdom Security Layer -Model (`layer.yaml`). OpenBao remains the custody and enforcement backend. -`secrets-engine` is the deterministic API over it: catalog, decision -consumption, plan/apply, guarded provisioning, verification, delivery, -evidence, lifecycle metadata, and native-access deactivation. It does not -render authorization decisions. Local evidence can be inspected through an +OpenBao remains the custody and enforcement backend. `secrets-engine` owns the +operator and agent interaction model: catalog, decision checks, plan/apply, +guarded provisioning, verification, delivery, evidence, lifecycle metadata, and +native-access deactivation. Local evidence can be inspected through an allowlisted per-lane `audit` summary without exposing record detail. ## Start Here -- [INTENT.md](INTENT.md) - why this repository exists, including the Engine / - Lifecycle declaration. -- [layer.yaml](layer.yaml) - machine-readable layer declaration and proposed - surfaces. +- [INTENT.md](INTENT.md) - why this repository exists. - [ProductRequirementsDocument.md](ProductRequirementsDocument.md) - product requirements and MVP scope. - [NetKingdom security infrastructure boundary pointer](docs/netkingdom-security-infrastructure.md) diff --git a/SCOPE.md b/SCOPE.md index 9cd589a..104400a 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -3,20 +3,15 @@ > Implemented capability boundary for agents and contributors. Aspirational > direction belongs in `INTENT.md`; current work and operational gates belong in > `workplans/`. -> -> **Layer declaration (accepted model v0.7):** Engine / Lifecycle. Machine-readable -> form: `layer.yaml`. PEP stance: `pep-stance.yaml`. This file states what the -> implementation currently does inside that layer; it does not restate doctrine. ## One-liner -secrets-engine is the Lifecycle engine for cataloged credential work: a -decision-gated Python CLI that validates non-secret lane metadata and -orchestrates narrowly scoped OpenBao policy, AppRole, provisioning, -verification, delivery, routing, handoff, and revocation actions. +secrets-engine is a decision-gated Python CLI that validates non-secret secret +lane metadata and orchestrates narrowly scoped OpenBao policy, AppRole, +provisioning, verification, delivery, routing, handoff, and revocation actions. -It is a deterministic API over OpenBao, not a vault, not a policy decision -point, not an identity provider, and not a general secrets API. +It is an OpenBao workflow client, not a vault, authorization service, identity +provider, credential broker, or general secrets API. ## Implemented Capabilities @@ -38,19 +33,15 @@ point, not an identity provider, and not a general secrets API. - Resolves a catalog lane by catalog id or `approval.decision_ref`. - Resolves legacy lane decisions from State Hub by id, with tracked local YAML - mirrors retained only for non-production and explicit throwaway demos. This is - not an access-engine decision record. -- Fails every production live command closed while there is no durable - access-engine / ActionAuthorization record. That fail-closed row is the - published unreachable-engine stance for `prod` in `pep-stance.yaml`. A local - mirror can unlock a prod-labeled lane only when an explicit unsafe-demo - switch, disabled Hub URL, and loopback OpenBao target are all present. Plans - and `apply --dry-run` remain usable. + mirrors retained only for non-production and explicit throwaway demos. +- Fails every production live command closed while State Hub lacks the durable + action-authorization endpoint. A local mirror can unlock a prod-labeled lane + only when an explicit unsafe-demo switch, disabled Hub URL, and loopback + OpenBao target are all present. Plans and `apply --dry-run` remain usable. - Builds and validates the flex-auth `ActionAuthorization` profile, including exact lane/stage/action/target/actor/purpose matching, bounded validity, State Hub authority, request digest and decision binding, accepted policy package/version, and an independently required distinct-approver threshold. - Validation is consume-only; this process does not evaluate policy. - Renders guarded OpenBao plans for exact consumer ACL policies and AppRoles. - Applies policy and AppRole metadata idempotently. Existing mounts render a non-mutating check and are never created by apply. @@ -125,8 +116,7 @@ cannot be recovered through that AppRole. event on a best-effort basis. Posts carry stable idempotency/source headers. Each requested State Hub delivery receives an append-only local `delivered`, `queued`, `failed`, or `skipped-no-topic` companion record; edge-relay queued - receipts retain only the non-secret outbox id. This trail is attributive: - completeness is not claimed, and it is not `audit-core`. + receipts retain only the non-secret outbox id. - Every live privileged CLI handler records an attempt before lane-approval resolution and a terminal success, verification failure, rejection, interruption, or typed backend/input failure. Failure evidence contains the @@ -221,11 +211,9 @@ verification, `approle-login` is auth-capability handoff metadata, and - First-class rotate, compromise, reactivate, lease-status, or audit report commands; lifecycle operations currently execute plans without persistent lane state. -- Resolution of a durable access-engine decision record / State Hub - ActionAuthorization and wiring its validated approval threshold to each - production handler. The consumer validator exists; the serving endpoint does - not, so live production remains fail-closed. -- Direct access-engine evaluation, JWT signature verification, or identity +- Resolution of a durable State Hub action authorization and wiring its + validated approval threshold to each production handler. +- Direct flex-auth evaluation, JWT signature verification, or identity authentication. KeyCape claims receive only a consumer preflight; OpenBao is responsible for cryptographic JWT validation. - Runtime tenancy isolation; `org`, `repo`, consumers, and stages are catalog @@ -233,55 +221,32 @@ verification, `approle-login` is auth-capability handoff metadata, and - Management or health verification of ESO, Kubernetes Secrets, deployments, provider accounts, SSH issuance, tunnels, or remote transport. - Any backend other than the local `bao`/`vault` CLI speaking to OpenBao. -- An SSH-CA signing engine API. ops-warden still signs through its declared - OpenBao gap; this repository has accepted that surface as proposed only. -- A secret-use evidence engine API for kings-guard. `route` and `audit` are - operator summaries over local JSONL, not an observation surface. -- Emission to `audit-core`. Evidence today is local JSONL plus best-effort - State Hub progress notes, classified attributive, completeness not claimed. -- Named stance-application records (stage, failure mode, decision id present - only where rendered). Fail-closed production currently surfaces as a - `DecisionError` on the privileged-evidence path. -- Security-zone membership as a request claim. PEP scope is catalog stage. ## System Boundary -- **OpenBao / railiance-platform** (Tooling) owns custody, policy enforcement, - leases, and backend audit. secrets-engine is the Lifecycle API over it. -- **access-engine (`flex-auth`)** owns authorization decisions. secrets-engine - consumes a decision record or applies its published unreachable-engine stance; - it does not evaluate policy. -- **approval-engine** owns the durable approval object. secrets-engine may - consume an approval as an input claim and must not store or mutate one. -- **gate-house** owns security doctrine. Doctrine reaches this engine only as it - already reached the decision, never as a side channel. -- **audit-core** owns evidence custody and integrity. Local JSONL and State Hub - notes are not that archive. +- **OpenBao / railiance-platform** owns custody, policy enforcement, leases, and + audit. secrets-engine invokes it through supplied credentials. +- **flex-auth / State Hub decisions** own authorization. secrets-engine only + resolves and enforces recorded decision status. - **user-engine / key-cape** own identity, OIDC, MFA, and claims. -- **ops-warden** issues SSH certificates (Staff PEP) and routes non-SSH - credential needs here; it does not vend their values. The SSH-CA write - surface is a proposed engine API, not a transferred lane. +- **ops-warden** issues SSH certificates and routes non-SSH credential needs; it + does not vend their values. - **ops-bridge** owns tunnels and remote execution transport and may consume a scoped delivery path. - **workload/platform repositories** own ESO/Kubernetes delivery, provider rotation, and application health. - **info-tech-canon / net-kingdom** own canonical terminology and the - cross-system security boundary. **gate-house** owns the layer model. + cross-system security boundary. Canonical boundary: `net-kingdom/docs/secrets-engine-security-infrastructure-boundary.md`. -Layer model: `net-kingdom/canon/standards/security-layer-model_v0.7.md`. -Working companion: `net-kingdom/SECURITY-COMPANION.md`. ## Security Rules - Never place raw secret values in Git, State Hub, chat, prompts, workplans, normal logs, or evidence. -- Never render or cache an authorization decision. Catalog admission, a dry-run, - an old workload CCR, or a local fixture is not an access-engine allow. -- Never treat silence from `access-engine` as permission. Production live - actions fail closed; any unreachable-engine residue must be the published - stance, recorded, never implicit. +- Never treat catalog admission, a dry-run, or an old workload CCR as approval + for a new production auth surface. - Never mutate an existing shared mount or replace workload delivery by implication. - Never add KV destruction back to ordinary `revoke`; irreversible custody @@ -289,22 +254,7 @@ Working companion: `net-kingdom/SECURITY-COMPANION.md`. approved action. The live path remains disabled until that approval contract exists. - Keep bootstrap and handoff material outside repositories with mode 0600 and - explicit expiry/revocation handling. Bootstrap is not an implicit fallback - from service identity. -- Never claim that local evidence or a missing record proves occurrence or - non-occurrence. Completeness is not claimed. - -## Layer-model obligations (current vs intended) - -| Obligation | Current | Intended | -| --- | --- | --- | -| Layer declaration | `INTENT.md` frontmatter + `layer.yaml` | Keep in this repository's own voice | -| One decision point | Consumer validator for ActionAuthorization; production live fail-closed | Consume an access-engine decision record before every protected side effect | -| PEP stance | `pep-stance.yaml`; prod fail-closed, build/test fail-open relative to access-engine | Published map equals shipped behaviour; stance application recorded by name | -| Evidence bound | Attributive local JSONL + best-effort State Hub | Load-bearing vs attributive classified; load-bearing to `audit-core` with cadence | -| SSH-CA surface | Proposed; not shipped | Engine API for ops-warden's Staff PEP; lane stewardship stays with ops-warden | -| Secret-use evidence | `route` / `audit` over local JSONL | Engine surface of lease/revocation/mount/rotation metadata | -| Agent credential | Bootstrap token file still accepted; KeyCape scaffold unwired | Per-task, time-bounded service identity; no standing engine credential | + explicit expiry/revocation handling. ## Where Current Work Lives @@ -312,7 +262,6 @@ Working companion: `net-kingdom/SECURITY-COMPANION.md`. - `docs/hardening-backlog.md` tracks exit from bootstrap mode. - `history/` contains dated capability and intent assessments. - `INTENT.md` remains the stable aspirational direction. -- `layer.yaml` and `pep-stance.yaml` are the layer-model declaration surface. ## Provided Capabilities @@ -349,7 +298,7 @@ title: Non-secret routing and evidence pointers description: Reports decision/readiness metadata and records scrubbed local and best-effort State Hub evidence without returning secret values. Append-only delivery receipts expose State Hub failures, and an allowlisted audit command summarizes lane operations and cleanup. KV verification can attest a - supplied real unrelated identity, but the engine does not own identity selection. This is attributive - local evidence, not audit-core and not a secret-use observation API. + supplied real unrelated identity, but the engine does not own identity selection. This is not a durable + remote audit store. keywords: [routing, evidence, state-hub, audit, secrets] ``` diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 035f3ae..5bf81c8 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -8,16 +8,16 @@ | Kind | ID | Status | Lane | Source | | --- | --- | --- | --- | --- | -| workplan | SECRETS-WP-ADHOC-2026-08-21 | finished | — | workplans/ADHOC-2026-08-21.md | -| workplan | SECRETS-WP-ADHOC-2026-08-23 | finished | — | workplans/ADHOC-2026-08-23.md | +| workplan | ADHOC-2026-08-21 | finished | — | workplans/ADHOC-2026-08-21.md | +| workplan | ADHOC-2026-08-23 | finished | — | workplans/ADHOC-2026-08-23.md | | workplan | SECRETS-WP-0001 | finished | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md | | workplan | SECRETS-WP-0002 | finished | — | workplans/SECRETS-WP-0002-bootstrap.md | | workplan | SECRETS-WP-0004 | finished | — | workplans/SECRETS-WP-0004-warden-sign-token-lane.md | | workplan | SECRETS-WP-0005 | finished | — | workplans/SECRETS-WP-0005-scope-intent-value-gaps.md | | workplan | SECRETS-WP-0006 | active | — | workplans/SECRETS-WP-0006-catalog-lane-adoption.md | | workplan | SECRETS-WP-0007 | active | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md | -| task | SECRETS-WP-ADHOC-2026-08-21-T01 | done | — | workplans/ADHOC-2026-08-21.md | -| task | SECRETS-WP-ADHOC-2026-08-23-T01 | done | — | workplans/ADHOC-2026-08-23.md | +| task | ADHOC-2026-08-21-T01 | done | — | workplans/ADHOC-2026-08-21.md | +| task | ADHOC-2026-08-23-T01 | done | — | workplans/ADHOC-2026-08-23.md | | task | SECRETS-WP-0001-T01 | done | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md | | task | SECRETS-WP-0001-T02 | done | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md | | task | SECRETS-WP-0001-T03 | done | — | workplans/SECRETS-WP-0001-statehub-bootstrap.md | @@ -56,4 +56,3 @@ | task | SECRETS-WP-0007-T05 | progress | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md | | task | SECRETS-WP-0007-T06 | progress | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md | | task | SECRETS-WP-0007-T07 | wait | — | workplans/SECRETS-WP-0007-production-lifecycle-hardening.md | -| intake | SECRETS-IN-0001 | open | — | intakes/intakes.md | diff --git a/docs/netkingdom-security-infrastructure.md b/docs/netkingdom-security-infrastructure.md index 02aad6b..abd048f 100644 --- a/docs/netkingdom-security-infrastructure.md +++ b/docs/netkingdom-security-infrastructure.md @@ -17,14 +17,6 @@ belongs to NetKingdom because it defines cross-system security infrastructure responsibilities and boundaries across OpenBao, flex-auth, user-engine/key-cape, ops-warden, ops-bridge, info-tech-canon, State Hub, and agents. -The accepted layer model and working companion, which this repository now -declares against as Engine / Lifecycle, live at: - -```text -net-kingdom/canon/standards/security-layer-model_v0.7.md -net-kingdom/SECURITY-COMPANION.md -``` - secrets-engine consumes that boundary and implements the secrets workflow, catalog, stage policies, OpenBao apply/delivery mechanics, and evidence model that the canonical document assigns to it. diff --git a/history/2026-08-29-layer-model-scope-intent-review.md b/history/2026-08-29-layer-model-scope-intent-review.md deleted file mode 100644 index 0e14ba1..0000000 --- a/history/2026-08-29-layer-model-scope-intent-review.md +++ /dev/null @@ -1,163 +0,0 @@ -# Review: Security layer model v0.7 vs INTENT, SCOPE, and implementation - -Date: 2026-08-29 -Author: grok -Standard: `net-kingdom/canon/standards/security-layer-model_v0.7.md` (accepted) -Companion: `net-kingdom/SECURITY-COMPANION.md` v0.2 -Compared: adapted `INTENT.md`, updated `SCOPE.md`, current CLI implementation -Intake closed by this session: `SECRETS-IN-0001` - -## Executive assessment - -The accepted layer model confirms what this repository already was in practice -and had not yet declared in its own voice: **Engine, role Lifecycle** — a -deterministic API over OpenBao for cataloged credential lanes. The gate-house -review note at the top of `INTENT.md` was not a declaration. That note is now -replaced by frontmatter, prose, `layer.yaml`, and a published PEP stance map. - -The intent remains directionally sound. The layer model does not move the -system boundary (OpenBao stays Tooling; `access-engine` stays the only PDP; -ops-warden stays the SSH Staff PEP). It **tightens the contract** this engine -owes at every protected side effect, and it **names two proposed surfaces** -this repository had not answered: an SSH-CA signing API, and secret-use -evidence for kings-guard. - -`SCOPE.md` and the implementation already describe a guarded OpenBao CLI MVP -with one proven native workload pilot. Against the new intent they are -**aligned on layer and ownership, behind on PEP, decision, and evidence -obligations**. Several of those obligations overlap active work in -`SECRETS-WP-0007`; the new work is the layer-model remainder, not a restart of -CLI hardening. - -A finding is raised against the §4 catalog cell, not against the proposed -layer: the catalog says this repository owns "custody". Custody is OpenBao's. -This engine owns the lifecycle API over that custody. - -## What was declared - -| Artifact | Content | -| --- | --- | -| `INTENT.md` frontmatter | `layer: Engine`, `role: Lifecycle` | -| `INTENT.md` body | Own-voice declaration, PEP shape, evidence bound, proposed surfaces | -| `layer.yaml` | Machine-readable declaration; owned OpenBao contact; no decision surface | -| `pep-stance.yaml` | Unreachable-engine stance by catalog stage; prod fail-closed | -| `scripts/check_layer_conformance.py` | Mechanical check of the above | -| `tests/test_layer_conformance.py` | Declaration + published map equals shipped prod fail-closed | - -Contest of the proposed **layer** (Engine): none. The determinism test holds. - -Contest of **catalog wording**: yes — "custody" should not appear as this -engine's owned property. - -Answers to proposed ownerships: - -| Proposed surface | Answer | -| --- | --- | -| SSH-CA signing write (`ops-warden` §5.3 gap) | **Accept** as a Lifecycle engine API. **Decline** operational SSH-lane stewardship. ops-warden remains the Staff PEP. | -| Secret-use evidence (`kings-guard`) | **Accept** as a proposed Lifecycle engine surface, bounded by §9.6. Not shipped. | - -## INTENT vs SCOPE - -No contradiction on layer, owners, or forbidden claims. Drift is maturity: - -| Intent claim | SCOPE today | Gap kind | -| --- | --- | --- | -| Engine / Lifecycle declaration | Declared | Closed this session | -| Consume an access-engine decision before every protected side effect | Consumer validator exists; production live fail-closed because the durable object is not served | Implementation (WP-0007-T04 + WP-0008-T02) | -| Published PEP stance equals shipped behaviour, stance application recorded | Map published; prod fail-closed matches; records are `DecisionError`, not named stance fields | Implementation (WP-0008-T01) | -| Evidence to `audit-core` under §9.6 | Local JSONL + best-effort State Hub; attributive; completeness not claimed | Implementation (WP-0008-T03) | -| SSH-CA engine API | Not implemented; ops-warden still writes OpenBao | Proposed surface (WP-0008-T04) | -| Secret-use evidence API | `route` / `audit` summarize local JSONL | Proposed surface (WP-0008-T05) | -| No standing agent credential | Bootstrap token file still accepted; KeyCape scaffold unwired to OpenBao | Hardening (WP-0007-T05 / WP-0008-T06) | -| Service API | CLI only | Deferred until WP-0007 exit criteria | -| Every allow has a lifetime | Delivery sessions self-revoke; decision TTL waits on durable authorization | Partial | -| Doctrine as claims | Catalog/policies are local YAML | Later; do not compile outcomes here | - -`SCOPE.md` remains the implemented-capability ledger. Volatile workplan status -stays out of it. The new durable additions are the layer table, the updated -system boundary, and the explicit not-implemented layer-model surfaces. - -## INTENT vs implementation - -The strongest implemented slice is unchanged: - -```text -catalog -> approval-status or fail-closed prod gate -> guarded plan - -> policy/AppRole apply -> bounded presence/capability verification - -> CLI-local exec/handoff -> non-secret route/evidence pointer -``` - -That slice is a Lifecycle engine **shape**. It is not yet a conforming PEP. - -### Scorecard against the accepted model - -| Rule | Assessment | Evidence | Material gap | -| --- | --- | --- | --- | -| §3.3 Lifecycle | Aligned in shape | OpenBao isolated in `openbao.py`; catalog/plan/delivery/lifecycle are the API | Service API absent; CLI is the only surface | -| §6 one decision point | Partial, fail-closed | `authorization.py` validates a supplied object and does not evaluate policy. Production live commands refuse without a durable ActionAuthorization. Build/test still accept State Hub status or a local fixture | Local/hub status is not an access-engine decision record. Risk of compiled early decision if fixtures return for production | -| §6.1 compiled data | Watch | Catalog admission and publication-scope policy clamp lanes; they must not become the allow | Keep catalog descriptive; effects stay in the decision | -| §6.4 PEP obligation 1 | Partial | Privileged handlers record attempt/terminal evidence; production refusal is a `DecisionError` | Stance application is not recorded as named fields (stage, failure mode, decision id present only where rendered) | -| §6.4 PEP obligation 2 | Aligned as declared | No verdict cache; `verdict_caching: none` | Must remain none when ActionAuthorization is live | -| §6.4 PEP obligation 3 | Published, not yet loaded | `pep-stance.yaml` is total over build/test/prod/unknown; tests pin prod fail-closed to `_require_lane_approval` | Code still hardcodes the gate rather than loading the map. Build/test fail-open is relative to access-engine, not a manufactured allow — that residue is itself a gap | -| §6.4 PEP obligation 4 | Attributive | Local JSONL + hub receipts; OpenBao remains backend audit | Completeness not claimed (correct). No `audit-core` emit | -| §9.6 load-bearing vs attributive | Undeclared in code | `layer.yaml` classifies today's trail as attributive | Destroy, revoke, and dual-control provision will become load-bearing once they are production-live. They need atomic local outbox + cadence/heartbeat **before** any control branches on them | -| §9.7 lifetime | Partial | AppRole sessions self-revoke; ActionAuthorization has `expires_at` in the consumer schema | Production never reaches the lifetime because live prod is disabled. Bootstrap tokens remain standing credentials | -| §20.2 credentials after a decision | Principle aligned; production blocked | Fail-closed is the honest residue while the PDP object is unserved | Do not reopen production on hub status | -| Staff must not touch OpenBao | External | ops-warden still has the SSH-CA write gap with intended owner here | Engine surface not shipped; do not take the Staff lane | -| Agent rule 1 (no standing credential) | Not met | `--bootstrap-token-file` and `BAO_TOKEN` are the live auth path | KeyCape scaffold exists and is deliberately unwired | -| Observation / actuation | Correctly not claimed | Companion §10: nothing is observed in production; nothing can be contained automatically | Do not plan as if either existed | - -### Overlap with active workplans - -`SECRETS-WP-0007` already owns CLI hardening that the 2026-08-23 assessment -called P0/P1: merge-safe provision (done), lifecycle split (in progress), -exact-action authorization (in progress), session self-revocation (in -progress), verification/evidence (in progress). Those tasks stay there. - -What the layer model **adds**, and what `SECRETS-WP-0008` is for: - -1. Bind the published PEP stance to shipped behaviour and record stance - application by name. -2. Treat the durable ActionAuthorization as an **access-engine decision - record** under §6.4, not as a State Hub status check, once WP-0007-T04's - endpoint exists. -3. Classify evidence and emit it to `audit-core`. -4. Answer the two proposed surfaces (SSH-CA API, secret-use evidence) in this - repository's own contract. -5. Retire standing engine credentials as a layer obligation, not only as - bootstrap hygiene. - -Do not start service mode, runtime tenancy, or a second custody backend from -this review. Companion §10 still holds: nothing is observed in production, and -nothing can be contained automatically. - -## Recommended sequence - -1. Keep `SECRETS-WP-0007` as the CLI-hardening track. Do not reopen live - production on coarse hub status. -2. Land `SECRETS-WP-0008-T01` — load `pep-stance.yaml` as the source of the - fail-closed gate and record stance application fields. -3. When the durable authorization object is served, consume it as a decision - record (`WP-0008-T02` / `WP-0007-T04`) with digest+lifetime replay rules. -4. Classify evidence; emit load-bearing classes to `audit-core` with a local - outbox; declare cadence or heartbeat. -5. Publish the SSH-CA engine contract with ops-warden before writing a client - replacement. Keep their Staff PEP. -6. Publish the secret-use evidence contract with kings-guard. `audit` is not - that contract. -7. Wire service identity only after the platform-owned OpenBao JWT role exists. - Bootstrap remains break-glass. - -## Conclusion - -`INTENT.md` now declares Engine / Lifecycle in this repository's own voice and -accepts the two proposed surfaces without taking Staff work. `SCOPE.md` is -honest about the CLI MVP and the new not-implemented obligations. The -implementation is a Lifecycle engine in shape and a partial PEP in behaviour: -production fail-closed is the correct residue, build/test still run on a -pre-decision-record approval path, and evidence is attributive local notes -rather than `audit-core`. - -The repository should not advertise itself as a service API, a second PDP, a -custody backend, an SSH issuer, or an observation system. Follow-on work is -`SECRETS-WP-0008`. diff --git a/intakes/intakes.md b/intakes/intakes.md index bd5a3af..f6dc6ab 100644 --- a/intakes/intakes.md +++ b/intakes/intakes.md @@ -7,21 +7,13 @@ id: SECRETS-IN-0001 kind: intake title: 'Declaration requested: state this repository''s layer in INTENT.md (security layer model §11)' -status: closed +status: open origin: cross-repo -origin_ref: net-kingdom security-layer-model_v0.7 §11 +origin_ref: net-kingdom security-layer-model_v0.4 §11 priority: low owner: secrets-engine requested_by: gate-house proposed_layer: Engine -resolution: >- - Declared Engine / Lifecycle in INTENT.md frontmatter and layer.yaml on - 2026-08-29 against accepted v0.7. Layer not contested. Catalog cell - "custody" is a finding: OpenBao owns custody; this engine owns the - lifecycle API over it. SSH-CA signing accepted as a proposed engine API - and declined as a Staff lane. Secret-use evidence accepted as proposed. - Review: history/2026-08-29-layer-model-scope-intent-review.md. - Workplan: SECRETS-WP-0008. description: 'A conformance sweep on 2026-08-28 found this repository has no layer declaration of its own. It carries a layering review note gate-house wrote into the top of its INTENT.md on 2026-08-24, and that note names a layer — but the words diff --git a/layer.yaml b/layer.yaml deleted file mode 100644 index a60d205..0000000 --- a/layer.yaml +++ /dev/null @@ -1,144 +0,0 @@ -# secrets-engine — NetKingdom security layer declaration -# -# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md -# Companion: net-kingdom/SECURITY-COMPANION.md -# Voice: INTENT.md (this repository's own declaration, §11) -# Validate: python3 scripts/check_layer_conformance.py -# -# §11 requires a machine-readable form because prose cannot distinguish a -# declaration from a transcribed review. ops-warden offered the layer.yaml -# shape estate-wide; this file is that shape adapted for a Lifecycle engine -# rather than a Staff repository. -# -# §5 (Staff never touches Tooling) does not apply to this repository's OpenBao -# client. A Lifecycle engine is the deterministic API over Tooling it fronts. -# OpenBao contact here is owned, not a §5.3 gap. - -schema_version: "0.1" -framework: netkingdom-security-layer-model -standard_version: "0.7" -repository: secrets-engine -layer: engine -role: lifecycle -declared_by: INTENT.md -declared_at: "2026-08-29" - -# §3.3: a Lifecycle engine is a deterministic API over Tooling it owns. -# OpenBao remains the vault; this engine fronts it. -owned_tooling: - - id: openbao-lifecycle - tooling: OpenBao - modules: - - src/secrets_engine/openbao.py - operations: - - "bao policy/auth/kv subprocess adapter" - - "CAS-aware KV create/patch via JSON input files, never argv values" - note: >- - This is the owned Lifecycle contact, not a Staff §5 shape. A new direct - OpenBao client outside the listed modules is a finding. - -# §6: no repository other than access-engine exposes an authorization decision -# surface. secrets-engine consumes and validates a supplied ActionAuthorization -# object; it does not evaluate policy. -decision_surfaces_exposed: none - -# §6.4 — apply, provision, exec, handoff, revoke, and lifecycle mutations -# cause protected side effects. Being PEP-shaped does not change the layer. -pep_shaped: true -pep_stance: pep-stance.yaml -protected_actions: - - apply - - provision - - verify - - exec - - handoff - - revoke - - lifecycle-suspend - - lifecycle-deactivate - - lifecycle-destroy - -# §13 proposed capabilities. Owner status is proposed, not assented, until -# the surface exists in this repository's own contract. -proposed_capabilities: - - - id: ssh-ca-signing-surface - state: unowned-capability - capability: >- - Deterministic engine API for OpenBao SSH-CA signing, so a Staff PEP - (ops-warden) does not hold a direct VaultCA write. - intended_owner: secrets-engine - owner_status: proposed - blocked_on: >- - No engine API exists yet. ops-warden continues to sign through its - declared §5.3 gap. This repository accepts the engine surface and - declines operational ownership of the SSH access lane. - review: "2026-11-28" - consequence: >- - ops-warden's ssh-ca-signing-write gap remains the production path. - - - id: secret-use-evidence - state: unowned-capability - capability: >- - Lease, revocation, mount, rotation, and delivery-session metadata - exposed as an engine surface for kings-guard secret-abuse posture. - intended_owner: secrets-engine - owner_status: proposed - blocked_on: >- - Local JSONL plus best-effort State Hub notes exist; they are not an - engine API and they are not audit-core. Completeness is not claimed. - review: "2026-11-28" - consequence: >- - kings-guard secret-abuse posture stays fixture-driven. - -# Contacts that are not Tooling, recorded so the check is total. -non_tooling_clients: - - - id: state-hub-decisions-and-progress - target: state-hub - layer: not-catalogued - modules: - - src/secrets_engine/decisions.py - - src/secrets_engine/evidence.py - operation: "HTTP GET /decisions/{id} and POST /progress/" - write: true - note: >- - Outside §5 by the scope rule: State Hub is not a §4 Tooling row. - Decisions consumed from it are a gap relative to access-engine, not a - Tooling contact. Progress notes are not a security state plane. - - - id: access-engine-action-authorization - target: access-engine (flex-auth) - layer: engine - modules: - - src/secrets_engine/authorization.py - operation: "Local validation of a supplied ActionAuthorization object" - write: false - note: >- - Engine-to-engine consume, not a decision surface. The durable State Hub - endpoint that would serve the object is not yet available; production - live actions therefore fail closed (see pep-stance.yaml). - - - id: key-cape-service-auth-scaffold - target: key-cape - layer: tooling - modules: - - src/secrets_engine/service_auth.py - operation: "HTTPS client_credentials exchange; not connected to OpenBao" - write: false - note: >- - key-cape is catalogued Tooling. This contact is an identity-token - preflight only and is deliberately not a write into OpenBao. Wiring it - as OpenBao JWT login remains blocked on the platform-owned role. - -# §9.6 — what this repository may claim about evidence it writes today. -evidence_bound: - kind: attributive - proves: - - local JSONL records that were written were not rewritten by this engine - does_not_prove: - - that a record was ever sent to audit-core - - absence of a record as evidence of non-occurrence - - completeness of the trail - destination_today: "local JSONL plus best-effort State Hub progress notes" - intended_destination: audit-core - completeness_claimed: false diff --git a/pep-stance.yaml b/pep-stance.yaml deleted file mode 100644 index 88790c6..0000000 --- a/pep-stance.yaml +++ /dev/null @@ -1,85 +0,0 @@ -# secrets-engine — PEP unreachable-engine stance map -# -# Framework: net-kingdom/canon/standards/security-layer-model_v0.7.md §6.4, §9.3 -# Companion: net-kingdom/SECURITY-COMPANION.md §5 -# Voice: INTENT.md / layer.yaml -# Validate: pytest tests/test_layer_conformance.py -# -# §6.4 obligation 3 requires a declared unreachable-engine stance that is -# total, scoped per zone or equivalent, carries no implicit default and no -# per-call discretion, and is published rather than held in code comments. -# Catalog stage is the equivalent scope until security-zone membership arrives -# as a claim on the decision. -# -# The property that makes this worth reading: tests/test_layer_conformance.py -# asserts this map against the shipped production fail-closed gate in -# secrets_engine.cli._require_lane_approval. A published map free to drift -# from the code is worse than none. - -schema_version: "0.1" -framework: netkingdom-security-layer-model -standard_version: "0.7" -repository: secrets-engine -pep_shape: true -declared_by: INTENT.md -decision_engine: access-engine # flex-auth until the governed rename -scope: catalog-stage # equivalent scope; not security-zones yet - -protected_action: >- - OpenBao metadata apply, KV provision, verification, exec delivery, - auth-capability handoff, native revoke, and lifecycle - suspend/deactivate/destroy - -# Total by construction over every catalog stage plus unknown. -# No implicit default — an unlisted value is a config error, not a fallback. -# "fail_closed" means no protected side effect without a durable -# access-engine / ActionAuthorization record. -# "fail_open" here means the engine-unreachable residue: proceed only under -# the existing lane-approval check (State Hub or local fixture), which is -# itself a tracked gap relative to access-engine, not a manufactured allow. -stance: - build: fail_open - test: fail_open - prod: fail_closed - unknown: fail_closed - -# Explicit, three-factor exception. Not a stance row and not a default. -# All three must hold or the production gate remains closed. -demo_exception: - id: unsafe-local-demo - requires: - - "SECRETS_ENGINE_UNSAFE_DEMO=1" - - "hub URL empty" - - "OpenBao target is loopback" - applies_to: prod - note: >- - Throwaway local demonstration only. Not a production residue and not - available when any one factor is missing. - -on_apply: - recorded_fields: - - stage - - action - - approval_status # rejected when the stance refuses - - error_type # DecisionError when fail_closed fires - - decision_id # present only where a decision was resolved - written_to: - - "local JSONL (.evidence/evidence-YYYY-MM-DD.jsonl)" - - "best-effort State Hub progress note" - never_recorded: "any secret material, token, password, or raw value" - gap: >- - Stance application is currently recorded as a DecisionError on the - privileged-evidence path, not as named stance fields (stage, failure - mode, decision id present only where rendered). Closing that gap is - SECRETS-WP-0008-T01. - -# §6.4 obligation 2 — the verdict is never cached. A retry of the same -# canonical request digest inside the decision lifetime is the decision -# being used as issued; a different resource is not. -verdict_caching: none - -# §6.4 obligation 4 / §9.6 — attributive today. No control branches on the -# presence of a local evidence record. Completeness is not claimed. -reconstructability: - bound: "§9.6 attributive — completeness is not claimed" - declared_at: layer.yaml#evidence_bound diff --git a/scripts/check_layer_conformance.py b/scripts/check_layer_conformance.py deleted file mode 100644 index b612faf..0000000 --- a/scripts/check_layer_conformance.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python3 -"""Check secrets-engine against the NetKingdom security layer model (§3.3, §6, §11). - -Read-only. This is the Engine/Lifecycle adaptation of the ops-warden reference -checker. §5 Staff shapes do not apply to the owned OpenBao contact. - -Mechanical checks: - -- a machine-readable declaration exists and says Engine / Lifecycle -- INTENT.md frontmatter matches that declaration -- no authorization decision surface is exposed -- the PEP stance map is published at the path named in the declaration -- every OpenBao subprocess adapter lives in a module listed as owned tooling - -Exit 0 clean, 1 finding, 2 declaration malformed. -""" -from __future__ import annotations - -import argparse -import re -import sys -from pathlib import Path - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -SRC = ROOT / "src" / "secrets_engine" -DECL = ROOT / "layer.yaml" -INTENT = ROOT / "INTENT.md" - -BAO_ARGV = re.compile( - r"""\[\s*(?:["']bao["']|bao_bin\b|bao_binary\b|self\.bao_bin)\s*,""" -) -DECISION_SURFACE = re.compile( - r"""\b(evaluate_policy|check_permission|render_decision|pdp_decide)\b""" -) - - -def load_declaration() -> dict: - if not DECL.exists(): - print(f"MISSING: {DECL} — secrets-engine must declare in its own voice (§11)") - raise SystemExit(2) - decl = yaml.safe_load(DECL.read_text()) - for key in ( - "layer", - "role", - "repository", - "standard_version", - "owned_tooling", - "decision_surfaces_exposed", - "pep_shaped", - "pep_stance", - ): - if key not in decl: - print(f"MALFORMED: layer.yaml has no {key!r}") - raise SystemExit(2) - if decl["layer"] != "engine": - print(f"MALFORMED: declared layer is {decl['layer']!r}, expected 'engine'") - raise SystemExit(2) - if decl["role"] != "lifecycle": - print(f"MALFORMED: declared role is {decl['role']!r}, expected 'lifecycle'") - raise SystemExit(2) - if decl["repository"] != "secrets-engine": - print(f"MALFORMED: repository is {decl['repository']!r}") - raise SystemExit(2) - return decl - - -def intent_frontmatter() -> dict: - text = INTENT.read_text(encoding="utf-8") - if not text.startswith("---"): - print("MALFORMED: INTENT.md has no YAML frontmatter (§11 / companion §2)") - raise SystemExit(2) - end = text.find("\n---", 3) - if end < 0: - print("MALFORMED: INTENT.md frontmatter is unclosed") - raise SystemExit(2) - return yaml.safe_load(text[3:end]) or {} - - -def owned_modules(decl: dict) -> set[str]: - names: set[str] = set() - for entry in decl.get("owned_tooling", []): - for module in entry.get("modules", []): - names.add(Path(module).name) - return names - - -def scan_bao_modules() -> dict[str, list[int]]: - found: dict[str, list[int]] = {} - for path in sorted(SRC.rglob("*.py")): - hits: list[int] = [] - for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): - stripped = line.strip() - if stripped.startswith("#"): - continue - if BAO_ARGV.search(line): - hits.append(n) - if hits: - found[path.name] = hits - return found - - -def scan_decision_surfaces() -> dict[str, list[int]]: - found: dict[str, list[int]] = {} - for path in sorted(SRC.rglob("*.py")): - hits: list[int] = [] - for n, line in enumerate(path.read_text(encoding="utf-8").splitlines(), 1): - if DECISION_SURFACE.search(line): - hits.append(n) - if hits: - found[path.name] = hits - return found - - -def main() -> int: - ap = argparse.ArgumentParser() - ap.add_argument("--report", action="store_true") - args = ap.parse_args() - - decl = load_declaration() - front = intent_frontmatter() - ok = True - - if str(front.get("layer", "")).lower() != "engine": - ok = False - print("FINDING: INTENT.md frontmatter layer is not Engine") - if str(front.get("role", "")).lower() != "lifecycle": - ok = False - print("FINDING: INTENT.md frontmatter role is not Lifecycle") - - if decl["decision_surfaces_exposed"] not in {None, "none"}: - ok = False - print("FINDING: decision_surfaces_exposed is not none — §6 forbids a second PDP") - - surfaces = scan_decision_surfaces() - if surfaces: - ok = False - print("FINDING: possible authorization decision surface in source:") - for name, lines in sorted(surfaces.items()): - print(f" src/secrets_engine/{name}: line(s) {', '.join(map(str, lines[:6]))}") - - stance = ROOT / str(decl["pep_stance"]) - if decl.get("pep_shaped") and not stance.is_file(): - ok = False - print(f"FINDING: pep_shaped but {decl['pep_stance']} is missing (§6.4 / §13.1)") - - found = scan_bao_modules() - owned = owned_modules(decl) - undeclared = {name: lines for name, lines in found.items() if name not in owned} - if undeclared: - ok = False - print("FINDING: OpenBao adapter outside owned_tooling modules:") - for name, lines in sorted(undeclared.items()): - print(f" src/secrets_engine/{name}: line(s) {', '.join(map(str, lines[:6]))}") - - if args.report: - print( - f"{decl['repository']} — layer: {decl['layer']} " - f"role: {decl['role']} (model v{decl['standard_version']})" - ) - print(f"declared by {decl['declared_by']}") - print(f"pep stance: {decl['pep_stance']}") - print(f"owned OpenBao modules: {sorted(owned)}") - print(f"OpenBao argv adapters found: {sorted(found)}") - - if ok and not args.report: - print( - "PASS — Engine/Lifecycle declaration present, no decision surface, " - f"{len(found)} OpenBao adapter module(s) owned." - ) - elif ok: - print("\nPASS — declaration, owned tooling, and PEP stance path hold.") - return 0 if ok else 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/tests/test_layer_conformance.py b/tests/test_layer_conformance.py deleted file mode 100644 index 302da57..0000000 --- a/tests/test_layer_conformance.py +++ /dev/null @@ -1,117 +0,0 @@ -"""Layer-model conformance (security-layer-model_v0.7 §3.3, §6, §11). - -The declaration must be this repository's own, machine-readable, and equal to -the shipped production fail-closed gate. A published stance map that may drift -from the code is worse than none. -""" -from __future__ import annotations - -import subprocess -import sys -from pathlib import Path -from types import SimpleNamespace - -import pytest -import yaml - -from secrets_engine.catalog import validate_entry -from secrets_engine.cli import _require_lane_approval -from secrets_engine.errors import DecisionError - -from tests.test_catalog import VALID - -ROOT = Path(__file__).resolve().parents[1] -SCRIPT = ROOT / "scripts" / "check_layer_conformance.py" -DECL = ROOT / "layer.yaml" -STANCE = ROOT / "pep-stance.yaml" -INTENT = ROOT / "INTENT.md" - - -def _decl() -> dict: - return yaml.safe_load(DECL.read_text(encoding="utf-8")) - - -def _stance() -> dict: - return yaml.safe_load(STANCE.read_text(encoding="utf-8")) - - -def test_declares_engine_lifecycle_in_its_own_voice(): - assert DECL.exists(), "no layer.yaml — §11 requires a machine-readable declaration" - data = _decl() - assert data["repository"] == "secrets-engine" - assert data["layer"] == "engine" - assert data["role"] == "lifecycle" - assert data["declared_by"] == "INTENT.md" - assert data["decision_surfaces_exposed"] == "none" - assert data["pep_shaped"] is True - - -def test_intent_frontmatter_matches_declaration(): - text = INTENT.read_text(encoding="utf-8") - assert text.startswith("---"), "companion §2 requires INTENT.md frontmatter" - end = text.find("\n---", 3) - front = yaml.safe_load(text[3:end]) - assert front["layer"] == "Engine" - assert front["role"] == "Lifecycle" - assert front["declaration"] == "layer.yaml" - assert front["pep_stance"] == "pep-stance.yaml" - - -def test_checker_passes_on_the_real_tree(): - result = subprocess.run( - [sys.executable, str(SCRIPT)], - capture_output=True, - text=True, - check=False, - ) - assert result.returncode == 0, result.stdout + result.stderr - - -def test_proposed_capabilities_carry_gap_record_fields(): - for cap in _decl()["proposed_capabilities"]: - for field in ("capability", "intended_owner", "blocked_on", "review", "state"): - assert cap.get(field), f"{cap.get('id')} missing {field}" - assert cap["state"] == "unowned-capability" - assert cap["owner_status"] == "proposed" - - -def test_stance_map_is_total_over_catalog_stages(): - stance = _stance()["stance"] - required = {"build", "test", "prod", "unknown"} - assert required <= set(stance), f"stance not total; missing {required - set(stance)}" - assert set(stance.values()) <= {"fail_open", "fail_closed"} - assert stance["prod"] == "fail_closed" - assert stance["unknown"] == "fail_closed" - assert _stance()["verdict_caching"] == "none" - - -def test_published_prod_stance_equals_shipped_fail_closed(monkeypatch): - """pep-stance.yaml prod: fail_closed must equal _require_lane_approval.""" - assert _stance()["stance"]["prod"] == "fail_closed" - entry = validate_entry(dict(VALID, stage="prod", approval={"model": "bootstrap-only"})) - cfg = SimpleNamespace(hub_url="http://127.0.0.1:8000", bao_addr="http://127.0.0.1:8200") - monkeypatch.delenv("SECRETS_ENGINE_UNSAFE_DEMO", raising=False) - with pytest.raises(DecisionError, match="live production remains disabled"): - _require_lane_approval(cfg, entry, "apply") - - -def test_build_stage_is_not_the_production_fail_closed_gate(tmp_path, monkeypatch): - """build is fail_open relative to access-engine: lane approval still applies.""" - assert _stance()["stance"]["build"] == "fail_open" - (tmp_path / ".decisions").mkdir() - (tmp_path / ".decisions" / "x.yaml").write_text( - "id: x\ntitle: approved\nstatus: resolved\nsuperseded_by: null\n" - ) - import secrets_engine.cli as cli - - monkeypatch.setattr(cli, "repo_root", lambda: tmp_path) - entry = validate_entry( - dict( - VALID, - stage="build", - path="build/team/thing", - approval={"model": "decision", "decision_ref": "x"}, - ) - ) - cfg = SimpleNamespace(hub_url="", bao_addr="http://127.0.0.1:8200") - assert _require_lane_approval(cfg, entry, "apply").id == "x" diff --git a/workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md b/workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md deleted file mode 100644 index 3bd6978..0000000 --- a/workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md +++ /dev/null @@ -1,222 +0,0 @@ ---- -id: SECRETS-WP-0008 -type: workplan -title: "Evolve the Lifecycle engine to the accepted security layer model" -domain: infotech -repo: secrets-engine -status: ready -owner: grok -topic_slug: custodian -created: "2026-08-29" -updated: "2026-08-29" ---- - -# SECRETS-WP-0008 - Evolve the Lifecycle engine to the accepted security layer model - -## Goal - -Close the gaps between the 2026-08-29 layer declaration (Engine / Lifecycle) -and the obligations the accepted NetKingdom Security Layer Model v0.7 places on -a PEP-shaped Lifecycle engine. - -Source review: `history/2026-08-29-layer-model-scope-intent-review.md`. -Declaration surface already landed this session: `INTENT.md` frontmatter, -`layer.yaml`, `pep-stance.yaml`, and `tests/test_layer_conformance.py`. - -This workplan does **not** restart CLI hardening owned by `SECRETS-WP-0007`. -It adds the layer-model remainder: stance binding, decision-record consumption, -`audit-core` evidence, and the two proposed engine surfaces. - -## Non-goals - -- Taking ops-warden's SSH access lane or becoming a credential broker. -- Rendering or caching authorization decisions. -- Observation in production or automatic containment (companion §10: both are - at zero estate-wide). -- Service mode, runtime tenancy, or a second custody backend. -- Reopening live production on State Hub status or local fixtures. - -## Coordination - -- `SECRETS-WP-0007-T04` owns the consumer validator and the fail-closed - production gate until a durable ActionAuthorization object is served. -- `SECRETS-WP-0007-T05` owns delivery-session self-revocation and the KeyCape - scaffold; this workplan owns the layer-model "no standing credential" bar. -- railiance-platform owns production OpenBao and the JWT role. -- `access-engine` / State Hub own serving the decision record. -- `audit-core` owns evidence custody. -- ops-warden owns the SSH Staff PEP and must assent any SSH-CA API contract. -- kings-guard owns secret-abuse posture and must assent the evidence surface. -- gate-house owns statute §13.1 inventory of PEP stance maps. - -## Tasks - -## Publish stance application as shipped behaviour - -```task -id: SECRETS-WP-0008-T01 -status: todo -priority: high -``` - -`pep-stance.yaml` is published and tests pin `prod: fail_closed` to -`_require_lane_approval`. The gate is still hardcoded, and a refusal is -recorded as a `DecisionError` rather than named stance fields. - -Load the published map as the source of shipped defaults. When the -unreachable-engine residue is applied, record at least stage, failure mode, and -decision id present only where a decision was rendered. Keep the three-factor -unsafe-demo exception out of the stance table. Assert map equality in -`tests/test_layer_conformance.py` the way ops-warden's stance test does. - -Acceptance: - -- `_require_lane_approval` (or its successor) reads `pep-stance.yaml`. -- Changing the YAML without changing the code fails the test, and the reverse. -- Fail-closed production evidence includes named stance fields, never a secret. -- Build/test remain fail-open *relative to access-engine* and still require - the existing lane-approval check; that residue stays documented as a gap - until T02. - -## Consume access-engine decision records - -```task -id: SECRETS-WP-0008-T02 -status: wait -priority: high -``` - -Blocked on the durable ActionAuthorization serving path owned with -`SECRETS-WP-0007-T04` / State Hub / `access-engine`. - -Once that object is served, a protected side effect holds a decision record -identifying the request it was rendered for. Replay is permitted iff the -canonical request digest matches and the lifetime holds. Do not cache the -verdict. Do not treat State Hub status, a local fixture, or catalog admission -as the decision. - -Acceptance: - -- Every live privileged production handler passes one shared consume function. -- Wrong digest, expired lifetime, superseded status, or missing record fail - closed before OpenBao. -- Evidence carries the decision id; a fail-closed residue still records the - stance instead. -- Local fixtures cannot authorize steady-state production. - -## Emit evidence under the §9.6 bound - -```task -id: SECRETS-WP-0008-T03 -status: todo -priority: high -``` - -Classify each privileged action as load-bearing or attributive. Today's trail -is attributive local JSONL plus best-effort State Hub notes; completeness is -already not claimed and must stay that way until the destination is -`audit-core`. - -Load-bearing classes (at least live destroy, production revoke/suspend of a -control, and dual-control provision once those are enabled) require atomic -emission via a **local** outbox and a declared cadence or heartbeat. Do not -place a synchronous `audit-core` dependency inside the state-change -transaction. Attributive classes may keep the current non-blocking trade if -that trade stays declared. - -Acceptance: - -- A machine-readable classification exists next to the declaration. -- No control in this repository branches on presence or absence of a local - evidence record. -- Load-bearing emission is queued locally; an `audit-core` outage does not - block a revocation. -- Documentation uses the sound claim: the archive proves records it holds were - not altered or truncated after arrival. - -## Accept the SSH-CA signing engine surface - -```task -id: SECRETS-WP-0008-T04 -status: todo -priority: medium -``` - -ops-warden's `ssh-ca-signing-write` gap names this engine as intended owner. -Accept the **engine API**, decline the Staff lane. Publish a contract -ops-warden can call after an `access-engine` decision: sign a short-lived -certificate for an `adm`/`agt`/`atm` actor under the caller's request, present -no additional authority, reconstruct as the caller's action. - -Do not implement a replacement client until ops-warden assents the contract. -Do not unpark the standalone `warden-sign` AppRole as break-glass. - -Acceptance: - -- A non-secret contract document in `docs/` states request, decision binding, - lifetime, evidence, and non-responsibilities. -- ops-warden has a recorded chance to assent or contest. -- No OpenBao SSH-CA write is added to this CLI ahead of that assent. -- `layer.yaml` owner_status remains `proposed` until the surface ships. - -## Accept the secret-use evidence surface - -```task -id: SECRETS-WP-0008-T05 -status: todo -priority: medium -``` - -kings-guard's secret-abuse posture is fixture-driven because no engine exposes -lease, revocation, mount, rotation, and delivery-session metadata. `route` and -`audit` are operator summaries over local JSONL and are not that surface. - -Publish a read-only engine contract of non-secret metadata, bounded by §9.6: -it cannot prove omission at source. Coordinate destination with `audit-core`. -Do not give kings-guard a vault client, and do not return values. - -Acceptance: - -- Contract lists fields, freshness, and the evidence bound. -- kings-guard has a recorded chance to assent or contest. -- No secret material, token, or accessor is in the surface. -- `layer.yaml` owner_status remains `proposed` until the surface ships. - -## No standing engine credential - -```task -id: SECRETS-WP-0008-T06 -status: wait -priority: medium -``` - -Blocked on the platform-owned OpenBao JWT mount/role (railiance-platform) and -on `SECRETS-WP-0007-T05` wiring. Companion §7 / statute §3.4: an agent holds -no long-lived credential of its own. Authority is per task, time-bounded, and -attributable to the principal it acts for. - -Bootstrap token files remain break-glass, heavily evidenced, never an implicit -fallback from service identity. - -Acceptance: - -- Steady-state CLI/API auth is the reviewed service identity, not - `--bootstrap-token-file`. -- Bootstrap is a named break-glass provider with distinct evidence. -- No fallback from JWT/service-auth failure into bootstrap or AppRole. -- Issued delivery sessions continue to self-revoke as in WP-0007-T05. - -## Exit criteria - -- The published PEP stance is the shipped default and stance application is - recorded by name. -- Production live side effects require an access-engine decision record, or - remain fail-closed with a recorded stance. -- Evidence classification and destination match §9.6; completeness is not - claimed. -- SSH-CA and secret-use surfaces are either contracted with the declaring - repositories or explicitly declined in this repository's own voice. -- Bootstrap is not the steady-state engine credential. -- `layer.yaml` / `pep-stance.yaml` / INTENT frontmatter stay in one voice. -- No raw secret values in Git, State Hub, chat, prompts, workplans, evidence, - or argv.