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

Assistant: codex
Assistant-Model: gpt-6.1-sol
Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
This commit is contained in:
codex 2026-10-17 02:12:35 +02:00
parent cac8eabef5
commit c562d7134c
5 changed files with 273 additions and 0 deletions

View file

@ -0,0 +1,51 @@
# Original KeyCape TLS recovery boundary
Source disposition, 2026-10-17, scoped to railiance-cluster INTENT: cluster runtime,
ingress/certificate plumbing and backup posture. This is source preparation only.
It is not native acceptance, an issuer/key custody assent, a reissue decision or
permission to mutate the cluster.
NetKingdom's ingress source names `kc.coulomb.social`, `sso/kc-tls`,
`ClusterIssuer/letsencrypt-prod` and Traefik termination before KeyCape8080.
NetKingdom owns that namespace intent; railiance-cluster owns cert-manager,
ingress controller and cluster backup posture. No inspected source establishes
who independently holds the original private TLS recovery material when the
ordinary issuer/cluster is unavailable. A missing exact routing-catalog entry
cannot prove absence of an owning subsystem.
The identity archive excludes `kc-tls`. Current S2 backup source encrypts SQLite
state, Helm values and kubeconfig to the same host's `/opt/backup/railiance/cluster`
and retains seven versions. No inspected source proves an actually fresh,
independent offsite object or original certificate/key/trust recovery. Archived
RAIL-BS-WP-0004 is historical local-backup delivery; backlog RCLUSTER-WP-0007-T05
is gated HA issuance/renewal; active RCLUSTER-WP-0017 is signer runtime/storage.
None is an existing live task for this original TLS recovery demand.
`railiance-backup-s2` also stages raw state/Helm output in named `/tmp` paths
without early hard-loss cleanup; `railiance-restore-s2` prints whole-state restore
instructions. Do not use either as an isolated TLS extraction path. RCLUSTER-WP-0018
tracks exact inventory, safe private staging/extraction preparation where required,
independent outage proof and future archive-boundary disposition. No original
ciphertext or historical outcome is rewritten by that work.
`tools/public-tls-metadata.py` is an offline public-only step. After the source
owner identifies independent original **public certificate/chain and trust files**:
```sh
python3 tools/public-tls-metadata.py --public-cert ORIGINAL_PUBLIC_CERT_CHAIN.pem --public-trust ORIGINAL_PUBLIC_TRUST.pem
```
It accepts no private-key argument, bounds regular-file reads, refuses symlinks and
non-certificate PEM material, and reports public hashes/SANs/validity/counts only.
The file names are owner-local placeholders, not a key request. Output establishes
public metadata, not originality, hostname/chain acceptance, reissue permission or
custody. Do not supply a Secret object or combined certificate/key PEM. Fail closed
when original source, trust or exact bindings are missing; no fresh self-signed
trust workaround is admitted.
For FUTURE NetKingdom archives, the source owners must explicitly disposition
original TLS coverage or a reviewed reissue contract: producer/custody owner,
private artifact boundary, offline recovery independence, original public hashes,
scoped capture/read permissions, retention and native verification. Merely adding
`kc-tls` to secret_refs would change capture/permission/custody scope and is not
authorized by this source review. It cannot repair the current archive retroactively.

View file

@ -0,0 +1,34 @@
import importlib.util
from pathlib import Path
from datetime import datetime,timedelta,timezone
from unittest.mock import patch
import pytest
from cryptography import x509
from cryptography.hazmat.primitives import hashes,serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID
spec=importlib.util.spec_from_file_location('metadata',Path(__file__).parents[1]/'tools/public-tls-metadata.py');m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m)
@pytest.fixture
def cert(tmp_path):
key=rsa.generate_private_key(public_exponent=65537,key_size=2048);name=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,'synthetic.invalid')]);now=datetime.now(timezone.utc)
cert=(x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(key.public_key()).serial_number(1).not_valid_before(now-timedelta(minutes=1)).not_valid_after(now+timedelta(hours=1)).add_extension(x509.SubjectAlternativeName([x509.DNSName('synthetic.invalid')]),critical=False).sign(key,hashes.SHA256()))
p=tmp_path/'public.pem';p.write_bytes(cert.public_bytes(serialization.Encoding.PEM));return p
def test_public_only_metadata_and_hashes(cert):
result=m.describe(cert,cert)
assert result['leaf_dns_sans']==['synthetic.invalid'];assert result['network_contacted'] is False
assert '-----BEGIN' not in str(result)
def test_mixed_or_private_pem_is_refused(cert):
cert.write_bytes(cert.read_bytes()+b'\n-----BEGIN PRIVATE KEY-----\nsynthetic\n-----END PRIVATE KEY-----')
with pytest.raises(ValueError):m.public_bundle(cert)
def test_oversize_is_refused_before_read(cert):
with patch.object(m,'LIMIT',1),patch.object(m.os,'open') as opened:
with pytest.raises(ValueError):m.public_bundle(cert)
opened.assert_not_called()
def test_symlink_refused(cert):
link=cert.with_name('link.pem');link.symlink_to(cert)
with pytest.raises(ValueError):m.public_bundle(link)

