feat(admission): founder-signed trust and recovery-attempt admission (WP14 T02, WP15 T05)
Reuse the gated-executor approval pattern: ssh-keygen -Y signatures over exact bytes, verified against a runtime-unwritable allowed_signers anchor with a fixed namespace per purpose. load_trust now refuses responder trust without a founder admission; the KeyCape selector preflight gains --admission for one exact, bounded recovery attempt. WP14 re-pointed to CUST-WP-0082-T06 and split founder key admission into human-gated T04; WP15 T04 now requires the signed admission. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 809600@bnt-lap001 Assistant-Session: 40ba228f-c8a0-4ec9-bb29-3129951afdc4
This commit is contained in:
parent
497040c58c
commit
ebd8e49aff
15 changed files with 469 additions and 29 deletions
68
docs/founder-admission.md
Normal file
68
docs/founder-admission.md
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Founder admission (SSH signature over exact bytes)
|
||||
|
||||
Two inputs need authority from outside this process: the Access Engine
|
||||
responder trust file (SECRETS-WP-0014) and a KeyCape recovery attempt
|
||||
(SECRETS-WP-0015). The runtime itself must not be able to grant either. Both
|
||||
now use the approval pattern the estate already accepted in **gated-executor**
|
||||
(GEXEC-WP-0001, installed on railiance01 2026-10-20). The founder signs exact
|
||||
bytes with the passphrase-protected key, and the verifier trusts only an
|
||||
`allowed_signers` file that the runtime account cannot write.
|
||||
|
||||
Implementation: `src/secrets_engine/founder_admission.py`. No new approval
|
||||
service, network call or key type is involved. `ssh-keygen -Y` works offline,
|
||||
so the recovery case still works when KeyCape/OpenBao are down.
|
||||
|
||||
## Anchor
|
||||
|
||||
```
|
||||
/etc/secrets-engine/admission/ root:root 0755
|
||||
/etc/secrets-engine/admission/allowed_signers root:root 0644
|
||||
```
|
||||
|
||||
`SECRETS_ENGINE_ADMISSION_ANCHOR` may name another directory. The checks stay
|
||||
the same either way: the directory and file are not symlinks, not group/world
|
||||
writable, and not owned by the running UID unless that UID is root. The signer
|
||||
principal is read from the signature (`find-principals`), and it must match
|
||||
exactly one `allowed_signers` entry.
|
||||
|
||||
Namespaces are fixed per purpose, so a signature for one purpose cannot admit
|
||||
another. That includes a gexec action approval (`gexec-approval`).
|
||||
|
||||
| Purpose | Namespace | Signed file | Signature |
|
||||
| --- | --- | --- | --- |
|
||||
| PDP responder trust | `secrets-engine-pdp-trust` | `pdp-trust.json` | `pdp-trust.json.sig` |
|
||||
| Recovery attempt | `secrets-engine-recovery-attempt` | attempt admission JSON | `<file>.sig` |
|
||||
|
||||
## Admit or rotate responder trust
|
||||
|
||||
```bash
|
||||
ssh-keygen -Y sign -f ~/.ssh/<founder-key> -n secrets-engine-pdp-trust pdp-trust.json
|
||||
```
|
||||
|
||||
`load_trust` verifies the signature over the same bytes it parses. Any edit
|
||||
needs a new signature, so rotation means a newly signed file. Overlap is two
|
||||
keys in one signed file, and removal is a signed file without the old key.
|
||||
Neither the file's presence nor a loopback URL counts as admission.
|
||||
|
||||
## Admit one recovery attempt
|
||||
|
||||
```json
|
||||
{"schema": "secrets-engine.recovery-attempt-admission.v1",
|
||||
"selector_sha256": "sha256:<exact selector file bytes>",
|
||||
"source_sha256": "sha256:<service_auth.py>",
|
||||
"issuer": "https://kc.coulomb.social",
|
||||
"case": "secrets15-nk49-readiness",
|
||||
"not_before": "YYYY-MM-DDTHH:MM:SSZ", "not_after": "YYYY-MM-DDTHH:MM:SSZ"}
|
||||
```
|
||||
|
||||
```bash
|
||||
ssh-keygen -Y sign -f ~/.ssh/<founder-key> -n secrets-engine-recovery-attempt attempt.json
|
||||
tools/check-keycape-recovery-selector.py --selector docs/keycape-recovery-selector.json \
|
||||
--source-sha256 sha256:<...> --admission attempt.json
|
||||
```
|
||||
|
||||
The window may be at most 24 hours. A pass reports
|
||||
`founder_admitted_attempt_not_native_acceptance`. That status proves the
|
||||
founder admitted this exact attempt. It does not prove the attempt ran, that it
|
||||
ran only once, or that the native result passed. Single-use consumption and
|
||||
cleanup stay with the bounded NK-WP-0049 execution process.
|
||||
Loading…
Add table
Add a link
Reference in a new issue