feat(recovery): prepare owner-bound offline continuity preflight
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
This commit is contained in:
tegwick 2026-10-17 01:03:36 +02:00
parent 535681052c
commit c59bbf568c
5 changed files with 248 additions and 0 deletions

View file

@ -0,0 +1,38 @@
# KeyCape original service recovery selector
Source-owner disposition, 2026-10-17, limited to secrets-engine's current consumer
contract. Current src/secrets_engine/service_auth.py fixes client/audience
secrets-engine-openbao, subject service:secrets-engine, tenant tenant:platform,
role secrets-engine and exact openbao:login scope. The NetKingdom producer binds
KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET; original KeyCape native evidence
uses openbao:admin as the denied-scope case. This selector matches the current
consumer source. This disposition is not native assent or a credential grant.
SECRETS-WP-0012 finished on historical attended custody/probe evidence and stays
finished. SECRETS-WP-0008-T06 retains functional steady-state adoption and scoped
backend entitlement. SECRETS-WP-0013 retains task-scoped backend execution. The
new SECRETS-WP-0015 covers only this original archive/isolated consumer preparation
and later accepted continuity proof; those existing gates are not duplicated.
The current consumer profile forbids a client-secret file and requires its
attended in-memory OpenBao source. That source depends on ordinary KeyCape OIDC
and OpenBao availability. The isolated issuer's native checker can consume its
original archive-delivered issuer binding without a provider login, but that does
not prove the secrets-engine consumer path works during an OpenBao/issuer outage.
Do not bypass the no-file/source contract, retrieve a live credential, redirect a
production consumer or invoke probe-openbao to fill this recovery gap. An exact
owner-reviewed outage-compatible proof/custody plan remains required.
Offline public preflight, after integration of reviewed source:
```sh
python3 tools/check-keycape-recovery-selector.py --selector docs/keycape-recovery-selector.json --source-sha256 sha256:c0ade429edf452ad17f05d690b7ebef091db7f1a42b5ac350f1e40955c8586c0
```
Use the exact hash in the owner packet, not a digest inferred at invocation from
an unreviewed file. The tool parses source constants with AST and never imports
the credential/backend module or calls the network. It requires all eight public
selector fields, exact source hash and unambiguous bounded JSON. Successful output
means source correspondence only. Original archived presence/secret binding,
actual TLS trust, native claim negatives and selected consumer continuity remain
separately evidenced. Missing/changed bindings refuse before any custody request.

View file

@ -0,0 +1,15 @@
{
"schema": "secrets-engine.public-recovery-selector.v1",
"status": "source_correspondence_only_not_native_assent",
"issuer": "https://kc.coulomb.social",
"consumer": {
"client_id": "secrets-engine-openbao",
"secret_env": "KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET",
"audience": "secrets-engine-openbao",
"scope": "openbao:login",
"subject": "service:secrets-engine",
"tenant": "tenant:platform",
"roles": "secrets-engine",
"deny_scope": "openbao:admin"
}
}

View file

