# 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.