Declare Engine/Lifecycle against security layer model v0.7
Replace the gate-house review note with this repository's own declaration: INTENT.md frontmatter, layer.yaml, and a published PEP stance map. SCOPE.md and agent boundary docs now match that layer. The review under history/ identifies the implementation remainder; SECRETS-WP-0008 is the follow-on workplan. SECRETS-IN-0001 is closed. The layer is not contested. Catalog "custody" is a finding: OpenBao owns custody, this engine owns the lifecycle API over it. SSH-CA signing is accepted as a proposed engine API and declined as a Staff lane. Assistant: grok Assistant-Session: 01a04cea-cb33-7c63-bad7-c1b0f9f0076b
This commit is contained in:
parent
b1f0f7b666
commit
1945e16685
14 changed files with 1177 additions and 108 deletions
|
|
@ -1,12 +1,15 @@
|
|||
## Repo boundary
|
||||
|
||||
This repo owns the **secrets-engine** workflow/interaction layer only. It does
|
||||
not own:
|
||||
This repo owns the **secrets-engine** Lifecycle engine only (NetKingdom
|
||||
security layer model v0.7: Engine / Lifecycle). It does not own:
|
||||
|
||||
- Secret custody, policy, lease, and audit backend → OpenBao / railiance-platform
|
||||
- SSH certificate issuance → ops-warden (`warden sign`)
|
||||
- SSH certificate issuance (Staff PEP) → ops-warden (`warden sign`)
|
||||
- Tunnels and remote transport → ops-bridge
|
||||
- Authorization decisions → flex-auth
|
||||
- Authorization decisions → access-engine (`flex-auth`)
|
||||
- Approval objects → approval-engine
|
||||
- Evidence custody and integrity → audit-core
|
||||
- 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)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
**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.
|
||||
**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.
|
||||
|
||||
**Domain:** infotech
|
||||
**Repo slug:** secrets-engine
|
||||
|
|
|
|||
19
AGENTS.md
19
AGENTS.md
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Repo Identity
|
||||
|
||||
**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.
|
||||
**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.
|
||||
|
||||
**Domain:** infotech
|
||||
**Repo slug:** secrets-engine
|
||||
|
|
@ -166,17 +166,24 @@ Read `docs/netkingdom-security-infrastructure.md` before changing secret
|
|||
catalogs, OpenBao policies, auth roles, delivery modes, or ops-warden routing.
|
||||
|
||||
Core rules:
|
||||
- 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.
|
||||
- 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.
|
||||
- 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.
|
||||
concepts as they harden. gate-house owns the layer model.
|
||||
- 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.
|
||||
|
||||
<!-- Append repo-specific agent instructions below this marker.
|
||||
The state-hub template sync preserves content after this line. -->
|
||||
|
|
|
|||
206
INTENT.md
206
INTENT.md
|
|
@ -1,40 +1,78 @@
|
|||
---
|
||||
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
|
||||
|
||||
> **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.
|
||||
> **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.
|
||||
>
|
||||
> The layer rule that binds every repository: **Staff never touches tooling
|
||||
> directly. It acts only through engine APIs.**
|
||||
> 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.
|
||||
>
|
||||
> **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 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 note records what should change. The body below is not yet adapted.*
|
||||
> **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 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 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.**
|
||||
**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.**
|
||||
|
||||
## 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 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:
|
||||
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:
|
||||
|
||||
- approved decisions still require a human to bridge low-level OpenBao details;
|
||||
- agents and operators hit interface mismatches before reaching the security
|
||||
|
|
@ -42,111 +80,145 @@ already 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;
|
||||
- ops-warden needs a reliable front door for credential and scoped capability
|
||||
use, not direct secret custody responsibility.
|
||||
- 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.
|
||||
|
||||
This repository exists to capture that complexity once, behind a stable and
|
||||
auditable interface.
|
||||
This repository exists to capture that complexity once, behind a stable,
|
||||
deterministic, and auditable interface.
|
||||
|
||||
## The Mission
|
||||
|
||||
To provide a decision-aware secrets workflow that can:
|
||||
To provide a decision-aware Lifecycle engine that can:
|
||||
|
||||
- translate approved requests into narrowly scoped OpenBao changes or capability
|
||||
- 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
|
||||
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;
|
||||
- record enough non-secret evidence for review, audit, and troubleshooting;
|
||||
- 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;
|
||||
- make routine secure work fast enough that operators and agents actually use
|
||||
the secure path.
|
||||
the Engine path instead of touching OpenBao.
|
||||
|
||||
## Core Principles
|
||||
|
||||
### 1. Decision First
|
||||
### 1. Decision elsewhere, materialization here
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### 2. OpenBao Enforces, secrets-engine Orchestrates
|
||||
### 2. OpenBao is Tooling; this engine is the API over it
|
||||
|
||||
OpenBao remains the vault, policy, lease, and audit backend. secrets-engine owns
|
||||
the workflow, catalog, validation, delivery, scoped capability handoff, and
|
||||
operator/agent interface.
|
||||
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.
|
||||
|
||||
### 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.
|
||||
rules. Stage is the current equivalent scope for the PEP stance map until
|
||||
security-zone membership is consumed as a claim.
|
||||
|
||||
### 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 review action. That ease must not
|
||||
require handing platform-root or platform-admin power to routine automation.
|
||||
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.
|
||||
|
||||
### 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 and replaced
|
||||
by narrower auth roles.
|
||||
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.
|
||||
|
||||
### 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. The engine should make
|
||||
this easier than copying or inspecting a secret value.
|
||||
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.
|
||||
|
||||
### 8. Auditable and Reversible
|
||||
### 8. Evidence is bounded, not omniscient
|
||||
|
||||
Every apply, read, delivery, lease, verification, revocation, and deactivation
|
||||
has non-secret evidence that can be reviewed later.
|
||||
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.
|
||||
|
||||
## What This Is
|
||||
|
||||
secrets-engine is:
|
||||
|
||||
- a workflow layer for approved secret changes, scoped capability grants, and
|
||||
access;
|
||||
- a CLI and service API for operators, agents, and automation;
|
||||
- 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 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;
|
||||
- an audit evidence writer for State Hub and local logs;
|
||||
- the credential interaction surface that ops-warden can route to.
|
||||
- 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.
|
||||
|
||||
## What This Is Not
|
||||
|
||||
secrets-engine is not:
|
||||
|
||||
- a replacement for OpenBao;
|
||||
- a replacement for OpenBao, or the owner of custody;
|
||||
- an identity provider or MFA system;
|
||||
- an authorization decision engine;
|
||||
- an authorization decision engine, an approval store, or a maturity 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.
|
||||
- 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.
|
||||
|
||||
## System Boundary
|
||||
|
||||
| Concern | Primary owner | secrets-engine responsibility |
|
||||
| --- | --- | --- |
|
||||
| Secret custody, leases, audit backend | OpenBao / railiance-platform | Use through least-privilege roles and validated paths. |
|
||||
| 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. |
|
||||
| Human and service identity | key-cape / user-engine | Consume identity claims; do not replace identity lifecycle. |
|
||||
| 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. |
|
||||
| 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. |
|
||||
| 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 evidence and decision links only. |
|
||||
| Request history and progress | State Hub | Write non-secret progress pointers only; not a security state plane. |
|
||||
|
||||
## Direction of Evolution
|
||||
|
||||
|
|
@ -161,10 +233,16 @@ 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. **Service mode:** expose an API that ops-warden, agents, CI, and future UI
|
||||
surfaces can use without knowing OpenBao internals.
|
||||
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.
|
||||
|
||||
## 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?**
|
||||
keeping raw values, OpenBao privileges, and production impact tightly bounded
|
||||
to a decision this engine did not render?**
|
||||
|
|
|
|||
15
README.md
15
README.md
|
|
@ -4,15 +4,20 @@ Headless, multi-application, multi-tenant secrets workflow and automation layer
|
|||
for approved secret custody, delivery, and lifecycle work across build, test,
|
||||
and production stages.
|
||||
|
||||
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
|
||||
**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
|
||||
allowlisted per-lane `audit` summary without exposing record detail.
|
||||
|
||||
## Start Here
|
||||
|
||||
- [INTENT.md](INTENT.md) - why this repository exists.
|
||||
- [INTENT.md](INTENT.md) - why this repository exists, including the Engine /
|
||||
Lifecycle declaration.
|
||||
- [layer.yaml](layer.yaml) - machine-readable layer declaration and proposed
|
||||
surfaces.
|
||||
- [ProductRequirementsDocument.md](ProductRequirementsDocument.md) - product
|
||||
requirements and MVP scope.
|
||||
- [NetKingdom security infrastructure boundary pointer](docs/netkingdom-security-infrastructure.md)
|
||||
|
|
|
|||
103
SCOPE.md
103
SCOPE.md
|
|
@ -3,15 +3,20 @@
|
|||
> 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 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.
|
||||
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.
|
||||
|
||||
It is an OpenBao workflow client, not a vault, authorization service, identity
|
||||
provider, credential broker, or general secrets API.
|
||||
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.
|
||||
|
||||
## Implemented Capabilities
|
||||
|
||||
|
|
@ -33,15 +38,19 @@ provider, credential broker, or 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.
|
||||
- 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.
|
||||
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.
|
||||
- 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.
|
||||
|
|
@ -116,7 +125,8 @@ 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.
|
||||
receipts retain only the non-secret outbox id. This trail is attributive:
|
||||
completeness is not claimed, and it is not `audit-core`.
|
||||
- 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
|
||||
|
|
@ -211,9 +221,11 @@ 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 State Hub action authorization and wiring its
|
||||
validated approval threshold to each production handler.
|
||||
- Direct flex-auth evaluation, JWT signature verification, or identity
|
||||
- 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
|
||||
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
|
||||
|
|
@ -221,32 +233,55 @@ 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** 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.
|
||||
- **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.
|
||||
- **user-engine / key-cape** own identity, OIDC, MFA, and claims.
|
||||
- **ops-warden** issues SSH certificates and routes non-SSH credential needs; it
|
||||
does not vend their values.
|
||||
- **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-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.
|
||||
cross-system security boundary. **gate-house** owns the layer model.
|
||||
|
||||
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 treat catalog admission, a dry-run, or an old workload CCR as approval
|
||||
for a new production auth surface.
|
||||
- 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 mutate an existing shared mount or replace workload delivery by
|
||||
implication.
|
||||
- Never add KV destruction back to ordinary `revoke`; irreversible custody
|
||||
|
|
@ -254,7 +289,22 @@ Canonical boundary:
|
|||
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.
|
||||
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 |
|
||||
|
||||
## Where Current Work Lives
|
||||
|
||||
|
|
@ -262,6 +312,7 @@ Canonical boundary:
|
|||
- `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
|
||||
|
||||
|
|
@ -298,7 +349,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 not a durable
|
||||
remote audit store.
|
||||
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.
|
||||
keywords: [routing, evidence, state-hub, audit, secrets]
|
||||
```
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ 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.
|
||||
|
|
|
|||
163
history/2026-08-29-layer-model-scope-intent-review.md
Normal file
163
history/2026-08-29-layer-model-scope-intent-review.md
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
# 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`.
|
||||
|
|
@ -7,13 +7,21 @@ id: SECRETS-IN-0001
|
|||
kind: intake
|
||||
title: 'Declaration requested: state this repository''s layer in INTENT.md (security
|
||||
layer model §11)'
|
||||
status: open
|
||||
status: closed
|
||||
origin: cross-repo
|
||||
origin_ref: net-kingdom security-layer-model_v0.4 §11
|
||||
origin_ref: net-kingdom security-layer-model_v0.7 §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
|
||||
|
|
|
|||
144
layer.yaml
Normal file
144
layer.yaml
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
# 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
|
||||
85
pep-stance.yaml
Normal file
85
pep-stance.yaml
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# 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
|
||||
178
scripts/check_layer_conformance.py
Normal file
178
scripts/check_layer_conformance.py
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
#!/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())
|
||||
117
tests/test_layer_conformance.py
Normal file
117
tests/test_layer_conformance.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
"""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"
|
||||
222
workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md
Normal file
222
workplans/SECRETS-WP-0008-layer-model-lifecycle-conformance.md
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
---
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue