feat(recovery): prepare owner-bound offline TLS continuity preflight
Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a1468b-63fa-7710-85dc-8f2e16b3ebfa
This commit is contained in:
parent
cac8eabef5
commit
c562d7134c
5 changed files with 273 additions and 0 deletions
51
docs/keycape-tls-recovery-boundary.md
Normal file
51
docs/keycape-tls-recovery-boundary.md
Normal 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.
|
||||
34
tests/test_public_tls_metadata.py
Normal file
34
tests/test_public_tls_metadata.py
Normal 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)
|
||||
55
tools/public-tls-metadata.py
Normal file
55
tools/public-tls-metadata.py
Normal 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())
|
||||
|
|
@ -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
|
||||
|
|
|
|||
132
workplans/RCLUSTER-WP-0018-keycape-original-tls-recovery.md
Normal file
132
workplans/RCLUSTER-WP-0018-keycape-original-tls-recovery.md
Normal 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue