fix(recovery): require one original client and refine native owner gates
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 04:15:30 +02:00
parent 70f408ca23
commit c52ca7f405
5 changed files with 270 additions and 26 deletions

View file

@ -7,7 +7,7 @@
"tests/run_isolated_keycape_native_fixture.py": "sha256:ecbadecc475622eaaf20a726110aebef5f4fe7af141110a98aca1640179b4a03",
"tests/test_identity_bundle.py": "sha256:be5ce74097594cc7f7e77dbccd6cb53aa72f263cd1bb442ce5464038aed1116c",
"tests/test_isolated_keycape_fixture_plan.py": "sha256:5945619f4c5957187763276cc900870a9f08f9475b63c240694688e0c2eca7dd",
"tests/test_isolated_keycape_recovery.py": "sha256:247e9a87f5be2ee9f20f7edbb4a9f612ab7cb0cd940392c3caeca8b046605f55",
"tests/test_isolated_keycape_recovery.py": "sha256:5f729e077db51dd6b17d00dc3eb71c0e652849f55fe68505a07befa663c91dea",
"tests/test_isolated_keycape_session.py": "sha256:cbdf51efde26eff337e9303a2c431f80b333b8846d763eb62853f81eb3f13e2c",
"tools/bootstrap-recovery/capture-identity.py": "sha256:47dca7c4dc96fa7eb9fb02c81fca9ca8dc5281aac918979eb7367148168ba705",
"tools/bootstrap-recovery/check-privacyidea-database.py": "sha256:90f454c87998d7f5d625e3c6656c3418f8f56275dc1879a9d19ed605431b94a4",
@ -25,7 +25,7 @@
"tools/bootstrap-recovery/inventory-scaleway.py": "sha256:a219df205708fa4ed44b763a418585d63c820c25434bc0fd14e805eb5c2f79d3",
"tools/bootstrap-recovery/inventory-storage-share.py": "sha256:673f9b17fb2f3fef6fcdb5c50a2bcca69e3df3157a9e5582a2d995c6b87640b5",
"tools/bootstrap-recovery/isolated-keycape-session.py": "sha256:8fa38efe28167b59a6307f342b2ac8fcda78ecda60e6228f22017424d2343c22",
"tools/bootstrap-recovery/isolated-keycape.py": "sha256:f67627b7d1647d75187dd899874efe22383bc91de5d97dabb9801076668ab3c2",
"tools/bootstrap-recovery/isolated-keycape.py": "sha256:86f37add7221e4c73e3f9d878c09a3cc81061627cbf024ed6887d973d929b718",
"tools/bootstrap-recovery/isolated-lldap.py": "sha256:35bfa8411186ad936a199bd450c7e18c2ad51059301a6db9650e6bd055ab1b81",
"tools/bootstrap-recovery/isolated-privacyidea.py": "sha256:4df7196678aac1ad594f32abf45c68dc21a9d2760d9e8a536a215a4a3c5d277f",
"tools/bootstrap-recovery/prepare-identity-coverage.py": "sha256:cd5f3067d28b4c7cc93ed08b8cf3869218bdecf7c54b5dffed22d697de71dbb1",
@ -81,7 +81,7 @@
"synthetic_source_root": "private /dev/shm/nk49-keycape-fixture-<suffix>",
"safe_result": "private /tmp/nk49-keycape-native-fixture-review-<suffix>/result.json"
},
"scope": "candidate itself on complete synthetic format/integrity archive fixture; no native PI/LLDAP/Authelia proof, real custody, real factor or real client credential; successor binds sealed-descriptor custody preparation sources; synthetic fixture itself overrides custody with generated fixture key as before",
"scope": "candidate itself on complete synthetic format/integrity archive fixture; no native PI/LLDAP/Authelia proof, real custody, real factor or real client credential; successor binds sealed-descriptor custody preparation sources; synthetic fixture itself overrides custody with generated fixture key as before; unexecuted exact-one-selector adapter successor: no new native outcome inferred from earlier fixtures",
"mutations": [
"conditional creation of exact fresh local containers and local-driver tmpfs volumes",
"private synthetic file import to those volumes",

View file

@ -54,6 +54,34 @@ class ArchiveTests(unittest.TestCase):
if row['path'].endswith('/deployment/keycape.json'):row.update(bytes=len(raw),sha256=hashlib.sha256(raw).hexdigest())
with self.assertRaises(a.Refused):self.payload()
class OriginalSelectorTests(unittest.TestCase):
"""Already-restored public fixture config; no archive/key/native action."""
def assert_refused_before_further_inputs(self,clients):
import yaml
cfg={'issuer':'https://issuer.example','clients':clients}
payload={'files':{'/etc/keycape/config.yaml':base64.b64encode(yaml.safe_dump(cfg).encode()).decode(),
'/etc/keycape/key.pem':'deliberately-not-base64'}}
admission={'issuer':'https://issuer.example','consumer':{'client_id':'existing','secret_env':'KEYCAPE_ORIGINAL_SECRET'}}
# Empty session/no env and an undecodable key also expose early private access.
from contextlib import ExitStack
with ExitStack() as stack:
blocked=[stack.enter_context(patch.object(a,name,side_effect=AssertionError('later input or native call')))
for name in ('container_file','runtime_file','runtime_files','command','check_jwks','process')]
with self.assertRaises(a.Refused):a.run_verifier({},admission,payload)
for call in blocked:call.assert_not_called()
def client(self):
return {'clientId':'existing','clientType':'confidential','secretRef':'env:KEYCAPE_ORIGINAL_SECRET',
'grantTypes':['client_credentials']}
def test_absent_original_selector_refused_before_further_inputs(self):
self.assert_refused_before_further_inputs([dict(self.client(),clientId='unrelated')])
def test_identical_duplicate_selector_refused_before_further_inputs(self):
self.assert_refused_before_further_inputs([self.client(),self.client()])
def test_conflicting_duplicate_selector_refused_before_further_inputs(self):
self.assert_refused_before_further_inputs([self.client(),dict(self.client(),secretRef='env:OTHER')])
def test_empty_registry_refused_before_further_inputs(self):
self.assert_refused_before_further_inputs([])
class NativeTests(unittest.TestCase):
def setUp(self):
from cryptography.hazmat.primitives import serialization

View file

@ -322,6 +322,23 @@ def runtime_file(session,path):
def run_verifier(session, admission, payload):
# Select from the already-restored config before further private/runtime reads.
# This is not public preflight before archive decryption or its key prompt.
import yaml
cfg = yaml.safe_load(base64.b64decode(payload['files']['/etc/keycape/config.yaml'],validate=True))
if not isinstance(cfg,dict) or cfg.get('issuer') != admission['issuer']:
raise Refused()
consumer = admission['consumer']
clients = cfg.get('clients')
if not isinstance(clients,list) or any(not isinstance(client,dict) for client in clients):
raise Refused()
matches = [client for client in clients if client.get('clientId') == consumer['client_id']]
if len(matches) != 1:
raise Refused()
client = matches[0]
if (client.get('secretRef') != 'env:'+consumer['secret_env'] or client.get('clientType') != 'confidential'
or 'client_credentials' not in client.get('grantTypes',[])):
raise Refused()
issuer = session['issuer']
if {v['target'] for v in session.get('volumes',{}).values()}!={'/etc/keycape','/var/lib/keycape-policy','/etc/keycape-factor','/recovery-tls'}:raise Refused()
from urllib.parse import urlsplit
@ -352,15 +369,7 @@ def run_verifier(session, admission, payload):
if durable!={k:v for k,v in files.items() if k.startswith('/var/lib/keycape-policy/')}:
raise Refused()
equal()
# Reading YAML is data parsing only; no archived executable is evaluated.
import yaml
cfg = yaml.safe_load(files['/etc/keycape/config.yaml'])
if cfg['issuer'] != admission['issuer']:
raise Refused()
consumer = admission['consumer']
client = next(x for x in cfg['clients'] if x['clientId']==consumer['client_id'])
if (client['secretRef']!='env:'+consumer['secret_env'] or client['clientType']!='confidential'
or 'client_credentials' not in client['grantTypes'] or not env.get(consumer['secret_env'])):
if not env.get(consumer['secret_env']):
raise Refused()
from cryptography.hazmat.primitives import serialization
key=serialization.load_pem_private_key(files['/etc/keycape/key.pem'],password=None)

View file

@ -708,6 +708,34 @@ receipts confer no new grant. The default production endpoint remains disabled
without the trusted integration. SQLite remains local/disposable only; any
production exception requires explicit scoped governance under ADR-0015.
2026-10-17 owner-integration refinement: T23 records the completed source/package
placement review, distinct from T21's source/disposable-SQL proof. Its dedicated
`rapp-identity-provisioner` recommendation is draft and unbound, not an accepted
package or existing repository. The full six-consumer rapp-postgres renderer
accepts the inert candidate on platform-pg-2 as its third of four declared
consumers; first-cell placement is refused as a fifth. Neither the estimated
four-consumer ceiling nor source requests establish native headroom or backup.
Next independent owner contracts: NetKingdom/package/substrate owners accept
exact deployment/rollback fate; rapp-postgres admits the dedicated consumer,
roles, five-connection budget and actual backup/restore; Platform admits exact
renewable runtime/migration delivery and TLS/CA/SAN; network owners scope SQL,
broker and checkpoint paths without assuming a narrow policy subtracts additive
same-namespace access. Protected caller owners must provide authoritative
GroupJournalBinding construction and current authorization; an independent
existing holder must authenticate and atomically advance the exact content
checkpoint, not a counter or ordinary Audit receipt. Directory/runtime owners
must select and prove either an exclusive all-writer maintenance barrier or
provider-supported CAS/generation, including established sessions, lifecycle
cascades and remove/readd ABA. No source review establishes those owner facts.
The default PostgreSQL activation and destructive native rollback remain refused.
Storage/lease acceptance can be proposed separately from LDAP mutation only after
exact owner source/target/custody/verification/recovery and cost/risk review;
State Hub guard approval grants none of it. Source-only integration review and
native acceptance matrix: Custodian native-storage-r1/OWNER-REVIEW.md. T14's
existing status, UUID, dependencies and native completion gate are retained.
2026-10-17 refinement: T20 retains the historical durable SQLite source and
actual disposable LLDAP proof. T21 extracts the completed PostgreSQL source and
bounded real disposable SQL compatibility proof. T14 remains `progress` for
@ -738,14 +766,33 @@ state_hub_task_id: "1b825705-4848-5edc-832f-b1622cd9bdb7"
```
Owner: NetKingdom for registration state, with Warden owning kernel evidence
admission. Next action: select the exact existing owner evidence holder/readback
contract and define authenticated lookup for the original creation attempt.
Completion requires restart/lost-receipt fixtures that bind case/source/principal,
original created-only provenance and current native revision before any undo;
uncertain outcomes remain non-replayable. The T13 adapter deliberately refuses
post-restart rollback and installs no reconciliation callback. A new broker or
unverified JSON receipt is not a recovery contract. Actual evidence-holder
acceptance and attended recovery remain separately authorized native work.
admission. T22 records the completed source review and exact existing-seam gaps;
T15 retains implementation and authenticated restart/lost-receipt acceptance.
Next action: identify the actual owner publisher/authenticator and bounded
original-attempt readback contract, and obtain Warden's reviewed original-intent
lookup/context interface through existing WARDEN-WP-0044-T03. The current Journal
retains attempt identity in historical rows but only hashes evidence references;
load drops that context and reconcile passes a fresh attempt to the callback.
The adapter's original payload and creation record remain process-local. Neither
that journal nor a current matching registry entry reconstructs creation authority.
The owner contract must bind the original Invocation, case/type/descriptor/catalog/
acceptance pins, source, principal and admitted tenant/realm provenance, separately
from the new reconciliation attempt and its independently revoked parent session.
Use an existing authenticated holder/readback; Audit Core's custody API is a
candidate only until its actual registration source/class, load-bearing emission
contract and separate scoped reader are admitted. No new broker or receipt trust
root is proposed. A missing original record, ambiguous publication, NO_CHANGE,
changed native revision or lost readback stays uncertain; no add replay or undo.
Completion requires restart/lost-receipt fixtures against the selected actual owner
contract: independently authenticated original created-only provenance, original
exact client entry and unchanged current postcreation UID/resourceVersion before
any undo, with wrong attempt/case/source/principal and unavailable-holder refusals.
The T13 adapter deliberately refuses post-restart rollback and installs no
reconciliation callback. Existing source review, JSON receipts and synthetic
trust do not satisfy native holder acceptance or authorize attended recovery.
Exact remaining owner facts: Custodian native-registration-r1/original-attempt-review.json.
## T16 — Select and review the actual machine descriptor composition
@ -757,11 +804,28 @@ state_hub_task_id: "200bf500-5084-5ae7-be26-3033fe1f296e"
```
Owner: NetKingdom for descriptor/type source and exact checker artifact; Warden
independently owns global provenance/admission. Next action: obtain review of the
exact checker source/artifact proposed by T17 and identify native owner evidence/
session callback contracts, then assemble the complete step descriptor set and
a proposed explicit type version selection. T13's historical descriptor retains
its synthetic checker; T17 supplies an artifact-bound proposal, not admission.
independently owns global provenance/admission. T22 records the completed seven-step
source1.2 inventory and regenerated register-client descriptor using the delivered
checker and current sealed-snapshot source; T16 retains exact owner review,
complete descriptor composition and admission. Next action: bind actual native
publisher/authenticator, original/current session verification and revocation,
executable storage/custody and config pins, then obtain the six missing fixed
owner descriptors/adapters for ensure-groups, mint-secret, deliver-secret,
bind-env, deliver-consumer-secret and hand-over. The inventory identifies each
existing owner seam, exact missing contract and created-only rollback requirement;
it is not an executable bundle. T14 and T15 remain independent prerequisites.
Review the dedicated consumer source1.2 composition only through the existing
explicit version-root selection. Keep canonical1.1/default and its accepted case
unchanged, and the source-only acknowledged1.2 reference case proposed without
acceptance fields. New descriptor bytes require a reviewed exact successor
revision/version under canon section6; they do not inherit historical source
acknowledgment. Changed authentication/materialization/trust needs the prescribed
informed-decision acknowledgment. Do not insert a partial descriptor map or use
--allow-unimplemented preparation as ordinary case/native admission. The registry
helper reports restart_required; separately review quiet-window restart/readiness
and actual consumer verification rather than infer activation from registry CAS.
Exact composition: Custodian native-registration-r1/descriptor-composition.json.
Completion requires separate owner source review of exact machine descriptors,
artifact/source/config pins, normal type/version admission and independent
evidence-verification boundaries. Warden d97652a refreshes producer validator
@ -1064,3 +1128,90 @@ exact source/pre/postimages and retained predecessors; native R5 manifest
storage/HA/backup/lease/TLS/capacity/restore admission, authoritative protected
callbacks, real LDAP writer fencing/CAS/ABA exclusion, owner assent or production
endpoint activation is claimed. Those remain live T14 work.
## T22 — Establish registration recovery source gaps and complete descriptor review inventory
```task
id: NK-WP-0048-T22
status: done
priority: high
state_hub_task_id: "ea50d93b-0d04-55d2-98c4-9b6c3fe925b1"
```
Completed 2026-10-17: source-only owner-contract review and complete review
inventory. Inspected the actual registration adapter, Warden Journal/Executor,
NetKingdom evidence compatibility checker and Audit Core custody/read surfaces.
No existing admitted holder/readback supplies authenticated original-attempt
recovery: payload/creation are process-local, persisted evidence references are
hashed, and reconcile receives a new attempt without original context. The exact
four-owner contract requirements and non-replayable missing/ambiguous cases are
recorded; no recovery callback, publisher, credential grant or native trust root
was installed. T15 retains implementation and actual acceptance.
The complete source1.2 inventory maps all seven follow-through steps to their
existing owners, missing exact contracts and created-only rollback requirements.
The actual maintained adapter regenerated the sole existing register-client
machine descriptor against the delivered 13,633,409-byte checker and current
sealed-memfd snapshot source. The checker rehash and Warden Descriptor.decode
round-trip passed; six owner descriptors remain absent. Actual source commands
render canonical1.1 and explicitly selected1.2 with execution.permitted false;
ordinary1.2 case admission still refuses its acknowledged/unimplemented type.
Canonical type/case/acknowledgment bytes were hash-checked unchanged. No tests were
added or rerun, no native configuration or effect occurred, and no owner assent
or complete machine bundle is claimed. T16 retains native executable custody,
complete exact descriptors, independent source review and normal version/type
admission. This extracted outcome supplies no native gate completion credit.
Evidence in the-custodian:
`docs/changes/netkingdom-target-2026-10-17/native-registration-r1/`, especially
original-attempt-review.json, descriptor-composition.json, source-verification.json,
source-manifest.json and manifest.json. The prior sixteen-gate audit remains
immutable. Publication, normal task UUID allocation and projection are separate
coordinator follow-through; no Hub UUID is assigned by this source record.
## T23 — Review native journal package assignment and storage integration boundaries
```task
id: NK-WP-0048-T23
status: done
priority: high
state_hub_task_id: "6e7cd483-c7af-53a2-9d0a-3d76b6fb49ee"
```
Completed 2026-10-17: source/package placement and protected integration review
only, separate from T21's completed PostgreSQL source and disposable-SQL proof.
The owner review recommends a dedicated independently rolled-back
`rapp-identity-provisioner`, owned by NetKingdom on rail-kubernetes, with a
schema-valid draft declaration and no reef binding. No repository, registry
entry, native package assignment or owner assent was created. Existing
rapp-user-engine ownership/token delivery does not assign it the provisioner.
The complete current rapp-postgres catalog has six declared consumers: four on
platform-pg and two on platform-pg-2. The inert renderer-valid provisioner
candidate fits platform-pg-2 at three of four, requesting one migration plus
four runtime connections; placing it on the first cell is refused as a fifth.
Normative package schema and full-catalog capacity/render checks passed. The
four-consumer ceiling is estimated, not measured native headroom, and no current
SQL session, backup or restore acceptance is claimed.
The exact review identifies remaining package/substrate, SQL role/lease/TLS,
network and backup/recovery contracts; independently authenticated protected
scope callbacks; an external content-bound checkpoint holder with atomic
advance/loss handling; and all-LDAP-writer exclusion or provider CAS/generation,
including lifecycle cascades and remove/readd ABA. Generic Audit custody is not
that checkpoint service. A database-only fence, final reread or one replica
cannot exclude foreign directory writers. The native acceptance/recovery matrix
keeps storage/lease admission separate from separately approved directory
mutation; the production activation remains disabled pending trusted integration.
Evidence in the-custodian:
`docs/changes/netkingdom-target-2026-10-17/native-storage-r1/OWNER-REVIEW.md`,
source-validation.json and hash-bound source-references.json, plus the inert
proposed declaration/consumer and renderer outputs. No native resource, lease,
credential, deployment, destructive effect, independent holder or native owner
acceptance was delivered. T14 retains these live gates with its original UUID
and progress status. This completed review adds no duplicate T21 acceptance
credit; publication, task UUID allocation and projection remain coordinator
follow-through and no UUID is hand-assigned here.

View file

@ -1130,6 +1130,16 @@ Evidence: Custodian October17 `secrets-engine-projection.json` and October16
`recovery-owner-handoffs-r1` source review; independent ID/UUID/status/remote
readback is recorded separately when performed.
2026-10-17 source checkpoint refinement: the completed exact-one original
selector guard and its bounded refusal/unique-selector proof are extracted into
T38. T36 retains exact archive-to-selector binding, current actual service-owner
recovery assent and outage-compatible private admission/consumer preparation.
The current eight-field source-selector correspondence is evidenced separately;
it does not complete those remaining obligations or any T18/T19 native gate.
T33 continues to require the complete reviewed TLS/custody/admission packet.
This extraction records one source outcome rather than counting it again as
original registration validation or consumer continuity.
## Verify actual SOPS compatibility and sealed-descriptor read/write custody
```task
@ -1170,6 +1180,52 @@ T33 attempt readiness, T18 actual recovery/consumer acceptance, T19 retained MFA
T35 TLS/trust disposition and T36 original registration/private admission remain
open under their existing owner boundaries.
## Deliver exact-one original selector guard and bounded source refusal proof
```task
id: NK-WP-0049-T38
status: done
priority: high
assignee: codex
depends_on: [NK-WP-0049-T34]
state_hub_task_id: "76dde6b9-5ee8-50d3-bf15-461d6d5f29b8"
```
Completed 2026-10-17 as source preparation, independently reviewed by the
coordinating Custodian agent. The adapter now requires exactly one selected
registration in the already-restored archive configuration before further runtime
environment reads, signer-key parsing or native checker invocation. Absent,
empty, identical-duplicate and conflicting-duplicate selectors refuse before
those later inputs; the existing unique-selector mocked native path still passes.
Current KeyCape Go configuration also rejects duplicate IDs; this makes the
adapter's own boundary explicit and establishes no native duplicate exploit.
The guard cannot run before archive decryption or the original key prompt.
The 39 adapter checks passed, including four added early-refusal regressions.
Running those four cases against the retained original adapter failed the new
refusal contract as expected. The broader 77-check source run retained 76 passes
and one stale fixture-proposal source-hash refusal. Only the two affected hashes
and scope of that unexecuted/unapproved successor proposal were then refreshed;
all four affected fixture-plan checks passed. The original manifest preimage and
failure receipt remain retained, and frozen actual R6 runtime receipts were not
changed or claimed as proof of this successor. No broader native rerun occurred.
Evidence: Custodian
`docs/changes/netkingdom-target-2026-10-17/native-identity-r1/`:
`owner-source.patch`, `verification.json`, `manifest.json`, retained preimages,
`original-behavior-regression.txt` and `focused-regressions.stderr.txt`.
The later task extraction and exact workplan successor bytes are in
`refinement-r2/`; the original source-proof packet remains immutable.
Canonical owner publication, allocator-assigned Hub UUID and projection must be
recorded separately through the normal owner process; none is claimed here.
This completes only the source guard/refusal deliverable extracted from T36.
T35 original TLS public provenance/private custody or accepted reissue, T36 exact
archive binding/current owner assent/consumer custody preparation, T33 complete
attempt readiness, T18 actual recovery/consumer acceptance and T19 retained-factor
MFA remain open. No actual archive, private key, Secret object, token exchange,
OpenBao login, rotation, production action or owner assent was exercised.
## Accept remaining recovery and caller evidence and close the G08 handoff
```task
@ -1189,7 +1245,7 @@ Owner boundary: NetKingdom, Platform and affected service owners, with Custodian
G08 receiving the final evidence. The completed archive/job milestones remain
accepted independently of this final gate.
**Done when:** T02 and T15–T37 have their stated completed or explicitly accepted
**Done when:** T02 and T15–T38 have their stated completed or explicitly accepted
handoff outcomes; service authentication/token/audit proof and current/legacy
caller dispositions are accepted; the source workplan and projected tasks agree;
and G08 has an exact evidence/handoff record. Record actionable residuals as live