View file

@ -0,0 +1,55 @@
#!/usr/bin/env python3
"""Owner-run public certificate/trust metadata; no private-key argument or network.
The owner supplies public-only PEM files from the independently established
original recovery source. This does not identify that source or accept reissue.
"""
import argparse
from datetime import timezone
import hashlib
import json
import os
from pathlib import Path
import re
import stat
from cryptography import x509
from cryptography.hazmat.primitives import serialization
LIMIT=1024*1024
CERT=re.compile(br'-----BEGIN CERTIFICATE-----\r?\n[A-Za-z0-9+/=\r\n]+-----END CERTIFICATE-----')
def public_bundle(path):
# Bound the read before allocation; refuse every non-certificate PEM block.
path=Path(os.path.abspath(path))
if any(p.is_symlink() for p in (path,*path.parents)) or not stat.S_ISREG(path.stat().st_mode) or not 0<path.stat().st_size<=LIMIT:raise ValueError()
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
with os.fdopen(fd,'rb') as source:
if not stat.S_ISREG(os.fstat(source.fileno()).st_mode) or not 0<os.fstat(source.fileno()).st_size<=LIMIT:raise ValueError()
raw=source.read(LIMIT+1)
if len(raw)>LIMIT:raise ValueError()
blocks=CERT.findall(raw)
if not blocks or len(blocks)>512 or CERT.sub(b'',raw).strip():raise ValueError()
return raw,[x509.load_pem_x509_certificate(block) for block in blocks]
def describe(cert_path,trust_path):
certificate,chain=public_bundle(cert_path);trust,roots=public_bundle(trust_path);leaf=chain[0]
try:names=leaf.extensions.get_extension_for_class(x509.SubjectAlternativeName).value.get_values_for_type(x509.DNSName)
except x509.ExtensionNotFound:names=[]
spki=leaf.public_key().public_bytes(serialization.Encoding.DER,serialization.PublicFormat.SubjectPublicKeyInfo)
def utc_date(name):
value=getattr(leaf,name+'_utc',None)
if value is None:value=getattr(leaf,name).replace(tzinfo=timezone.utc)
return value.isoformat()
return {'schema':'nk49.public-tls-metadata.v1','status':'public_metadata_only_not_originality_or_owner_assent',
'tls_cert_sha256':hashlib.sha256(certificate).hexdigest(),'trust_sha256':hashlib.sha256(trust).hexdigest(),
'leaf_public_spki_sha256':'sha256:'+hashlib.sha256(spki).hexdigest(),'leaf_dns_sans':names,
'leaf_not_before':utc_date('not_valid_before'),'leaf_not_after':utc_date('not_valid_after'),
'server_chain_certificates':len(chain),'trust_certificates':len(roots),'network_contacted':False,'private_key_argument_supported':False}
def main():
p=argparse.ArgumentParser(description=__doc__);p.add_argument('--public-cert',required=True);p.add_argument('--public-trust',required=True);a=p.parse_args()
try:result=describe(a.public_cert,a.public_trust)
except Exception:print('{"status":"public_metadata_refused","private_diagnostics_suppressed":true}');return 1
print(json.dumps(result,indent=2));return 0
if __name__=='__main__':raise SystemExit(main())

View file

