Implement SECRETS-WP-0008 unblocked layer-model obligations
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run

Load pep-stance.yaml as the live unreachable-engine gate and record named
stance fields on privileged evidence. Classify evidence, queue load-bearing
records in a local outbox, and add heartbeat/drain commands that never sit
on a mutation path. Publish proposed SSH-CA and secret-use evidence
contracts without adding an OpenBao SSH-CA write.

T02 (access-engine decision records) and T06 (no standing credential) stay
wait on external endpoints.

Assistant: grok
Assistant-Session: 01a04cea-cb33-7c63-bad7-c1b0f9f0076b
This commit is contained in:
tegwick 2026-08-29 12:52:55 +02:00
parent 57f6c4fa65
commit 3cd9955ac9
16 changed files with 1041 additions and 77 deletions

View file

@ -0,0 +1,78 @@
# Secret-use evidence engine contract (proposed)
Status: **proposed**. `layer.yaml` owner_status remains `proposed` until this
surface ships. kings-guard must assent or contest before it is treated as an
observation input.
Standard: NetKingdom Security Layer Model v0.7 §9.6. Companion:
`net-kingdom/SECURITY-COMPANION.md` §6.
## What this is
A read-only Lifecycle engine surface of **non-secret** lease, revocation,
mount, rotation, and delivery-session metadata, so kings-guard can evaluate
secret-abuse posture without a vault client.
`secrets-engine route` and `secrets-engine audit` are operator summaries over
local JSONL. They are not this surface.
## Bound (normative)
The archive proves the records it holds were not altered or truncated after
arrival. It does **not** prove a record was sent. Absence of a record is **not**
evidence of non-occurrence. Completeness is not claimed.
This surface cannot close adversarial omission at a compromised source. Cadence
for load-bearing classes is a heartbeat (`secrets-engine evidence heartbeat`),
not rate monitoring.
## Fields (non-secret)
| Field | Meaning | Freshness |
| --- | --- | --- |
| `as_of` | UTC timestamp of the snapshot | Snapshot time |
| `catalog_id` | Lane id | Catalog load |
| `stage` | `build` \| `test` \| `prod` | Catalog load |
| `kind` | `kv` \| `auth-capability` | Catalog load |
| `mount` | OpenBao mount name | Catalog load |
| `path` | KV path or allowlisted auth path, never a value | Catalog load |
| `field_names` | Declared field names, never values | Catalog load |
| `ready` | All declared fields present (boolean) | Last route/verify |
| `session_handle` | Accessor fingerprint only | Last exec/verify |
| `revocation_attempted` | Boolean | Last session |
| `revocation_succeeded` | Boolean | Last session |
| `lifecycle_operation` | `suspend` \| `deactivate` \| `destroy` \| `revoke` if one ran | Last lifecycle |
| `decision_id` | Present only where a decision was rendered | Last privileged action |
| `stance_stage` / `stance_failure_mode` | Named PEP residue | Last privileged action |
| `evidence_kind` | `load-bearing` \| `attributive` \| `heartbeat` | Per record |
Forbidden in this surface: secret values, tokens, passwords, AppRole
`secret_id`, raw accessors, certificate private keys, subprocess argv, and
exception prose.
## Freshness
Every row carries `as_of`. There is no cached authorization verdict. A consumer
must not treat a stale snapshot as a standing allow. Recommended maximum age
for posture evaluation is the heartbeat interval declared in
`evidence-classification.yaml` (`1d`) until a tighter contract is assented.
## Destination
Intended owner of custody is `audit-core`. Today the engine queues load-bearing
records in `evidence_dir/outbox/` and writes attributive records to local
JSONL. Drain to `audit-core` is best-effort and is never on the mutation path.
kings-guard reads this engine surface, not OpenBao.
## Non-responsibilities
This surface does not decide whether an action is permitted, does not expose an
approval-validity query, does not prove omission at source, and does not
observe production events kings-guard has not received. Companion §10 still
holds: nothing is observed in production until kings-guard reports otherwise.
## Assent
A coordination message to kings-guard records this contract. Assent or contest
belongs in kings-guard's own voice.

View file

@ -0,0 +1,90 @@
# SSH-CA signing engine contract (proposed)
Status: **proposed**. `layer.yaml` owner_status remains `proposed` until this
surface ships. ops-warden must assent or contest before any replacement client
is written.
Standard: NetKingdom Security Layer Model v0.7 §3.3 (Lifecycle), §6.4 (PEP).
Companion: `net-kingdom/SECURITY-COMPANION.md`.
## What this is
A deterministic Lifecycle engine API that asks OpenBao to sign a short-lived
SSH user certificate **after** an `access-engine` decision record naming the
request. secrets-engine presents no additional authority of its own beyond the
decision-bound role. The action reconstructs as the caller's.
## What this is not
- Operational ownership of the SSH access lane. `warden sign`, `cert_command`,
and host-access stewardship stay with ops-warden (Staff PEP).
- A break-glass AppRole. The cataloged standalone `warden-sign` AppRole stays
parked.
- An authorization decision, an approval store, or a second PDP.
- A writer of private keys. The caller supplies the public key; the engine
returns only the signed certificate material through a scoped delivery path.
## Caller
ops-warden, after it holds an `access-engine` decision for the sign request, or
after it has recorded the application of its own published unreachable-engine
stance in place of a decision (`pep-stance.yaml` in ops-warden).
## Request
| Field | Meaning |
| --- | --- |
| `actor_type` | `adm` \| `agt` \| `atm` |
| `actor_name` | Attributable principal the certificate is issued for |
| `identity` | Certificate key id / identity string |
| `principals` | Exact principal list; no wildcards |
| `public_key` | Caller-supplied public key (not a private key) |
| `ttl` | Requested lifetime; engine enforces the actor-type maximum |
| `decision_id` | Access-engine decision record id, present only where a decision was rendered |
| `request_digest` | Canonical digest of subject, action, resource, context |
| `stance` | Named unreachable-engine residue if no decision was rendered |
Replay is permitted iff the canonical request digest matches and the decision
lifetime holds. A different resource is a different request.
## Response (non-secret)
| Field | Meaning |
| --- | --- |
| `signed` | Whether OpenBao signed |
| `valid_before` | Certificate expiry |
| `role` | OpenBao SSH role used (`agt-role` / `adm-role` / `atm-role`) |
| `decision_id` | Echoed when present |
| `evidence_id` | Local non-secret evidence record id |
Certificate private material never appears in this response, in evidence, or in
argv. Delivery of the signed certificate uses a scoped file or exec path with
mode 0600, outside Git, same rules as existing handoff.
## Lifetime
Every allow has an explicit end: the certificate TTL, bounded by actor-type
maxima already documented on the `warden-sign` lane. No standing grant.
## Evidence
Attributive unless a future control branches on the presence of a signing
record — today none does. Completeness is not claimed. Records may include
actor type, role, `valid_before`, decision id, and stance fields. They must
never include a public-key blob if it could identify a live private key
handle, a token, or certificate private material.
Destination today: local JSONL. Intended destination: `audit-core`.
## Non-responsibilities
secrets-engine does not choose principals, mint SSH keys, open tunnels, log
into hosts, or override ops-warden's zone stance map. If `access-engine` is
unreachable, this engine applies **its own** published catalog-stage stance
(`pep-stance.yaml` in this repository) and does not consult ops-warden's map.
## Assent
A coordination message to ops-warden records this contract. Assent or contest
belongs in ops-warden's own voice. Until then no OpenBao SSH-CA write is added
to the secrets-engine CLI.