@ -0,0 +1,31 @@
import hashlib
import importlib.util
import json
from pathlib import Path
import pytest
spec=importlib.util.spec_from_file_location('check',Path(__file__).parents[1]/'tools/check-keycape-recovery-selector.py');m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
SOURCE=b'CLIENT_ID="secrets-engine-openbao"\nSUBJECT="service:secrets-engine"\nTENANT="tenant:platform"\nROLE="secrets-engine"\nSCOPE="openbao:login"\nraise RuntimeError("source is parsed, never executed")\n'
SELECTOR={'client_id':'secrets-engine-openbao','secret_env':'KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET','audience':'secrets-engine-openbao','scope':'openbao:login','subject':'service:secrets-engine','tenant':'tenant:platform','roles':'secrets-engine','deny_scope':'openbao:admin'}
DIGEST='sha256:'+hashlib.sha256(SOURCE).hexdigest()
def test_source_parsed_not_executed():
result=m.verify(SELECTOR,SOURCE,DIGEST)
assert result['network_contacted'] is False
@pytest.mark.parametrize('field',list(SELECTOR))
def test_each_selector_binding_is_exact(field):
bad={**SELECTOR,field:'different'}
with pytest.raises(ValueError):m.verify(bad,SOURCE,DIGEST)
def test_source_drift_and_untyped_digest_refused():
for source,digest in ((SOURCE+b'\n',DIGEST),(SOURCE,DIGEST.removeprefix('sha256:'))):
with pytest.raises(ValueError):m.verify(SELECTOR,source,digest)
def test_duplicate_constants_refused():
source=SOURCE+b'CLIENT_ID="other"\n'
with pytest.raises(ValueError):m.verify(SELECTOR,source,'sha256:'+hashlib.sha256(source).hexdigest())
def test_duplicate_public_metadata_keys_refused():
with pytest.raises(ValueError):json.loads('{"consumer":{},"consumer":{}}',object_pairs_hook=m.unique)
def test_oversize_public_input_refused(tmp_path):
p=tmp_path/'selector.json';p.write_bytes(b'x'*(m.LIMIT+1))
with pytest.raises(ValueError):m.read(p)
def test_symlink_public_input_refused(tmp_path):
p=tmp_path/'selector.json';p.write_text('{}');link=tmp_path/'link';link.symlink_to(p)
with pytest.raises(ValueError):m.read(link)

View file