@ -20,6 +20,7 @@ state_hub_workstream_id: "05ef985a-656e-5d28-970a-db1300a7b648"
id: RCLUSTER-WP-ADHOC-2026-07-27-T01
status: done
priority: high
state_hub_task_id: "3e3f39d3-5070-5ba3-97db-19f5f361f264"
```
Enable only `kubernetes.podspec-init-containers` in Knative

View file

@ -0,0 +1,132 @@
---
id: RCLUSTER-WP-0018
type: workplan
title: "Establish original KeyCape TLS recovery and future archive boundary"
domain: financials
repo: railiance-cluster
status: ready
owner: codex
topic_slug: railiance
created: "2026-10-17"
updated: "2026-10-17"
flavor: implementation
depends_on: []
related: [CUST-WP-0080, NK-WP-0049, RCLUSTER-WP-0007, RCLUSTER-WP-0017]
state_hub_workstream_id: "e0a7813d-c916-5e49-84b2-5e5b30441891"
---
Required owner source work for NK-WP-0049-T35 and CUST-WP-0080 G08. Reviewed
against current INTENT/SCOPE, backup sources and existing work records; ready
means work can proceed, not privileged execute authority. This does not promote
HA/backlog work, reopen archived local backup delivery or duplicate signer0017.
Source-owner scope is certificate plumbing/backup posture; NetKingdom owns
namespace ingress, while exact original independent TLS custody remains to be
established. No secret read, reissue, live change or native assent occurred.
## Record source boundary and public-only metadata preparation
```task
id: RCLUSTER-WP-0018-T01
status: done
priority: high
assignee: codex
state_hub_task_id: "7280193d-0d59-5b78-a2d3-96ebd127127f"
```
Completed 2026-10-17 as isolated source preparation. Independently reviewed the
17 public source references against current owner bytes, checked current owner
instructions/environment/supervision and existing plans, and recorded the exact
boundary in docs/keycape-tls-recovery-boundary.md. Prepared bounded public-only
certificate/trust metadata projection with generated-certificate tests; no actual
certificate/key read. Source integration/publication and task projection remain
separate receipts, not claimed by this completed preparation task.
Evidence: the-custodian docs/changes/netkingdom-target-2026-10-16/
recovery-owner-handoffs-r1/ and prior recovery-attempt-r1/t35-t36-source-next/.
## Establish exact independent original TLS artifact and provider custody metadata
```task
id: RCLUSTER-WP-0018-T02
status: todo
priority: high
assignee: codex
state_hub_task_id: "90836435-917f-504c-a711-3f29ece348e3"
```
Next: identify the actual original sso/kc-tls custody owner and independent public
provenance through existing owner processes. Record artifact/provider, timestamp,
public certificate/trust hashes, private custody entry point and outage dependencies
without values. Source review cannot infer a fresh backup, original key coverage
or independent decryption custody from a local backup script. Use credential
routing before any later credential need; missing route is not proof of absence.
Done when exact independent recovery or an explicitly owned unavailable capability
is evidenced as metadata and a reviewed next action, with no guessed assent.
## Prepare safe isolated original extraction or explicit reissue alternative
```task
id: RCLUSTER-WP-0018-T03
status: wait
priority: high
assignee: codex
depends_on: [RCLUSTER-WP-0018-T02]
blocking_reason: "Exact original artifact/provider/custody disposition is required before selecting extraction or reissue source."
state_hub_task_id: "5dfa9a3a-2639-5cf6-b0a4-13eef417d47a"
```
From T02, reuse the actual owner path to prepare an exact attended private-only
recovery command and full dependency/cleanup proof. Do not run whole-state restore
or expose raw state/Secret output. If existing backup staging is required, correct
only the needed path with bounded memory-only staging and early exact cleanup,
including error/parent-loss tests before use. Original recovery and accepted
reissue are distinct claims; no self-signed trust substitute, new broker or
implicit production action. Done when the exact source proposal, public metadata
preflight, negatives, timeout, abort and custody obligations are reviewed.
## Verify admitted original TLS recovery through the actual owner boundary
```task
id: RCLUSTER-WP-0018-T04
status: wait
priority: high
assignee: codex
needs_human: true
depends_on: [RCLUSTER-WP-0018-T02, RCLUSTER-WP-0018-T03]
blocking_reason: "Await exact reviewed attended owner custody path and its existing scoped authority; no private input or native attempt requested yet."
state_hub_task_id: "021b07bb-65bc-5743-bac5-4294b4be74f6"
```
Only the actual admitted owner action can prove recovered certificate/key pairing,
original hostname/trust and independent outage operation, or the expressly accepted
reissue result. Retain exact source/artifact/target/identity/negative/cleanup receipts.
Human custody/authority gates are not worker wakeups. Source and historical local
backup proof never fill native assent. Return bounded public proof to NK49T35;
no production mutation, credential retrieval or approval is granted by this task.
## Disposition original TLS coverage in the future identity archive contract
```task
id: RCLUSTER-WP-0018-T05
status: todo
priority: high
assignee: codex
state_hub_task_id: "5bc67a22-0991-5625-86a6-f749560d8a0e"
```
Next: deliver the source-owned requirement in docs/keycape-tls-recovery-boundary.md
to NetKingdom's existing contract process: original TLS custody or accepted reissue,
independent provider/key recovery, public identity hashes, scoped capture/read
permissions and verification/retention. Record named owner dependencies as live
work before closure. Do not append kc-tls to secret_refs, expand capture credentials
or rewrite a historical archive without those owner reviews/authority. Done when
the future contract requirement has an exact tracked disposition/handoff; it is
not proof of current archive coverage or actual future capture.
## Coordination and completion
Beneficiary NK-WP-0049-T35/CUST-WP-0080 remains linked to this obligor record by
derived cross_owner_wait case nk49-t35-rcluster18 in the packet. No new project
driver, live worker injection or message was created. Own T03 wait has an explicit
local prerequisite; T04 is a human gate. Close only after source/native obligations
are proved or an explicit bounded scope disposition hands off remaining live work.