Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a126e1-76f8-7643-8cf3-e93336539ade
712 lines
33 KiB
Python
712 lines
33 KiB
Python
#!/usr/bin/env python3
|
|
"""Founder-attended KEY39 lifecycle. Silent; private values never leave memory/K8s.
|
|
|
|
inspect is metadata-only (no Secret read). execute requires an exact approved
|
|
envelope, source/runtime pins and founder-held private tokens on stdin. No login,
|
|
custody fetch, production credential, role or policy mutation is implemented.
|
|
"""
|
|
import argparse
|
|
import base64
|
|
import copy
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import math
|
|
import os
|
|
from pathlib import Path
|
|
import secrets
|
|
import signal
|
|
import subprocess
|
|
import threading
|
|
import time
|
|
from contextlib import contextmanager
|
|
import urllib.parse
|
|
import urllib.request
|
|
import urllib.error
|
|
|
|
import yaml
|
|
|
|
CLUSTER = 'a553c742-0115-43d4-99a4-a5ca56fe0786'
|
|
TARGET = 'sso/keycape'
|
|
PROPOSAL = 'KEY-WP-0039-T02/attended-lifecycle-v5'
|
|
IDS = ['openbao-probe-' + c for c in ['aud', 'sub', 'tenant', 'roles', 'scope', 'expiry']]
|
|
ENVS = ['KEYCAPE_' + c.upper().replace('-', '_') + '_CLIENT_SECRET' for c in IDS]
|
|
TEMP = 'keycape-openbao-negative-key39'
|
|
BAO_ORIGINS = {'http://127.0.0.1:18200', 'http://openbao.openbao.svc:8200'}
|
|
REQUEST_DEADLINE = None
|
|
BUSINESS_MONOTONIC_DEADLINE = None
|
|
CLEANUP_MONOTONIC_DEADLINE = None
|
|
CLEANING = False
|
|
MAX_RESPONSE = 256 * 1024
|
|
HTTP_TOTAL_SECONDS = 15
|
|
CLEANUP_TOTAL_SECONDS = 300
|
|
HTTP_ABORT_SECONDS = 0.02
|
|
|
|
|
|
class Refused(Exception):
|
|
pass
|
|
|
|
|
|
def require(ok, reason):
|
|
if not ok:
|
|
raise Refused(reason)
|
|
|
|
|
|
def digest(data):
|
|
return hashlib.sha256(data).hexdigest()
|
|
|
|
|
|
def canonical(value):
|
|
return digest(json.dumps(value, sort_keys=True, separators=(',', ':')).encode())
|
|
|
|
|
|
def finite_number(value):
|
|
try:
|
|
return type(value) in (int, float) and math.isfinite(value)
|
|
except OverflowError:
|
|
return False
|
|
|
|
|
|
def read_json(raw):
|
|
def reject_constant(_):
|
|
raise Refused('nonfinite_json_constant')
|
|
return json.loads(raw, parse_constant=reject_constant)
|
|
|
|
|
|
def begin_cleanup():
|
|
global CLEANUP_MONOTONIC_DEADLINE
|
|
if CLEANUP_MONOTONIC_DEADLINE is None:
|
|
CLEANUP_MONOTONIC_DEADLINE = time.monotonic() + CLEANUP_TOTAL_SECONDS
|
|
|
|
|
|
def check_deadline():
|
|
if CLEANING:
|
|
require(CLEANUP_MONOTONIC_DEADLINE is not None
|
|
and time.monotonic() < CLEANUP_MONOTONIC_DEADLINE, 'cleanup_budget_expired')
|
|
return
|
|
if REQUEST_DEADLINE is not None and not CLEANING:
|
|
require(finite_number(REQUEST_DEADLINE), 'approved_window_invalid')
|
|
require(time.time() < REQUEST_DEADLINE, 'approved_window_expired')
|
|
if BUSINESS_MONOTONIC_DEADLINE is not None:
|
|
require(time.monotonic() < BUSINESS_MONOTONIC_DEADLINE, 'session_business_budget_expired')
|
|
|
|
|
|
def request_budget(cap, cleanup=False):
|
|
require(finite_number(cap) and 0 < cap <= 30, 'request_timeout_invalid')
|
|
budget = float(cap)
|
|
if cleanup or CLEANING:
|
|
if CLEANUP_MONOTONIC_DEADLINE is not None:
|
|
budget = min(budget, CLEANUP_MONOTONIC_DEADLINE - time.monotonic())
|
|
else:
|
|
check_deadline()
|
|
if REQUEST_DEADLINE is not None:
|
|
budget = min(budget, REQUEST_DEADLINE - time.time())
|
|
if BUSINESS_MONOTONIC_DEADLINE is not None:
|
|
budget = min(budget, BUSINESS_MONOTONIC_DEADLINE - time.monotonic())
|
|
require(budget > 0 and math.isfinite(budget), 'request_window_exhausted')
|
|
return budget
|
|
|
|
|
|
@contextmanager
|
|
def total_timeout(seconds):
|
|
"""Linux main-thread hard elapsed bound, including blocking headers/body reads."""
|
|
require(finite_number(seconds) and 0 < seconds <= 30, 'request_timeout_invalid')
|
|
require(threading.current_thread() is threading.main_thread()
|
|
and hasattr(signal, 'setitimer'), 'main_thread_linux_timer_required')
|
|
previous_timer = signal.getitimer(signal.ITIMER_REAL)
|
|
require(previous_timer == (0.0, 0.0), 'existing_alarm_timer_refused')
|
|
previous_handler = signal.getsignal(signal.SIGALRM)
|
|
def expired(*_):
|
|
raise Refused('request_total_timeout')
|
|
started = time.monotonic()
|
|
signal.signal(signal.SIGALRM, expired)
|
|
signal.setitimer(signal.ITIMER_REAL, seconds)
|
|
try:
|
|
yield
|
|
require(time.monotonic() - started <= seconds, 'request_total_timeout')
|
|
finally:
|
|
signal.setitimer(signal.ITIMER_REAL, 0)
|
|
signal.signal(signal.SIGALRM, previous_handler)
|
|
|
|
|
|
class NoRedirect(urllib.request.HTTPRedirectHandler):
|
|
def redirect_request(self, *args, **kwargs):
|
|
return None
|
|
|
|
|
|
class SessionTransport:
|
|
"""Fixed origins, no ambient proxy, bounded reads, same guards in cleanup."""
|
|
def __init__(self, bao_origin, receipt=None):
|
|
require(bao_origin in BAO_ORIGINS, 'bao_origin_invalid')
|
|
self.bao_origin = bao_origin
|
|
self.receipt = receipt if receipt is not None else {}
|
|
self.opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect())
|
|
|
|
def request(self, url, *, data=None, headers=None, method='POST', timeout=15):
|
|
allowed = {'https://kc.coulomb.social/token',
|
|
'https://kc.coulomb.social/.well-known/openid-configuration',
|
|
self.bao_origin + '/v1/auth/keycape-services/login',
|
|
self.bao_origin + '/v1/auth/token/revoke-self'}
|
|
require(url in allowed, 'http_target_not_reviewed')
|
|
require(method == ('GET' if url.endswith('/.well-known/openid-configuration') else 'POST'),
|
|
'http_method_not_reviewed')
|
|
cleanup = url.endswith('/v1/auth/token/revoke-self')
|
|
budget = request_budget(min(timeout, HTTP_TOTAL_SECONDS), cleanup=cleanup)
|
|
total_deadline = time.monotonic() + budget
|
|
req = urllib.request.Request(url, data=data, headers=headers or {}, method=method)
|
|
response, status, raw = None, None, None
|
|
try:
|
|
with total_timeout(budget):
|
|
try:
|
|
response = self.opener.open(req, timeout=budget)
|
|
except urllib.error.HTTPError as error:
|
|
response = error
|
|
except (urllib.error.URLError, OSError):
|
|
if url == self.bao_origin + '/v1/auth/keycape-services/login':
|
|
self.receipt['openbao_login_outcome_unknown'] = True
|
|
return None, b''
|
|
status = response.getcode()
|
|
raw = response.read(MAX_RESPONSE + 1)
|
|
require(len(raw) <= MAX_RESPONSE, 'http_response_oversized')
|
|
response.close()
|
|
response = None
|
|
if not cleanup:
|
|
check_deadline()
|
|
return status, raw
|
|
except Refused:
|
|
if url == self.bao_origin + '/v1/auth/keycape-services/login' and status in (None, 200):
|
|
# A late response cannot pass; preserve acquired-token cleanup when available.
|
|
if raw is not None and status == 200:
|
|
self.revoke_late_session(raw)
|
|
else:
|
|
self.receipt['openbao_login_outcome_unknown'] = True
|
|
raise
|
|
except (urllib.error.URLError, OSError):
|
|
if url == self.bao_origin + '/v1/auth/keycape-services/login':
|
|
if raw is not None and status == 200:
|
|
self.revoke_late_session(raw)
|
|
else:
|
|
self.receipt['openbao_login_outcome_unknown'] = True
|
|
return None, b''
|
|
except Exception:
|
|
if url == self.bao_origin + '/v1/auth/keycape-services/login':
|
|
if raw is not None and status == 200:
|
|
self.revoke_late_session(raw)
|
|
else:
|
|
self.receipt['openbao_login_outcome_unknown'] = True
|
|
raise Refused('http_response_failed') from None
|
|
finally:
|
|
if response is not None:
|
|
self.abort_response(response, total_deadline)
|
|
|
|
def abort_response(self, response, original_deadline):
|
|
"""One bounded exceptional close; never retry or reset the cleanup clock."""
|
|
self.receipt['http_abort_close_attempted'] = False
|
|
self.receipt['http_abort_close_succeeded'] = False
|
|
begin_cleanup()
|
|
try:
|
|
cap = request_budget(HTTP_ABORT_SECONDS, cleanup=True)
|
|
remaining = original_deadline - time.monotonic()
|
|
if remaining > 0:
|
|
cap = min(cap, remaining)
|
|
self.receipt['http_abort_close_attempted'] = True
|
|
with total_timeout(cap):
|
|
response.close()
|
|
self.receipt['http_abort_close_succeeded'] = True
|
|
except Exception:
|
|
self.receipt['http_response_abort_unconfirmed'] = True
|
|
raise Refused('http_response_abort_unconfirmed') from None
|
|
|
|
def revoke_late_session(self, raw):
|
|
self.receipt['late_acceptance_revoke_attempted'] = False
|
|
self.receipt['late_acceptance_revoke_succeeded'] = False
|
|
try:
|
|
token = read_json(raw)['auth']['client_token']
|
|
require(isinstance(token, str) and token, 'late_session_response_invalid')
|
|
except (KeyError, TypeError, ValueError, Refused):
|
|
self.receipt['openbao_login_outcome_unknown'] = True
|
|
return
|
|
begin_cleanup()
|
|
self.receipt['late_acceptance_revoke_attempted'] = True
|
|
try:
|
|
status, _ = self.post(self.bao_origin + '/v1/auth/token/revoke-self',
|
|
body={}, headers={'X-Vault-Token': token})
|
|
self.receipt['late_acceptance_revoke_succeeded'] = status == 204
|
|
except Exception:
|
|
self.receipt['late_acceptance_revoke_succeeded'] = False
|
|
finally:
|
|
del token
|
|
|
|
def post(self, url, form=None, body=None, headers=None, timeout=15):
|
|
headers = dict(headers or {})
|
|
if form is not None:
|
|
data = urllib.parse.urlencode(form).encode()
|
|
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
else:
|
|
data = json.dumps(body or {}).encode()
|
|
headers['Content-Type'] = 'application/json'
|
|
return self.request(url, data=data, headers=headers, timeout=timeout)
|
|
|
|
def discovery(self):
|
|
status, raw = self.request('https://kc.coulomb.social/.well-known/openid-configuration', method='GET')
|
|
require(status == 200, 'discovery_failed')
|
|
return json.loads(raw)
|
|
|
|
|
|
def k(arguments, payload=None):
|
|
# Execute on railiance01 in the founder's session, never workstation defaults.
|
|
check_deadline()
|
|
budget = request_budget(30)
|
|
with total_timeout(budget):
|
|
result = subprocess.run(['kubectl', '--kubeconfig=/etc/rancher/k3s/k3s.yaml',
|
|
'--request-timeout=20s', *arguments],
|
|
input=None if payload is None else json.dumps(payload),
|
|
text=True, capture_output=True, timeout=budget)
|
|
check_deadline()
|
|
require(result.returncode == 0, 'kubernetes_operation_failed')
|
|
return json.loads(result.stdout) if result.stdout.strip() else None
|
|
|
|
|
|
def get(kind, name):
|
|
return k(['-n', 'sso', 'get', kind, name, '-o', 'json'])
|
|
|
|
|
|
def meta(obj):
|
|
return {key: obj['metadata'][key] for key in ['uid', 'resourceVersion']}
|
|
|
|
|
|
def patch(kind, obj, path, value, dry=False):
|
|
ops = [{'op': 'test', 'path': '/metadata/' + key, 'value': val}
|
|
for key, val in meta(obj).items()]
|
|
ops.append({'op': 'replace', 'path': path, 'value': value})
|
|
args = ['-n', 'sso', 'patch', kind, obj['metadata']['name'], '--type=json',
|
|
'--patch-file=/dev/stdin', '-o', 'json']
|
|
if dry:
|
|
args += ['--dry-run=server']
|
|
return k(args, ops)
|
|
|
|
|
|
def registrations():
|
|
reference = {'displayName': 'Temporary KEY39 negative probe',
|
|
'allowedScopes': ['openbao:login'], 'grantTypes': ['client_credentials'],
|
|
'clientType': 'confidential', 'serviceSubject': 'service:secrets-engine',
|
|
'tenant': 'tenant:platform', 'roles': ['secrets-engine'], 'tokenLifetime': '1m'}
|
|
result = []
|
|
for index, client_id in enumerate(IDS):
|
|
c = dict(reference, clientId=client_id, secretRef='env:' + ENVS[index])
|
|
if index:
|
|
c['audience'] = 'secrets-engine-openbao'
|
|
if index == 1:
|
|
c['serviceSubject'] = 'service:openbao-probe'
|
|
if index == 2:
|
|
c['tenant'] = 'tenant:openbao-probe'
|
|
if index == 3:
|
|
c['roles'] = ['openbao-probe']
|
|
if index == 4:
|
|
c['allowedScopes'] = ['openbao:probe']
|
|
result.append(c)
|
|
return result
|
|
|
|
|
|
class UniqueLoader(yaml.SafeLoader):
|
|
pass
|
|
|
|
|
|
def unique(loader, node):
|
|
result = {}
|
|
for key_node, value_node in node.value:
|
|
key = loader.construct_object(key_node)
|
|
require(key not in result, 'duplicate_yaml_key')
|
|
result[key] = loader.construct_object(value_node)
|
|
return result
|
|
|
|
|
|
UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, unique)
|
|
|
|
|
|
def candidate_config(raw):
|
|
config = yaml.load(raw, Loader=UniqueLoader)
|
|
clients = config['clients']
|
|
require(isinstance(clients, list) and clients, 'clients_required')
|
|
ids = [c['clientId'] for c in clients]
|
|
require(len(ids) == len(set(ids)) and not set(ids).intersection(IDS), 'existing_probe_or_duplicate')
|
|
require(not {c.get('secretRef') for c in clients}.intersection('env:' + e for e in ENVS),
|
|
'probe_secretref_collision')
|
|
production = [c for c in clients if c['clientId'] == 'secrets-engine-openbao']
|
|
require(len(production) == 1, 'production_client_missing')
|
|
expected = registrations()[-1]
|
|
for field in ['allowedScopes', 'grantTypes', 'clientType', 'serviceSubject', 'tenant', 'roles']:
|
|
require(production[0].get(field) == expected[field], 'production_claim_contract_drift')
|
|
require(production[0].get('audience', production[0]['clientId']) == 'secrets-engine-openbao',
|
|
'production_audience_drift')
|
|
nodes = yaml.compose(raw)
|
|
node = next(v for key, v in nodes.value if key.value == 'clients')
|
|
require(isinstance(node, yaml.nodes.SequenceNode) and not node.flow_style,
|
|
'block_client_sequence_required')
|
|
index = node.end_mark.index
|
|
start = raw.rfind('\n', 0, index) + 1
|
|
if not raw[start:index].strip():
|
|
index = start
|
|
fragment = yaml.safe_dump(registrations(), sort_keys=False)
|
|
fragment = ''.join(' ' * node.start_mark.column + line if line.strip() else line
|
|
for line in fragment.splitlines(True))
|
|
if index and raw[index - 1] != '\n':
|
|
fragment = '\n' + fragment
|
|
updated = raw[:index] + fragment + raw[index:]
|
|
intended = copy.deepcopy(config)
|
|
intended['clients'].extend(registrations())
|
|
require(yaml.load(updated, Loader=UniqueLoader) == intended, 'unrelated_config_changed')
|
|
return updated
|
|
|
|
|
|
def candidate_spec(spec):
|
|
result = copy.deepcopy(spec)
|
|
require(result.get('replicas', 1) == 1 and result['strategy']['type'] == 'Recreate',
|
|
'single_replica_recreate_required')
|
|
container = next(c for c in result['template']['spec']['containers'] if c['name'] == 'keycape')
|
|
env = container['env']
|
|
require(not {e['name'] for e in env}.intersection(ENVS), 'probe_env_collision')
|
|
env.extend({'name': e, 'valueFrom': {'secretKeyRef': {'name': TEMP, 'key': c}}}
|
|
for c, e in zip(IDS, ENVS))
|
|
return result
|
|
|
|
|
|
def ready(spec, timeout=150):
|
|
deadline = time.monotonic() + timeout
|
|
while time.monotonic() < deadline:
|
|
dep = get('deployment', 'keycape')
|
|
require(dep['spec'] == spec, 'deployment_drift_during_readiness')
|
|
status = dep.get('status', {})
|
|
if (status.get('observedGeneration') == dep['metadata']['generation']
|
|
and all(status.get(key) == 1 for key in
|
|
['updatedReplicas', 'readyReplicas', 'availableReplicas', 'replicas'])):
|
|
return True
|
|
time.sleep(3)
|
|
raise Refused('readiness_timeout')
|
|
|
|
|
|
def verify_sources(envelope, recovery=False):
|
|
require(envelope.get('proposal_id') == PROPOSAL and envelope.get('target') == TARGET,
|
|
'proposal_target_mismatch')
|
|
require(envelope.get('disposition') == ('recovery-approved' if recovery else 'approved')
|
|
and envelope.get('founder_decision_id'),
|
|
'founder_exact_go_required')
|
|
require(envelope.get('source_hashes') and
|
|
str(Path(__file__).resolve()) in envelope['source_hashes'], 'executor_pin_required')
|
|
for filename, expected in envelope['source_hashes'].items():
|
|
require(digest(Path(filename).read_bytes()) == expected, 'reviewed_source_changed')
|
|
required = ['net-kingdom', 'key-cape', 'railiance-platform']
|
|
require(all(envelope.get('owner_acknowledgments', {}).get(owner) for owner in required),
|
|
'owner_acknowledgments_required')
|
|
expiry = envelope['valid_until_epoch']
|
|
maximum = envelope['maximum_session_seconds']
|
|
require(finite_number(expiry) and type(maximum) is int and
|
|
420 <= maximum <= 900 and 0 < expiry - time.time() <= maximum,
|
|
'attended_window_invalid')
|
|
if recovery:
|
|
return None
|
|
role = envelope['role_observation']
|
|
require(role['mount'] == 'keycape-services' and role['role'] == 'secrets-engine-openbao'
|
|
and role['issuer'] == 'https://kc.coulomb.social' and role['algorithm'] == 'RS256'
|
|
and role['bindings_exact'] is True and role['policy_exact'] is True,
|
|
'role_owner_observation_required')
|
|
leeway = role['raw_expiration_leeway']
|
|
require(type(leeway) is int and -1 <= leeway <= 150, 'leeway_outside_session_bound')
|
|
effective = 0 if leeway == -1 else leeway or 150
|
|
require(role['effective_expiration_leeway'] == effective, 'leeway_normalization_mismatch')
|
|
observed = role['observed_at_epoch']
|
|
require(finite_number(observed) and 0 <= time.time() - observed <= 300,
|
|
'role_observation_stale_or_future')
|
|
require(envelope['bao_addr'] in BAO_ORIGINS, 'bao_origin_invalid')
|
|
return effective
|
|
|
|
|
|
def load_probe(path):
|
|
spec = importlib.util.spec_from_file_location('key39_reviewed_probe', path)
|
|
result = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(result)
|
|
return result
|
|
|
|
|
|
def removed_client_denied(status, raw):
|
|
try:
|
|
body = json.loads(raw)
|
|
return status == 401 and body.get('error') == 'invalid_profile_usage' and body.get('feature') == 'client_id'
|
|
except (ValueError, AttributeError, TypeError):
|
|
return False
|
|
|
|
|
|
def recovered_config(raw):
|
|
config = yaml.load(raw, Loader=UniqueLoader)
|
|
clients = config['clients']
|
|
ids = [c['clientId'] for c in clients]
|
|
present = set(ids).intersection(IDS)
|
|
require(len(ids) == len(set(ids)) and present in [set(), set(IDS)], 'partial_probe_registry')
|
|
if not present:
|
|
return raw
|
|
require(clients[-6:] == registrations(), 'recovery_registration_drift')
|
|
node = next(v for key, v in yaml.compose(raw).value if key.value == 'clients')
|
|
first = node.value[-6].start_mark.index
|
|
start = raw.rfind('\n', 0, first) + 1
|
|
end = node.end_mark.index
|
|
line_start = raw.rfind('\n', 0, end) + 1
|
|
if not raw[line_start:end].strip():
|
|
end = line_start
|
|
restored = raw[:start] + raw[end:]
|
|
require(candidate_config(restored) == raw, 'recovery_byte_roundtrip_failed')
|
|
return restored
|
|
|
|
|
|
def recovered_spec(spec):
|
|
restored = copy.deepcopy(spec)
|
|
container = next(c for c in restored['template']['spec']['containers'] if c['name'] == 'keycape')
|
|
env = container['env']
|
|
expected = [{'name': e, 'valueFrom': {'secretKeyRef': {'name': TEMP, 'key': c}}}
|
|
for c, e in zip(IDS, ENVS)]
|
|
matches = [e for e in env if e['name'] in ENVS]
|
|
require(matches in [[], expected], 'partial_or_drifted_probe_env')
|
|
container['env'] = [e for e in env if e['name'] not in ENVS]
|
|
return restored
|
|
|
|
|
|
def delete_temporary(created):
|
|
# The exact raw API delete needs the same aggregate/per-request guards as patches.
|
|
k(['delete', '--raw=/api/v1/namespaces/sso/secrets/' + TEMP, '-f', '/dev/stdin'],
|
|
{'apiVersion': 'v1', 'kind': 'DeleteOptions', 'preconditions': meta(created)})
|
|
require(k(['-n', 'sso', 'get', 'secret', TEMP, '--ignore-not-found', '-o', 'json']) is None,
|
|
'temporary_secret_still_present')
|
|
|
|
|
|
def recover(envelope, receipt):
|
|
global REQUEST_DEADLINE, BUSINESS_MONOTONIC_DEADLINE, CLEANUP_MONOTONIC_DEADLINE, CLEANING
|
|
verify_sources(envelope, recovery=True)
|
|
# Separately approved recovery obligations are cleanup, never business writes.
|
|
REQUEST_DEADLINE = None
|
|
BUSINESS_MONOTONIC_DEADLINE = None
|
|
CLEANUP_MONOTONIC_DEADLINE = None
|
|
begin_cleanup()
|
|
CLEANING = True
|
|
transport = SessionTransport(envelope['bao_addr'], receipt=receipt)
|
|
require(k(['get', 'namespace', 'kube-system', '-o', 'json'])['metadata']['uid'] == CLUSTER,
|
|
'cluster_identity_changed')
|
|
config = get('secret', 'keycape-config')
|
|
dep = get('deployment', 'keycape')
|
|
raw = base64.b64decode(config['data']['config.yaml'], validate=True).decode()
|
|
restored = recovered_config(raw)
|
|
spec = recovered_spec(dep['spec'])
|
|
require(config['metadata']['uid'] == envelope['config_metadata']['uid']
|
|
and dep['metadata']['uid'] == envelope['deployment_metadata']['uid']
|
|
and digest(restored.encode()) == envelope['registry_sha256']
|
|
and canonical(spec) == envelope['deployment_spec_sha256'], 'recovery_baseline_drift')
|
|
temp = get('secret', TEMP)
|
|
require(set(temp.get('data', {})) == set(IDS)
|
|
and temp['metadata']['uid'] == envelope['temporary_secret_uid'], 'recovery_custody_identity_required')
|
|
values = {c: base64.b64decode(temp['data'][c], validate=True).decode() for c in IDS}
|
|
if raw != restored:
|
|
patch('secret', config, '/data/config.yaml', base64.b64encode(restored.encode()).decode())
|
|
if dep['spec'] != spec:
|
|
patch('deployment', dep, '/spec', spec)
|
|
ready(spec)
|
|
after = get('secret', 'keycape-config')
|
|
require(after['data'] == dict(config['data'], **{'config.yaml': base64.b64encode(restored.encode()).decode()}),
|
|
'recovery_readback_failed')
|
|
require(envelope['runner_path'] in envelope['source_hashes'], 'runner_pin_required')
|
|
probe = load_probe(envelope['runner_path'])
|
|
for client, value in values.items():
|
|
basic = base64.b64encode((client + ':' + value).encode()).decode()
|
|
status, body = transport.post('https://kc.coulomb.social/token',
|
|
form={'grant_type': 'client_credentials', 'scope': 'openbao:login'},
|
|
headers={'Authorization': 'Basic ' + basic})
|
|
require(removed_client_denied(status, body), 'removed_client_denial_unconfirmed')
|
|
observed = get('secret', TEMP)
|
|
require(observed['data'] == temp['data'] and observed['metadata']['uid'] == temp['metadata']['uid'],
|
|
'recovery_temporary_secret_drift')
|
|
delete_temporary(observed)
|
|
receipt.update(cleanup_verified=True, recovery_only=True)
|
|
|
|
|
|
def execute(envelope, private, receipt, checkpoint=lambda: None):
|
|
global REQUEST_DEADLINE, BUSINESS_MONOTONIC_DEADLINE, CLEANUP_MONOTONIC_DEADLINE, CLEANING
|
|
leeway = verify_sources(envelope)
|
|
REQUEST_DEADLINE = envelope['valid_until_epoch']
|
|
BUSINESS_MONOTONIC_DEADLINE = time.monotonic() + min(
|
|
REQUEST_DEADLINE - time.time(), envelope['maximum_session_seconds'] - 180)
|
|
CLEANUP_MONOTONIC_DEADLINE = None
|
|
CLEANING = False
|
|
runner_path = envelope['runner_path']
|
|
require(runner_path in envelope['source_hashes'], 'runner_pin_required')
|
|
probe = load_probe(runner_path)
|
|
transport = SessionTransport(envelope['bao_addr'], receipt=receipt)
|
|
discovery = transport.discovery()
|
|
require(discovery.get('issuer') == 'https://kc.coulomb.social' and
|
|
discovery.get('token_endpoint') == 'https://kc.coulomb.social/token',
|
|
'issuer_discovery_changed')
|
|
require(isinstance(private.get('human_token'), str) and private['human_token']
|
|
and isinstance(private.get('coding_agent_token'), str) and private['coding_agent_token'],
|
|
'founder_private_tokens_required')
|
|
require(k(['get', 'namespace', 'kube-system', '-o', 'json'])['metadata']['uid'] == CLUSTER,
|
|
'cluster_identity_changed')
|
|
before = get('secret', 'keycape-config')
|
|
deployment = get('deployment', 'keycape')
|
|
require(not before['metadata'].get('ownerReferences'), 'controller_owned_config')
|
|
require(meta(before) == envelope['config_metadata']
|
|
and meta(deployment) == envelope['deployment_metadata']
|
|
and canonical(deployment['spec']) == envelope['deployment_spec_sha256'],
|
|
'approved_runtime_revision_changed')
|
|
raw = base64.b64decode(before['data']['config.yaml'], validate=True).decode()
|
|
require(digest(raw.encode()) == envelope['registry_sha256'], 'approved_registry_changed')
|
|
updated = base64.b64encode(candidate_config(raw).encode()).decode()
|
|
intended_data = dict(before['data'], **{'config.yaml': updated})
|
|
candidate = candidate_spec(deployment['spec'])
|
|
ready(deployment['spec'])
|
|
# Exact GET existence is tested via create (never adopt/delete an existing Secret).
|
|
values = {c: secrets.token_urlsafe(48) for c in IDS}
|
|
temporary = {'apiVersion': 'v1', 'kind': 'Secret', 'metadata': {'namespace': 'sso', 'name': TEMP},
|
|
'type': 'Opaque', 'data': {c: base64.b64encode(v.encode()).decode() for c, v in values.items()}}
|
|
patch('secret', before, '/data/config.yaml', updated, dry=True)
|
|
patch('deployment', deployment, '/spec', candidate, dry=True)
|
|
k(['create', '-f', '/dev/stdin', '--dry-run=server', '-o', 'json'], temporary)
|
|
created = None
|
|
mutations_attempted = False
|
|
began = time.monotonic()
|
|
try:
|
|
# Set before API call: uncertain response must still trigger reconciliation.
|
|
check_deadline()
|
|
mutations_attempted = True
|
|
created = k(['create', '-f', '/dev/stdin', '-o', 'json'], temporary)
|
|
receipt['temporary_secret_uid'] = created['metadata']['uid']
|
|
checkpoint()
|
|
patch('secret', before, '/data/config.yaml', updated)
|
|
patch('deployment', deployment, '/spec', candidate)
|
|
ready(candidate)
|
|
require(get('secret', 'keycape-config')['data'] == intended_data, 'registry_drift')
|
|
require(time.monotonic() - began < 300, 'probe_start_too_late')
|
|
# Current admitted agent token stays founder-owned; no existing secret fetched.
|
|
original_exchange = probe.exchange
|
|
def exchange(post, endpoint, client, secret, scope):
|
|
if client == 'codex-railiance-platform':
|
|
return private['coding_agent_token']
|
|
return original_exchange(post, endpoint, client, secret, scope)
|
|
probe.exchange = exchange
|
|
supplied = dict(values, **{'codex-railiance-platform': 'founder-held-token'})
|
|
def bounded_post(*args, **kwargs):
|
|
if not args[0].endswith('/v1/auth/token/revoke-self'):
|
|
require(time.monotonic() - began < envelope['maximum_session_seconds'] - 180,
|
|
'session_cleanup_reserve_reached')
|
|
return transport.post(*args, **kwargs)
|
|
def bounded_sleep(seconds):
|
|
while seconds > 0:
|
|
check_deadline()
|
|
require(time.monotonic() - began < envelope['maximum_session_seconds'] - 180,
|
|
'session_cleanup_reserve_reached')
|
|
part = min(seconds, 3)
|
|
time.sleep(part)
|
|
seconds -= part
|
|
result = probe.run(bounded_post, supplied, 'https://kc.coulomb.social/token',
|
|
envelope['bao_addr'], 'keycape-services', 'secrets-engine-openbao',
|
|
human_token=private['human_token'], leeway=leeway, sleep=bounded_sleep)
|
|
receipt['negative_probe'] = result
|
|
checkpoint()
|
|
check_deadline()
|
|
require(time.monotonic() - began <= envelope['maximum_session_seconds'], 'session_window_exceeded')
|
|
require(result['all_refused'], 'negative_proof_incomplete')
|
|
finally:
|
|
if mutations_attempted:
|
|
begin_cleanup()
|
|
CLEANING = True
|
|
# Never restore over an unrelated edit; keep custody for owner recovery on drift.
|
|
now = get('secret', 'keycape-config')
|
|
require(now['metadata']['uid'] == before['metadata']['uid'] and
|
|
now['data'] in [before['data'], intended_data], 'cleanup_registry_drift')
|
|
if now['data'] == intended_data:
|
|
patch('secret', now, '/data/config.yaml', before['data']['config.yaml'])
|
|
now = get('deployment', 'keycape')
|
|
require(now['metadata']['uid'] == deployment['metadata']['uid'] and
|
|
now['spec'] in [deployment['spec'], candidate], 'cleanup_deployment_drift')
|
|
if now['spec'] == candidate:
|
|
patch('deployment', now, '/spec', deployment['spec'])
|
|
ready(deployment['spec'])
|
|
require(get('secret', 'keycape-config')['data'] == before['data'], 'cleanup_readback_failed')
|
|
# No fresh JWT is minted: deleted clients must fail at the issuer.
|
|
for client in IDS:
|
|
basic = base64.b64encode((client + ':' + values[client]).encode()).decode()
|
|
status, body = transport.post('https://kc.coulomb.social/token',
|
|
form={'grant_type': 'client_credentials', 'scope': 'openbao:login'},
|
|
headers={'Authorization': 'Basic ' + basic})
|
|
require(removed_client_denied(status, body),
|
|
'removed_client_denial_unconfirmed')
|
|
if created is None:
|
|
# A timed-out create may have succeeded; compare generated bytes before delete.
|
|
observed = get('secret', TEMP)
|
|
require(observed.get('data') == temporary['data'], 'temporary_secret_identity_unconfirmed')
|
|
created = observed
|
|
observed = get('secret', TEMP)
|
|
require(observed['metadata']['uid'] == created['metadata']['uid']
|
|
and observed.get('data') == temporary['data'], 'temporary_secret_drift')
|
|
delete_temporary(observed)
|
|
receipt['cleanup_verified'] = True
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
|
ap.add_argument('mode', choices=['inspect', 'founder-preflight', 'execute', 'recover'])
|
|
ap.add_argument('--envelope')
|
|
ap.add_argument('--confirm')
|
|
ap.add_argument('--receipt', required=True)
|
|
args = ap.parse_args()
|
|
receipt = {'proposal_id': PROPOSAL, 'target': TARGET, 'native_accepted': False,
|
|
'values_emitted': False, 'cleanup_verified': False}
|
|
# Do not truncate or follow a link; protected receipts cannot overwrite credentials.
|
|
fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
|
|
def checkpoint():
|
|
os.lseek(fd, 0, os.SEEK_SET)
|
|
os.ftruncate(fd, 0)
|
|
os.write(fd, json.dumps(receipt, sort_keys=True, indent=2).encode())
|
|
os.fsync(fd)
|
|
rc = 1
|
|
try:
|
|
if args.mode in ['inspect', 'founder-preflight']:
|
|
require(k(['get', 'namespace', 'kube-system', '-o', 'json'])['metadata']['uid'] == CLUSTER,
|
|
'cluster_identity_changed')
|
|
dep = get('deployment', 'keycape')
|
|
receipt.update(deployment_metadata=meta(dep), deployment_spec_sha256=canonical(dep['spec']),
|
|
image=next(c['image'] for c in dep['spec']['template']['spec']['containers']
|
|
if c['name'] == 'keycape'), mode='metadata_only')
|
|
if args.mode == 'founder-preflight':
|
|
require(args.confirm == 'FOUNDER ATTENDED KEY39 PREFLIGHT', 'founder_attendance_required')
|
|
config = get('secret', 'keycape-config')
|
|
require(not config['metadata'].get('ownerReferences'), 'controller_owned_config')
|
|
raw = base64.b64decode(config['data']['config.yaml'], validate=True).decode()
|
|
candidate_config(raw)
|
|
candidate_spec(dep['spec'])
|
|
require(k(['-n', 'sso', 'get', 'secret', TEMP, '--ignore-not-found', '-o', 'json']) is None,
|
|
'temporary_secret_already_exists')
|
|
ready(dep['spec'])
|
|
receipt.update(config_metadata=meta(config), registry_sha256=digest(raw.encode()),
|
|
baseline_ready=True, mode='founder_protected_preflight',
|
|
probe_ids_absent=True, probe_env_absent=True, temporary_secret_absent=True)
|
|
else:
|
|
require(args.envelope is not None, 'approval_envelope_required')
|
|
envelope = read_json(Path(args.envelope).read_text())
|
|
require(Path(args.envelope).stat().st_mode & 0o022 == 0, 'approval_envelope_writable')
|
|
signal.signal(signal.SIGTERM, lambda *_: (_ for _ in ()).throw(Refused('session_interrupted')))
|
|
if args.mode == 'recover':
|
|
recover(envelope, receipt)
|
|
else:
|
|
private = read_json(__import__('sys').stdin.read())
|
|
execute(envelope, private, receipt, checkpoint=checkpoint)
|
|
receipt['negative_and_cleanup_verified'] = True
|
|
rc = 0
|
|
except Refused as error:
|
|
receipt.update(status='incomplete_requires_owner_review', reason=str(error))
|
|
except BaseException:
|
|
# Fixed outcome only: exception text may contain protected Kubernetes/HTTP data.
|
|
receipt['status'] = 'incomplete_requires_owner_review'
|
|
finally:
|
|
checkpoint()
|
|
os.close(fd)
|
|
return rc
|
|
|
|
|
|
if __name__ == '__main__':
|
|
raise SystemExit(main())
|