@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Offline public selector preflight; no imports of credential/backend clients.
Checks current consumer source constants and the producer's already-deployed
public env/denial selector. This is source metadata, not runtime/owner assent.
"""
import argparse
import ast
import hashlib
import json
import os
from pathlib import Path
import re
import stat
ROOT=Path(__file__).resolve().parents[1]
LIMIT=128*1024
def read(path):
path=Path(os.path.abspath(path))
if any(p.is_symlink() for p in (path,*path.parents)):raise ValueError()
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
with os.fdopen(fd,'rb') as source:
info=os.fstat(source.fileno())
if not stat.S_ISREG(info.st_mode) or not 0<info.st_size<=LIMIT:raise ValueError()
raw=source.read(LIMIT+1)
if len(raw)>LIMIT:raise ValueError()
return raw
def unique(pairs):
result={}
for key,value in pairs:
if key in result:raise ValueError()
result[key]=value
return result
def verify(selector,source,source_sha256):
if not re.fullmatch('sha256:[a-f0-9]{64}',source_sha256) or 'sha256:'+hashlib.sha256(source).hexdigest()!=source_sha256:raise ValueError()
needed={'CLIENT_ID','SUBJECT','TENANT','ROLE','SCOPE'};constants={}
for node in ast.parse(source).body:
if isinstance(node,ast.Assign):
for target in node.targets:
if isinstance(target,ast.Name) and target.id in needed:
if target.id in constants or not isinstance(node.value,ast.Constant) or not isinstance(node.value.value,str):raise ValueError()
constants[target.id]=node.value.value
if set(constants)!=needed:raise ValueError()
expected={'client_id':constants['CLIENT_ID'],'secret_env':'KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET',
'audience':constants['CLIENT_ID'],'scope':constants['SCOPE'],'subject':constants['SUBJECT'],
'tenant':constants['TENANT'],'roles':constants['ROLE'],'deny_scope':'openbao:admin'}
if selector!=expected:raise ValueError()
return {'schema':'secrets-engine.recovery-selector-preflight.v1','status':'source_selector_matches_not_native_assent',
'source_sha256':source_sha256,'consumer':expected,'credential_source_exercised':False,'network_contacted':False}
def main():
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--selector',required=True);p.add_argument('--source-sha256',required=True)
args=p.parse_args()
try:
proposal=json.loads(read(args.selector),object_pairs_hook=unique)
if not isinstance(proposal,dict) or not isinstance(proposal.get('consumer'),dict):raise ValueError()
result=verify(proposal['consumer'],read(ROOT/'src/secrets_engine/service_auth.py'),args.source_sha256)
except Exception:print('{"status":"source_selector_refused","private_diagnostics_suppressed":true}');return 1
print(json.dumps(result,indent=2));return 0
if __name__=='__main__':raise SystemExit(main())

View file

@ -0,0 +1,100 @@
---
id: SECRETS-WP-0015
type: workplan
title: "Prepare original KeyCape service selector and isolated consumer continuity"
domain: infotech
repo: secrets-engine
status: ready
owner: codex
topic_slug: custodian
created: "2026-10-17"
updated: "2026-10-17"
flavor: implementation
depends_on: []
related: [CUST-WP-0080, NK-WP-0049, SECRETS-WP-0012, SECRETS-WP-0008, SECRETS-WP-0013]
state_hub_workstream_id: "68a0642e-4908-5260-89c9-3adce60e6a4d"
---
Needed source-owner work for NK-WP-0049-T36/T18. Reviewed against current
Lifecycle INTENT, explicit consumer constants/custody source and dated historical
proof. Ready means work can proceed, not native assent. Finished0012 remains
finished; functional adoption/backend entitlement stay0008T06/0013. No new grant,
secret fetch, provider login, production redirect or external message occurred.
## Establish exact current intended source selector
```task
id: SECRETS-WP-0015-T01
status: done
priority: high
assignee: codex
state_hub_task_id: "a3e0e243-edac-5ade-a3d5-f58dac41633a"
```
Completed 2026-10-17 as source review. Current consumer constants and config guards
agree with secrets-engine-openbao client/audience, service:secrets-engine,
tenant:platform, role secrets-engine and only openbao:login. Producer env selector
and historical denied openbao:admin case are independently referenced, without
accepting the producer's native role. Prior native issuance7Oct and consumer
probe10Oct predate reviewed archive16Oct; they neither prove exact archived bytes
nor current native assent. Recorded docs/keycape-isolated-recovery-selector.md.
Evidence: Custodian recovery-owner-handoffs-r1 and prior17-source review.
## Deliver bounded offline public source selector preflight
```task
id: SECRETS-WP-0015-T02
status: done
priority: high
assignee: codex
state_hub_task_id: "eab3b758-10ce-5464-b5f7-3a152ee1327a"
```
Completed 2026-10-17 as isolated source preparation. Prepared AST-only source-hash
and all-eight-selector-field checker with bounded strict JSON, source drift,
duplicate, oversize and symlink negatives. It imports no backend/credential client
and never exchanges a token. Tests use public/synthetic metadata only. Canonical
integration/publication/projection must be recorded separately; successful output
is source correspondence, not native acceptance or a new secret source.
## Prepare outage-compatible original consumer proof without custody bypass
```task
id: SECRETS-WP-0015-T03
status: todo
priority: high
assignee: codex
state_hub_task_id: "6262a915-7655-50be-b9f7-481f816d7335"
```
Next: map the exact original archive-delivered registration and producer-native
checker result to the intended consumer, then prepare a source-reviewed isolated
proof/custody plan. Current consumer source requires attended OpenBao memory read
and refuses files; ordinary OpenBao/KeyCape outage is therefore a real dependency.
Do not add a fallback, persistent consumer copy or unreviewed injection source.
Clarify which continuity claim can be proved offline and which requires an exact
accepted owner custody/provider path. Bind current original selectors, image/
source/TLS metadata, negative cases, timeout/abort and cleanup; retain protected
backend operation entitlement in existing0008/0013. Done when that exact plan and
remaining owner dependencies are reviewable, without retrieving any value.
## Verify the separately admitted actual isolated consumer result
```task
id: SECRETS-WP-0015-T04
status: wait
priority: high
assignee: codex
needs_human: true
depends_on: [SECRETS-WP-0015-T03, NK-WP-0049-T33]
blocking_reason: "Await exact consumer proof/custody plan plus original TLS/private admission readiness; actual attended owner path/authority remains required."
state_hub_task_id: "aeec6eb0-39e2-5f7c-8f5d-778133edfc73"
```
Only a separately admitted owner action can establish actual archived registration,
issuer trust, native negative checks and consumer continuity. Source selector and
historical provider proof do not fill this gate. No production consumer redirect,
new grant, live OpenBao read, backend write or retained-factor MFA is authorized.
Return exact bounded evidence to NK49T36/T18; MFA remains NK49T19. Human gate is
never a worker wakeup. Cross-owner dependency is attached to derived case
secrets15-nk49-readiness in the packet; do not strand the wait or invent a driver.