docs: preserve reviewed NetKingdom session artifacts
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Reject generated-only commits / generated-commit-guard (push) Waiting to run

This commit is contained in:
codex 2026-11-01 23:17:09 +01:00
parent 8231b2a8f5
commit 4735d6755f
48 changed files with 6488 additions and 0 deletions

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# First creation only. Existing KeyCape configuration requires a scoped owner update.
set +x
set -euo pipefail
umask 077
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec python3 "$SCRIPT_DIR/create-secrets.py" "$@"

View file

@ -0,0 +1,92 @@
import base64, importlib.util, json, os, secrets, socket, subprocess, tempfile, time
from pathlib import Path
from unittest.mock import patch
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import yaml
import http.server, ssl, threading, urllib.request, urllib.error, datetime, ipaddress
from cryptography import x509
from cryptography.x509.oid import NameOID
import argparse
parser=argparse.ArgumentParser(description='Exercise the pinned image and verifier over loopback HTTPS using synthetic credentials only.')
parser.add_argument('--receipt', required=True, type=Path)
args=parser.parse_args()
spec=importlib.util.spec_from_file_location('rollout',Path(__file__).with_name('approval-clients-rollout.py'))
m=importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
m.verify_artifact()
def call(args):
p=subprocess.run(args,capture_output=True,timeout=45)
if p.returncode: raise RuntimeError('contained_docker_command_failed')
return p
sock=socket.socket(); sock.bind(('127.0.0.1',0)); port=sock.getsockname()[1]; sock.close()
class Proxy(http.server.BaseHTTPRequestHandler):
def log_message(self, *args): pass
def handle_proxy(self):
body = self.rfile.read(int(self.headers.get('Content-Length', 0))) if self.command == 'POST' else None
req = urllib.request.Request(f'http://127.0.0.1:{port}'+self.path, data=body,
headers={k:self.headers[k] for k in ['Content-Type','Authorization'] if k in self.headers})
try:
response=urllib.request.urlopen(req,timeout=20)
except urllib.error.HTTPError as error: response=error
with response:
self.send_response(response.code)
self.send_header('Content-Type', response.headers.get('Content-Type','application/json'))
self.end_headers(); self.wfile.write(response.read())
do_GET=handle_proxy
do_POST=handle_proxy
server=http.server.ThreadingHTTPServer(('127.0.0.1',0),Proxy)
https_port=server.server_address[1]
name='keycape-approval-exercise-'+secrets.token_hex(5)
with tempfile.TemporaryDirectory(prefix='keycape-private-exercise-') as temp:
root=Path(temp)
key=rsa.generate_private_key(public_exponent=65537,key_size=2048)
(root/'key.pem').write_bytes(key.private_bytes(serialization.Encoding.PEM,serialization.PrivateFormat.PKCS8,serialization.NoEncryption()))
os.chmod(root/'key.pem',0o600)
name_attr=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,'KeyCape local exercise')])
cert=(x509.CertificateBuilder().subject_name(name_attr).issuer_name(name_attr).public_key(key.public_key())
.serial_number(x509.random_serial_number()).not_valid_before(datetime.datetime.now(datetime.timezone.utc)-datetime.timedelta(minutes=1))
.not_valid_after(datetime.datetime.now(datetime.timezone.utc)+datetime.timedelta(hours=1))
.add_extension(x509.SubjectAlternativeName([x509.IPAddress(ipaddress.ip_address('127.0.0.1'))]),False)
.add_extension(x509.BasicConstraints(ca=True,path_length=None),True))
from cryptography.hazmat.primitives import hashes
(root/'cert.pem').write_bytes(cert.sign(key,hashes.SHA256()).public_bytes(serialization.Encoding.PEM))
context=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER); context.load_cert_chain(root/'cert.pem',root/'key.pem')
server.socket=context.wrap_socket(server.socket,server_side=True)
threading.Thread(target=server.serve_forever,daemon=True).start()
config=yaml.safe_load((m.KEYCAPE/'config/dev-config.yaml').read_text())
config.update(issuer=f'https://127.0.0.1:{https_port}')
config['authelia']['issuer']='https://auth.coulomb.social'
config['clients'].extend(m.registrations())
(root/'config.yaml').write_text(yaml.safe_dump(config,sort_keys=False))
os.chmod(root/'config.yaml',0o600)
values={c['clientId']:secrets.token_urlsafe(48) for c in m.registrations()}
(root/'env').write_text('KEYCAPE_CONFIG=/etc/keycape/config.yaml\n'+''.join(c['secretRef'].removeprefix('env:')+'='+values[c['clientId']]+'\n' for c in m.registrations()))
os.chmod(root/'env',0o600)
created=False
try:
call(['docker','run','-d','--rm','--name',name,'--user',str(os.getuid()),'--publish',f'127.0.0.1:{port}:8080',
'--env-file',str(root/'env'),'--mount','type=bind,source='+temp+',target=/etc/keycape,readonly',m.IMAGE])
created=True
with patch.object(m,'ISSUER',config['issuer']), patch.dict(os.environ,{'SSL_CERT_FILE':str(root/'cert.pem')}):
for _ in range(30):
try:
status,_=m.http('/.well-known/openid-configuration')
if status==200: break
except Exception: pass
time.sleep(.5)
else: raise RuntimeError('scratch_keycape_not_ready')
def fake_get(kube,kind,name):
index=m.SECRET_NAMES.index(name)
return {'data': {'client-secret':base64.b64encode(values[m.IDS[index]].encode()).decode()}}
receipt={}
with patch.object(m,'get',side_effect=fake_get):
m.acceptance([],receipt)
receipt.update(target='disposable pinned KeyCape image; synthetic keys and clients only',
pinned_native_cli_verification='passed against loopback HTTPS with synthetic credentials',cleanup_complete=False)
finally:
if created: call(['docker','stop','--time=5',name])
server.shutdown(); server.server_close()
receipt['cleanup_complete']=True
fd=os.open(args.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
with os.fdopen(fd,'w') as out: out.write(json.dumps(receipt,indent=2)+'\n')
print('Pinned-image scratch acceptance passed; synthetic credentials removed.')

View file

@ -0,0 +1,312 @@
#!/usr/bin/env python3
"""Register or remove exactly one KeyCape client from a SecurityCase, without exporting Secret values.
Entry point of the playbook capability net-kingdom.keycape-client-registration,
composed by the SecurityCaseType oidc-browser-relying-party
(security-case-types/, canon security-case-types_v0.1). It generalizes the
per-client helpers in this directory (portal-client-rollout,
register-coulomb-social, vergabe-client-rollout,
secrets-engine-openbao-client-rollout) and keeps their safeguards:
- contained kubectl, cluster-identity pin and verified upstream issuer pin
- byte-preserving merge: only this client's lines change; every other config
byte, the signing key and unrelated clients are preserved
- exact match: absent -> add; identical -> noop; drift, duplicates or a shared
secretRef -> refuse (update = remove, then add, each accepted)
- compare-and-swap on the Secret's uid and resourceVersion, then read-back
- confidential clients: the KeyCape Deployment must already bind the client's
secret env var (delivered from custody), or the next pod start fails closed
- KeyCape's own startup checks (keycape check-config, key-cape c528daa) on the
live and the proposed registry, fed on stdin and never written to disk; apply
refuses a registry that would stop the single replica at its next start
- no Secret value, parser message or Kubernetes error text leaves the process
Modes: inspect (default), dry-run (server-side), apply. apply requires the
case's acceptance record instead of a per-client founder decision id: the case
was accepted under an acknowledged type.
keycape-client.py client-from-case <case.yaml>
keycape-client.py inspect --client-file client.yaml [--operation remove]
keycape-client.py apply --client-file client.yaml --case-acceptance <record> \\
--check-config <keycape binary> --expected-uid <uid> --expected-resource-version <rv>
"""
from __future__ import annotations
import argparse
import base64
import copy
import importlib.util
import json
import re
import subprocess
import sys
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
def module(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
result = importlib.util.module_from_spec(spec)
spec.loader.exec_module(result)
return result
lane = module('portal_rollout', 'portal-client-rollout.py')
pin = lane.pin
Refused = lane.Refused
require = lane.require
CASE_TYPE = re.compile(r'^oidc-browser-relying-party@1\.\d+$')
CLIENT_ID = re.compile(r'^[a-z0-9][a-z0-9-]{1,62}$')
# KeyCape refuses '*' and '?' at startup; OAuth 2.0 forbids fragments.
REDIRECT = re.compile(r'^https://[^*?#\s,]+$')
SCOPES = {'openid', 'profile', 'email', 'groups'}
def env_name(client_id):
return 'KEYCAPE_' + client_id.upper().replace('-', '_') + '_CLIENT_SECRET'
def env_secret_name(client_id):
return f'keycape-{client_id}-client'
def client_from_params(params):
"""The KeyCape client record for a case of oidc-browser-relying-party."""
cid = params['client_id']
require(CLIENT_ID.fullmatch(cid or '') is not None, 'invalid_client_id')
redirects = [r for r in params['redirect_uris'].split(',')]
require(redirects and all(REDIRECT.fullmatch(r) for r in redirects), 'redirect_uris_must_be_exact_https')
scopes = params['allowed_scopes'].split(',')
require(scopes[0] == 'openid' and set(scopes) <= SCOPES and len(set(scopes)) == len(scopes),
'allowed_scopes_invalid')
# KeyCape has no per-client group gate; the consumer reads groups, and UserInfo
# returns them only for the groups scope.
require(not params.get('access_groups') or 'groups' in scopes, 'access_groups_require_groups_scope')
require(params['client_type'] in ('confidential', 'public'), 'client_type_invalid')
# The type derives custody at platform/workloads/keycape/<client_id>; a case cannot choose it.
require('secret_ref' not in params, 'secret_ref_is_derived_by_the_type')
require(params['mfa_required'] in ('true', 'false'), 'mfa_required_invalid')
client = {
'clientId': cid,
'displayName': params['display_name'],
'redirectUris': redirects,
'allowedScopes': scopes,
'grantTypes': ['authorization_code'],
'clientType': params['client_type'],
}
if params['client_type'] == 'confidential':
client['secretRef'] = 'env:' + env_name(cid)
if params['mfa_required'] == 'true':
client['mfaRequired'] = True
return client
def client_from_case(case):
spec = case.get('spec') or {}
require(CASE_TYPE.fullmatch(str(spec.get('type', ''))) is not None, 'case_type_not_oidc_browser_relying_party')
return client_from_params(spec.get('parameters') or {})
def env_binding(client):
return {'name': env_name(client['clientId']), 'valueFrom': {
'secretKeyRef': {'name': env_secret_name(client['clientId']), 'key': 'client-secret'}}}
def _client_node(raw):
root = yaml.compose(raw)
node = next(value for key, value in root.value if key.value == 'clients')
require(isinstance(node, yaml.nodes.SequenceNode) and not node.flow_style,
'block_client_sequence_required')
return node
def _line_start(raw, index):
start = raw.rfind('\n', 0, index) + 1
return start if not raw[start:index].strip() else index
def _inspect(secret, client):
raw, config, _ = pin.issuer_document(secret)
require(config['authelia'].get('issuer') == pin.VERIFIED_UPSTREAM_ISSUER, 'verified_issuer_pin_required')
clients = config.get('clients')
require(isinstance(clients, list) and clients, 'client_sequence_required')
require(all(isinstance(c, dict) and 'clientId' in c for c in clients), 'client_shape_unexpected')
ids = [c['clientId'] for c in clients]
require(len(ids) == len(set(ids)), 'duplicate_client_id')
if 'secretRef' in client:
others = [c for c in clients if c['clientId'] != client['clientId']]
require(all(c.get('secretRef') != client['secretRef'] for c in others), 'secret_ref_already_bound')
return raw, config, ids
def replacement(secret, client):
"""Return (base64 config, changed) with only this client added."""
raw, config, ids = _inspect(secret, client)
if client['clientId'] in ids:
require(config['clients'][ids.index(client['clientId'])] == client,
'existing_registration_differs_requires_reconciliation')
return secret['data']['config.yaml'], False
node = _client_node(raw)
index = _line_start(raw, node.end_mark.index)
indent = ' ' * node.start_mark.column
fragment = yaml.safe_dump([client], sort_keys=False, width=120)
fragment = ''.join(indent + 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:]
expected = copy.deepcopy(config)
expected['clients'].append(client)
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def removal(secret, client):
"""Rollback: return (base64 config, changed) with only this exact client removed."""
raw, config, ids = _inspect(secret, client)
if client['clientId'] not in ids:
return secret['data']['config.yaml'], False
position = ids.index(client['clientId'])
require(config['clients'][position] == client, 'existing_registration_differs_requires_reconciliation')
require(len(ids) > 1, 'removal_would_empty_client_sequence')
node = _client_node(raw)
item = node.value[position]
start = raw.rfind('\n', 0, item.start_mark.index) + 1
require(raw[start:item.start_mark.index].strip() == '-', 'unexpected_item_layout')
if position + 1 < len(node.value):
end = raw.rfind('\n', 0, node.value[position + 1].start_mark.index) + 1
else:
end = _line_start(raw, node.end_mark.index)
updated = raw[:start] + raw[end:]
expected = copy.deepcopy(config)
del expected['clients'][position]
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def deployment_bound(deployment, client):
"""Whether the live Deployment carries exactly this client's secret binding."""
if 'secretRef' not in client:
return None
containers = deployment['spec']['template']['spec']['containers']
container = next(c for c in containers if c['name'] == 'keycape')
matches = [e for e in container.get('env', []) if e.get('name') == env_name(client['clientId'])]
require(len(matches) <= 1 and all(e == env_binding(client) for e in matches), 'env_binding_drift')
return bool(matches)
def check_config(binary, config_text, env_names, policy_store, *, executable_fd=None):
"""KeyCape's startup checks on one registry: names and messages only, no values."""
executable = str(Path(binary).resolve()) if executable_fd is None else f'/proc/self/fd/{executable_fd}'
command = [executable, 'check-config', '--config', '/dev/stdin', '--env-names', ','.join(env_names)]
if policy_store:
command.append('--policy-store')
result = subprocess.run(command, input=config_text.encode(), capture_output=True, timeout=60, env={},
pass_fds=() if executable_fd is None else (executable_fd,))
report = json.loads(result.stdout)
require(isinstance(report, dict) and isinstance(report.get('valid'), bool)
and report['valid'] == (result.returncode == 0), 'check_config_output_shape')
return {'valid': report['valid'], 'clients': report.get('clients'),
'errors': [str(e) for e in report.get('errors') or []]}
def deployment_env_names(deployment):
containers = deployment['spec']['template']['spec']['containers']
container = next(c for c in containers if c['name'] == 'keycape')
return sorted(e['name'] for e in container.get('env', []))
def run(args, client):
kubectl = lane.kubectl
cluster = kubectl(['get', 'namespace', 'kube-system', '-o', 'json'])
require(cluster['metadata']['uid'] == lane.CLUSTER_UID, 'cluster_identity_changed')
before = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(not before['metadata'].get('ownerReferences'), 'controller_owned_secret')
metadata = pin.safe_metadata(before)
encoded, changed = (replacement if args.operation == 'add' else removal)(before, client)
bound = deployment = None
if 'secretRef' in client or args.check_config:
deployment = kubectl(['-n', 'sso', 'get', 'deployment', 'keycape', '-o', 'json'])
bound = deployment_bound(deployment, client)
checks = None
if args.check_config:
names = deployment_env_names(deployment)
policy_store = 'KEYCAPE_POLICY_PATH' in names
checker_options = ({'executable_fd': args.check_config_fd}
if getattr(args, 'check_config_fd', None) is not None else {})
checks = {stage: check_config(args.check_config, base64.b64decode(config).decode(), names, policy_store, **checker_options)
for stage, config in (('live', before['data']['config.yaml']), ('proposed', encoded))}
receipt = {'schema': 'netkingdom.keycape-client.v1', 'target': 'sso/keycape-config',
'before': metadata, 'operation': args.operation, 'client_id': client['clientId'],
'client_type': client['clientType'], 'change_needed': changed, 'mode': args.mode,
'deployment_env_bound': bound, 'check_config': checks, 'values_emitted': False, 'changed': False}
if args.mode == 'inspect':
return receipt
if args.mode == 'apply':
require(bool(args.case_acceptance), 'case_acceptance_record_required')
receipt['case_acceptance'] = args.case_acceptance
require(checks is not None, 'keycape_check_config_required')
require(checks['proposed']['valid'], 'proposed_registry_fails_keycape_check_config')
if args.operation == 'add' and bound is not None:
require(bound, 'deployment_env_binding_required_first')
require(metadata == {'uid': args.expected_uid, 'resource_version': args.expected_resource_version},
'observed_revision_changed')
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/config.yaml', 'value': encoded},
]
command = ['-n', 'sso', 'patch', 'secret', 'keycape-config', '--type=json',
'--patch-file=/dev/stdin', '-o', 'json']
if args.mode == 'dry-run':
command += ['--dry-run=server']
result = kubectl(command, patch)
require(result['data'] == dict(before['data'], **{'config.yaml': encoded}), 'patch_response_mismatch')
if args.mode == 'apply':
after = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(after['metadata']['uid'] == metadata['uid']
and after['data'] == dict(before['data'], **{'config.yaml': encoded}),
'readback_mismatch_stop_without_stale_replay')
receipt.update(changed=changed, after=pin.safe_metadata(after), restart_required=changed)
receipt.update(unrelated_config_bytes_preserved=True, other_secret_data_unchanged=True)
return receipt
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
parser.add_argument('mode', nargs='?', default='inspect',
choices=['inspect', 'dry-run', 'apply', 'client-from-case'])
parser.add_argument('case', nargs='?', help='case file, for client-from-case')
parser.add_argument('--client-file', help='KeyCape client record (output of client-from-case)')
parser.add_argument('--operation', default='add', choices=['add', 'remove'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
parser.add_argument('--case-acceptance', help='the case acceptance record; required for apply')
parser.add_argument('--check-config', help='a keycape binary with check-config (key-cape c528daa or later); required for apply')
args = parser.parse_args(argv)
try:
if args.mode == 'client-from-case':
require(bool(args.case), 'case_file_required')
print(yaml.safe_dump(client_from_case(yaml.safe_load(Path(args.case).read_text())), sort_keys=False), end='')
return 0
require(bool(args.client_file), 'client_file_required')
client = yaml.safe_load(Path(args.client_file).read_text())
require(isinstance(client, dict) and 'clientId' in client, 'client_file_shape')
print(json.dumps(run(args, client), sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except pin.IssuerPinError as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
# Parser and Kubernetes errors can contain full Secret material.
print(json.dumps({'status': 'failed', 'reason': 'contained_operation_failed'}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,217 @@
"""Unexercised, injection-only attended registration seam for Warden's v1 kernel.
No CLI, credentials, evidence trust root or live registration is installed here.
Owner publication/authentication and parent-session revocation are mandatory
injected capabilities. The kernel must independently admit both evidence kinds.
"""
from __future__ import annotations
import argparse
from contextlib import contextmanager
import fcntl
import os
import stat
from dataclasses import asdict, replace
import json
import importlib.util
from pathlib import Path
import yaml
from warden.case_run import Adapter, Descriptor, Invocation, OwnerResult, contained_output, digest, IDENTIFIER, DIGEST
from warden.case_receipt_state import Outcome, Refused
ROOT = Path(__file__).resolve().parent
SOURCE_FILES = ('keycape-registration-adapter.py', 'keycape-client.py',
'portal-client-rollout.py', 'openbao-client-config.py',
'register-user-engine-portal.py')
REFERENCE = 'net-kingdom/keycape-registration@v1'
# Importing the maintained helper performs no tooling operation.
_spec = importlib.util.spec_from_file_location('registration_owner_helper', ROOT / 'keycape-client.py')
helper = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(helper)
@contextmanager
def checker_snapshot(path, expected_digest):
"""Execute only verified immutable bytes, independent of later path changes.
Linux sealed memfd and procfs are required. This is a per-attempt executable
snapshot, not native owner storage custody or process/credential isolation.
"""
if not isinstance(expected_digest, str) or not DIGEST.fullmatch(expected_digest):
raise Refused('exact reviewed checker digest required')
source_fd = os.open(path, os.O_RDONLY | os.O_CLOEXEC | os.O_NOFOLLOW | os.O_NONBLOCK)
with os.fdopen(source_fd, 'rb') as source:
metadata = os.fstat(source.fileno())
if not stat.S_ISREG(metadata.st_mode):
raise Refused('regular checker artifact required')
if metadata.st_size > 64 * 1024 * 1024:
raise Refused('checker artifact exceeds snapshot limit')
data = source.read(64 * 1024 * 1024 + 1)
if len(data) > 64 * 1024 * 1024 or digest(data) != expected_digest:
raise Refused('check-config binary changed or exceeds snapshot limit')
fd = os.memfd_create('keycape-reviewed-checker', os.MFD_CLOEXEC | os.MFD_ALLOW_SEALING)
try:
with os.fdopen(os.dup(fd), 'wb') as output:
output.write(data)
os.fchmod(fd, 0o500)
seals = fcntl.F_SEAL_WRITE | fcntl.F_SEAL_GROW | fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_SEAL
fcntl.fcntl(fd, fcntl.F_ADD_SEALS, seals)
if fcntl.fcntl(fd, fcntl.F_GET_SEALS) != seals:
raise Refused('immutable checker snapshot required')
yield fd
finally:
os.close(fd)
def source_digest(check_config_digest):
"""Pin adapter plus the complete local helper import closure, in filename order."""
if not isinstance(check_config_digest, str) or not DIGEST.fullmatch(check_config_digest):
raise Refused('exact reviewed checker digest required')
composition = {'files': {name: digest((ROOT / name).read_bytes()) for name in SOURCE_FILES},
'check_config_binary': check_config_digest}
return digest(json.dumps(composition, sort_keys=True, separators=(',', ':')).encode())
def descriptor(check_config_digest):
return Descriptor('warden.case.execution.v1', 'register-client', 'net-kingdom',
REFERENCE, source_digest(check_config_digest), 'human', 'key-cape-oidc-login',
'client_id', 'change', ('keycape-client',), False,
('parent-session',))
def build(*, admitted: Invocation, case_source: bytes, expected_uid: str,
expected_resource_version: str, case_acceptance: str,
check_config: Path, check_config_digest: str, publish, authenticate,
revoke_session):
"""Bind one admitted case and observed revision; return a closed kernel Adapter.
publish(bytes)->identifier durably retains only the closed metadata payload.
authenticate(identifier, bytes)->True must verify independent owner custody.
revoke_session(invocation)->identifier must revoke this attempt's parent
session; Warden independently authenticates it using its own evidence verifier.
These callbacks are capabilities, never command strings or case parameters.
Only a verified creation in this process may be rolled back. Process loss,
write/readback ambiguity, publication/revocation failure and source drift
require owner reconciliation; none are converted to retry-safe failures.
"""
reviewed_source = source_digest(check_config_digest)
desc = descriptor(check_config_digest)
if type(admitted) is not Invocation or not all(
isinstance(value, str) and IDENTIFIER.fullmatch(value)
for value in (admitted.step, admitted.owner, admitted.target,
admitted.actor, admitted.principal, admitted.lane)):
raise Refused('bounded invocation required')
if desc.adapter_source != reviewed_source:
raise Refused('owner source changed during binding')
if (admitted.step, admitted.owner, admitted.actor, admitted.lane) != (
desc.step, desc.owner, desc.actor, desc.lane):
raise Refused('registration invocation boundary differs')
if admitted.operation != 'admission' or admitted.attempt_id:
raise Refused('expected admission invocation')
if type(case_source) is not bytes or len(case_source) > 262144 or digest(case_source) != admitted.pins.case:
raise Refused('case source pin differs')
# Pins.adapter is the descriptor digest, not the owner source digest.
descriptor_bytes = json.dumps(asdict(desc), sort_keys=True, separators=(',', ':')).encode()
if admitted.pins.adapter != digest(descriptor_bytes):
raise Refused('descriptor pin differs')
if not all(isinstance(x, str) and 0 < len(x) <= 192 for x in
(expected_uid, expected_resource_version, case_acceptance)):
raise Refused('bounded attended binding required')
if digest(case_acceptance.encode()) != admitted.pins.acceptance:
raise Refused('acceptance evidence binding differs')
if not all(callable(x) for x in (publish, authenticate, revoke_session)):
raise Refused('owner evidence and revocation capabilities required')
with contained_output():
case = yaml.safe_load(case_source)
client = helper.client_from_case(case)
if admitted.target != client['clientId']:
raise Refused('case target differs')
check_config = Path(check_config)
records = {}
creation = None
def binding(inv, operation):
if (replace(inv, attempt_id='', operation='admission') != admitted
or inv.operation != operation or not IDENTIFIER.fullmatch(inv.attempt_id)):
raise Refused('attempt binding differs')
if source_digest(check_config_digest) != reviewed_source:
raise Refused('owner source changed')
def execute(inv, operation, created=()):
nonlocal creation
try:
with contained_output():
binding(inv, 'run' if operation == 'add' else 'rollback')
uid, revision = expected_uid, expected_resource_version
if operation == 'remove':
if (created != ('keycape-client',) or creation is None
or not verify(*creation)):
raise Refused('no authenticated case-created client')
original_payload = json.loads(records[creation[1].evidence][1])
uid, revision = original_payload['after']['uid'], original_payload['after']['resource_version']
args = argparse.Namespace(mode='apply', operation=operation,
expected_uid=uid, expected_resource_version=revision,
case_acceptance=case_acceptance, check_config=str(check_config))
with checker_snapshot(check_config, check_config_digest) as checker_fd:
args.check_config_fd = checker_fd
receipt = helper.run(args, client)
# The helper has completed CAS, whole-data readback and preservation.
# Never retain check-config diagnostics or arbitrary helper fields.
if not (receipt['schema'] == 'netkingdom.keycape-client.v1'
and receipt['target'] == 'sso/keycape-config'
and receipt['client_id'] == admitted.target
and receipt['mode'] == 'apply' and receipt['operation'] == operation
and receipt['values_emitted'] is False
and type(receipt['changed']) is bool
and receipt['unrelated_config_bytes_preserved'] is True
and receipt['other_secret_data_unchanged'] is True
and receipt['before'] == {'uid': uid, 'resource_version': revision}
and receipt['after']['uid'] == uid
and receipt['check_config']['proposed']['valid'] is True):
raise Refused('owner readback contract differs')
after = receipt['after']
if (set(after) != {'uid', 'resource_version'} or
not all(isinstance(v, str) and 0 < len(v) <= 192 for v in after.values())):
raise Refused('owner revision contract differs')
changed = receipt['changed']
outcome = (Outcome.ROLLED_BACK if operation == 'remove' else
Outcome.SUCCESS if changed else Outcome.NO_CHANGE)
objects = ('keycape-client',) if operation == 'add' and changed else ()
finally:
# Even helper failure must end the parent session. Failures propagate
# contained to the kernel and become uncertain, never safe to replay.
with contained_output():
revocation = revoke_session(inv)
if not isinstance(revocation, str) or not IDENTIFIER.fullmatch(revocation):
raise Refused('session revocation evidence missing')
payload = json.dumps({'schema': 'netkingdom.keycape-registration.attempt.v1',
'invocation': asdict(inv), 'source': reviewed_source,
'outcome': outcome.value, 'created': objects, 'before': receipt['before'],
'after': after, 'session_revocation': revocation},
sort_keys=True, separators=(',', ':')).encode()
with contained_output():
evidence = publish(payload)
if not isinstance(evidence, str) or not IDENTIFIER.fullmatch(evidence) or evidence in records:
raise Refused('owner evidence identifier invalid or reused')
result = OwnerResult(outcome, evidence, objects, True, revocation,
attempt_id=inv.attempt_id, operation=inv.operation)
records[evidence] = (inv, payload, result)
if operation == 'add' and changed:
creation = (inv, result)
elif operation == 'remove':
creation = None
return result
def verify(inv, result):
record = records.get(result.evidence) if type(result) is OwnerResult else None
if record is None or record[0] != inv or record[2] != result:
return False
with contained_output():
return authenticate(result.evidence, record[1]) is True
return Adapter(REFERENCE, reviewed_source, lambda inv: execute(inv, 'add'),
verify, lambda inv, created: execute(inv, 'remove', created))

View file

@ -0,0 +1,304 @@
#!/usr/bin/env python3
"""Patch or verify non-secret KeyCape live config requirements.
The script reads a Kubernetes Secret JSON object from stdin. It never prints the
decoded KeyCape config or private key; stdout is either a JSON merge patch for
kubectl, or a short non-secret verification message.
"""
from __future__ import annotations
import argparse
import base64
import json
import copy
import re
import subprocess
import sys
from datetime import datetime, timezone
from typing import Any
try:
import yaml
except ImportError as exc: # pragma: no cover - operator environment guard
raise SystemExit("PyYAML is required: install python3-yaml or run in the NetKingdom tool environment") from exc
OPENBAO_CLIENT = {
"clientId": "openbao-admin",
"displayName": "Railiance OpenBao Admin",
"redirectUris": [
"http://localhost:8250/oidc/callback",
"http://127.0.0.1:8250/oidc/callback",
"http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback",
],
"allowedScopes": ["openid", "profile", "email", "groups"],
"grantTypes": ["authorization_code"],
"clientType": "public",
}
# bao.coulomb.social was retired on 2026-09-15; its callbacks must not return.
RETIRED_OPENBAO_REDIRECT_URIS = (
"https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback",
"https://bao.coulomb.social/ui/vault/auth/keycape/oidc/callback",
)
RAPP_QONTO_CLIENT = {
"clientId": "rapp-qonto-client",
"displayName": "rapp-qonto workload",
"allowedScopes": ["qonto:read"],
"grantTypes": ["client_credentials"],
"clientType": "confidential",
"secretRef": "env:KEYCAPE_RAPP_QONTO_CLIENT_SECRET",
"serviceSubject": "rapp-qonto",
"tenant": "tenant:friendly:binky",
"roles": ["qonto-reader"],
}
LLDAP_REQUIRED = {
"userOU": "ou=people",
"groupOU": "ou=groups",
}
# Verified against an actual signed upstream token on 2026-09-08 (KEY-WP-0013).
VERIFIED_UPSTREAM_ISSUER = "https://auth.coulomb.social"
class IssuerPinError(Exception):
"""Only fixed, non-secret reason codes may leave the contained operation."""
class UniqueLoader(yaml.SafeLoader):
pass
def unique_mapping(loader: Any, node: Any) -> dict[str, Any]:
result = {}
for key_node, value_node in node.value:
key = loader.construct_object(key_node, deep=True)
if not isinstance(key, str) or key in result:
raise IssuerPinError("ambiguous_yaml_mapping")
result[key] = loader.construct_object(value_node, deep=True)
return result
UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, unique_mapping)
def issuer_document(secret: dict[str, Any]) -> tuple[str, dict[str, Any], Any]:
"""Parse privately; never include a parser exception or document in output."""
raw = base64.b64decode(secret['data']['config.yaml'], validate=True).decode('utf-8')
if any(isinstance(t, (yaml.tokens.AnchorToken, yaml.tokens.AliasToken)) for t in yaml.scan(raw)):
raise IssuerPinError("yaml_alias_or_anchor")
config = yaml.load(raw, Loader=UniqueLoader)
if not isinstance(config, dict) or not isinstance(config.get('authelia'), dict):
raise IssuerPinError("unexpected_configuration_shape")
node = yaml.compose(raw)
authelia = next(v for k, v in node.value if k.value == 'authelia')
if authelia.flow_style or not authelia.value:
raise IssuerPinError("unsupported_authelia_mapping")
return raw, config, authelia
def issuer_replacement(secret: dict[str, Any]) -> tuple[str, bool, str]:
raw, config, authelia = issuer_document(secret)
old = config['authelia'].get('issuer')
prior = 'absent' if 'issuer' not in config['authelia'] else 'matching' if old == VERIFIED_UPSTREAM_ISSUER else 'different'
if old == VERIFIED_UPSTREAM_ISSUER:
return secret['data']['config.yaml'], False, prior
field = next((v for k, v in authelia.value if k.value == 'issuer'), None)
if field is not None:
if not isinstance(field, yaml.nodes.ScalarNode):
raise IssuerPinError("unsupported_issuer_value")
updated = raw[:field.start_mark.index] + json.dumps(VERIFIED_UPSTREAM_ISSUER) + raw[field.end_mark.index:]
else:
index = authelia.start_mark.index - authelia.start_mark.column
indent = ' ' * authelia.start_mark.column
updated = raw[:index] + indent + 'issuer: ' + json.dumps(VERIFIED_UPSTREAM_ISSUER) + '\n' + raw[index:]
expected = copy.deepcopy(config)
expected['authelia']['issuer'] = VERIFIED_UPSTREAM_ISSUER
if yaml.load(updated, Loader=UniqueLoader) != expected:
raise IssuerPinError("unexpected_configuration_change")
return base64.b64encode(updated.encode()).decode('ascii'), True, prior
def contained_kube(args: list[str], payload: Any = None) -> Any:
# Secret JSON and patch bodies stay in process memory and child stdin.
# In particular, never use -p <patch>, a shell pipeline, or inherited stderr.
result = subprocess.run(
['kubectl', '--request-timeout=20s', '-n', 'sso', *args],
input=None if payload is None else json.dumps(payload),
capture_output=True, text=True, timeout=30, check=False,
)
if result.returncode:
raise IssuerPinError("kubernetes_operation_failed")
return json.loads(result.stdout)
def safe_metadata(obj: dict[str, Any]) -> dict[str, str]:
metadata = obj['metadata']
uid, revision = metadata['uid'], metadata['resourceVersion']
if not re.fullmatch(r'[0-9a-f-]{36}', uid) or not re.fullmatch(r'[0-9]+', revision):
raise IssuerPinError("unexpected_metadata_shape")
return {'uid': uid, 'resource_version': revision}
def issuer_live(mode: str, expected_uid: str | None = None, expected_revision: str | None = None) -> dict[str, Any]:
if mode == 'issuer-pin-live' and (not expected_uid or not expected_revision):
raise IssuerPinError("pin_requires_observed_uid_and_revision")
before = contained_kube(['get', 'secret', 'keycape-config', '-o', 'json'])
if before.get('kind') != 'Secret' or before['metadata'].get('name') != 'keycape-config' or before['metadata'].get('namespace') != 'sso':
raise IssuerPinError("unexpected_target")
metadata = safe_metadata(before)
if mode == 'issuer-pin-live' and metadata != {'uid': expected_uid, 'resource_version': expected_revision}:
raise IssuerPinError("observed_revision_changed")
replacement, changed, prior = issuer_replacement(before)
receipt = {'schema': 'netkingdom.keycape-issuer-pin.v1', 'mode': mode,
'observed_at': datetime.now(timezone.utc).isoformat(),
'target': 'sso/keycape-config', 'verified_issuer': VERIFIED_UPSTREAM_ISSUER,
'before': metadata, 'prior_pin_state': prior,
'issuer_matches': not changed, 'config_changed': False,
'secret_values_emitted': False, 'deployment_changed': False}
if mode == 'issuer-check-live':
return receipt
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': expected_uid},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': expected_revision},
{'op': 'replace', 'path': '/data/config.yaml', 'value': replacement},
]
contained_kube(['patch', 'secret', 'keycape-config', '--type=json', '--patch-file=/dev/stdin', '-o', 'json'], patch)
after = contained_kube(['get', 'secret', 'keycape-config', '-o', 'json'])
expected_data = dict(before['data'], **{'config.yaml': replacement})
if after['metadata']['uid'] != metadata['uid'] or after['data'] != expected_data:
raise IssuerPinError("readback_mismatch_stop_without_replaying_stale_config")
receipt.update({'after': safe_metadata(after), 'issuer_matches': True,
'config_changed': changed, 'other_secret_data_unchanged': True,
'unrelated_config_bytes_preserved': True})
return receipt
def issuer_main(args: Any) -> int:
try:
receipt = issuer_live(args.mode, args.expected_uid, args.expected_resource_version)
except IssuerPinError as exc:
receipt = {'schema': 'netkingdom.keycape-issuer-pin.v1', 'status': 'failed', 'reason': str(exc)}
except Exception:
# Kubernetes/YAML exceptions may contain entire response/config fragments.
receipt = {'schema': 'netkingdom.keycape-issuer-pin.v1', 'status': 'failed', 'reason': 'contained_operation_failed'}
print(json.dumps(receipt, sort_keys=True))
return 1 if receipt.get('status') == 'failed' else 0
def load_config() -> dict[str, Any]:
secret = json.load(sys.stdin)
encoded_config = (secret.get("data") or {}).get("config.yaml")
if not encoded_config:
raise SystemExit("keycape-config Secret does not contain data.config.yaml")
try:
config_text = base64.b64decode(encoded_config).decode("utf-8")
except Exception as exc: # noqa: BLE001 - concise operator error
raise SystemExit("could not decode private config; diagnostics suppressed") from None
config = yaml.safe_load(config_text) or {}
if not isinstance(config, dict):
raise SystemExit("KeyCape config.yaml must decode to a YAML mapping")
return config
def client_errors(config: dict[str, Any], required: dict[str, Any]) -> list[str]:
clients = config.get("clients")
if not isinstance(clients, list):
return ["clients must be a list"]
target = next(
(client for client in clients if isinstance(client, dict) and client.get("clientId") == required["clientId"]),
None,
)
if target is None:
return [f"missing {required['clientId']} client"]
errors: list[str] = []
for key, expected in required.items():
if key in ("redirectUris", "allowedScopes", "grantTypes", "roles"):
missing = sorted(set(expected) - set(target.get(key) or []))
if missing:
errors.append(f"{required['clientId']}.{key} missing: {', '.join(missing)}")
elif target.get(key) != expected:
errors.append(f"{required['clientId']}.{key} should be {expected!r}")
return errors
def retired_redirect_errors(config: dict[str, Any]) -> list[str]:
clients = config.get("clients") if isinstance(config.get("clients"), list) else []
target = next(
(client for client in clients if isinstance(client, dict) and client.get("clientId") == OPENBAO_CLIENT["clientId"]),
{},
)
retired = sorted(set(RETIRED_OPENBAO_REDIRECT_URIS) & set(target.get("redirectUris") or []))
return [f"{OPENBAO_CLIENT['clientId']}.redirectUris retired: {', '.join(retired)}"] if retired else []
def upsert_client(config: dict[str, Any], required: dict[str, Any]) -> dict[str, Any]:
clients = config.get("clients")
if not isinstance(clients, list):
clients = []
config["clients"] = clients
for index, client in enumerate(clients):
if isinstance(client, dict) and client.get("clientId") == required["clientId"]:
clients[index] = dict(required)
return config
clients.append(dict(required))
return config
def lldap_errors(config: dict[str, Any]) -> list[str]:
lldap = config.get("lldap")
if not isinstance(lldap, dict):
return ["lldap must be a mapping"]
return [
f"lldap.{key} should be {expected!r}"
for key, expected in LLDAP_REQUIRED.items()
if lldap.get(key) != expected
]
def enforce_lldap_defaults(config: dict[str, Any]) -> dict[str, Any]:
lldap = config.get("lldap")
if not isinstance(lldap, dict):
lldap = {}
config["lldap"] = lldap
lldap.update(LLDAP_REQUIRED)
return config
def render_patch(config: dict[str, Any]) -> None:
raise SystemExit("Retired: raw registry patch output is disabled; use the reviewed owner rollout adapter.")
def verify(config: dict[str, Any]) -> None:
errors = client_errors(config, OPENBAO_CLIENT) + client_errors(config, RAPP_QONTO_CLIENT) + retired_redirect_errors(config) + lldap_errors(config)
if errors:
for error in errors:
print(f"[FAIL] {error}")
raise SystemExit(1)
print("[PASS] openbao-admin and rapp-qonto clients and LLDAP OU lookup settings are present")
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("mode", choices=("patch", "verify", "issuer-check-live", "issuer-pin-live"))
parser.add_argument("--expected-uid")
parser.add_argument("--expected-resource-version")
args = parser.parse_args()
if args.mode.startswith('issuer-'):
raise SystemExit(issuer_main(args))
if args.mode == "patch":
render_patch({}) # Refuse before reading secret-bearing stdin.
try:
config = load_config()
verify(config)
except (ValueError, TypeError, KeyError, AttributeError, yaml.YAMLError):
raise SystemExit("KeyCape verification failed; private input diagnostics suppressed.") from None
if __name__ == "__main__":
main()

View file

@ -0,0 +1,183 @@
#!/usr/bin/env python3
"""Founder-admitted node wrapper: private local child-ready pipe -> SSH control stdout.
No token read/copy, login, tunnel or approval authentication. Parent must capture
stdout over its admitted authenticated SSH peer; never use the silent warden lane.
"""
import argparse
import contextlib
import hashlib
import json
import math
import os
from pathlib import Path
import signal
import stat
import subprocess
import sys
import threading
import time
SCHEMA='key39-private-input-ready/v1'
PROPOSAL='KEY-WP-0039-T02/attended-lifecycle-v9-ready-input'
TARGET='sso/keycape'
MAX_READY=4096
MAX_ENVELOPE=65536
READY_TOTAL_SECONDS=5
class Refused(Exception):pass
def need(ok,code):
if not ok:raise Refused(code)
def finite(value):
try:return type(value) in (int,float) and math.isfinite(value)
except OverflowError:return False
def hash_bytes(raw):return hashlib.sha256(raw).hexdigest()
def hex_pin(value):return isinstance(value,str) and len(value)==64 and all(c in '0123456789abcdef' for c in value)
@contextlib.contextmanager
def deadline(seconds):
need(finite(seconds) and 0<seconds<=1200,'wrapper_budget_invalid')
need(threading.current_thread() is threading.main_thread()
and signal.getitimer(signal.ITIMER_REAL)==(0.0,0.0),'wrapper_timer_required')
handler=signal.getsignal(signal.SIGALRM)
def expired(*_):raise Refused('wrapper_timeout')
signal.signal(signal.SIGALRM,expired);signal.setitimer(signal.ITIMER_REAL,seconds)
try:yield
finally:signal.setitimer(signal.ITIMER_REAL,0);signal.signal(signal.SIGALRM,handler)
def decode(raw):
def unique(pairs):
result={}
for key,value in pairs:
need(key not in result,'wrapper_duplicate_key');result[key]=value
return result
try:
return json.loads(raw,object_pairs_hook=unique,
parse_constant=lambda _:(_ for _ in ()).throw(Refused('wrapper_nonfinite_json')))
except Refused:raise
except Exception:raise Refused('wrapper_json_invalid') from None
def pinned_file(path,pin,limit,private=False):
p=Path(path);need(p.is_absolute() and p.resolve()==p,'wrapper_absolute_pin_required')
need(hex_pin(pin),'wrapper_pin_required')
fd=os.open(p,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK)
try:
before=os.fstat(fd)
need(stat.S_ISREG(before.st_mode) and 0<before.st_size<=limit,'wrapper_file_invalid')
if private:
need(before.st_uid==os.getuid() and stat.S_IMODE(before.st_mode)==0o600
and before.st_nlink==1,'wrapper_private_envelope_required')
raw=os.read(fd,limit+1);after=os.fstat(fd)
need(len(raw)<=limit and hash_bytes(raw)==pin,'wrapper_source_changed')
need((before.st_dev,before.st_ino,before.st_size,before.st_mtime_ns,before.st_ctime_ns)==
(after.st_dev,after.st_ino,after.st_size,after.st_mtime_ns,after.st_ctime_ns),
'wrapper_file_changed')
return raw
finally:os.close(fd)
def validate_marker(raw,*,nonce,executor_pin,envelope_pin,envelope):
need(isinstance(raw,bytes) and 0<len(raw)<=MAX_READY and raw.endswith(b'\n')
and raw.count(b'\n')==1,'wrapper_ready_invalid')
marker=decode(raw)
expected={'schema','session_nonce','proposal_id','target','executor_sha256','envelope_sha256',
'bao_health_identity_sha256','valid_until_epoch','receive_budget_seconds'}
need(isinstance(marker,dict) and set(marker)==expected,'wrapper_ready_invalid')
need(marker['schema']==SCHEMA and marker['session_nonce']==nonce
and marker['proposal_id']==PROPOSAL and marker['target']==TARGET
and marker['executor_sha256']==executor_pin and marker['envelope_sha256']==envelope_pin
and marker['bao_health_identity_sha256']==envelope['bao_health_identity_sha256']
and marker['valid_until_epoch']==envelope['valid_until_epoch'], 'wrapper_ready_binding_changed')
need(finite(marker['valid_until_epoch']) and time.time()<marker['valid_until_epoch']
and finite(marker['receive_budget_seconds']) and 0<marker['receive_budget_seconds']<=15,
'wrapper_ready_window_expired')
return marker
def read_ready(fd):
raw=b''
while len(raw)<=MAX_READY:
chunk=os.read(fd,MAX_READY+1-len(raw))
if not chunk:break
raw+=chunk
need(len(raw)<=MAX_READY,'wrapper_ready_oversized')
return raw
def forward_ready(raw):
# Wrapper stdout is a captured control pipe. Never child stdout or token data.
current=os.fstat(1)
need(stat.S_ISFIFO(current.st_mode),'wrapper_private_control_pipe_required')
need((current.st_dev,current.st_ino)!=(os.fstat(0).st_dev,os.fstat(0).st_ino),
'wrapper_control_input_alias_refused')
offset=0
while offset<len(raw):
written=os.write(1,raw[offset:]);need(type(written) is int and written>0,'wrapper_control_write_failed')
offset+=written
def run(args):
proc=None;read_fd=None;write_fd=None
try:
need(hex_pin(args.session_nonce),'wrapper_nonce_required')
with deadline(READY_TOTAL_SECONDS):
child=pinned_file(args.child,args.child_sha256,256*1024)
envelope_raw=pinned_file(args.envelope,args.envelope_sha256,MAX_ENVELOPE,private=True)
envelope=decode(envelope_raw)
need(envelope.get('proposal_id')==PROPOSAL and envelope.get('target')==TARGET
and envelope.get('disposition')=='approved' and envelope.get('founder_decision_id'),
'wrapper_exact_owner_envelope_required')
need(envelope.get('source_hashes',{}).get(str(Path(args.child)))==args.child_sha256
and envelope.get('source_hashes',{}).get(str(Path(__file__).resolve()))==hash_bytes(Path(__file__).read_bytes()),
'wrapper_child_and_wrapper_pins_required')
expiry=envelope['valid_until_epoch'];maximum=envelope['maximum_session_seconds']
need(finite(expiry) and type(maximum) is int and 420<=maximum<=900
and 0<expiry-time.time()<=maximum,'wrapper_action_window_invalid')
absolute_end=time.monotonic()+expiry-time.time()+300
read_fd,write_fd=os.pipe()
proc=subprocess.Popen([sys.executable,args.child,'execute','--envelope',args.envelope,
'--receipt',args.receipt,'--ready-fd',str(write_fd),'--session-nonce',args.session_nonce,
'--ready-envelope-sha256',args.envelope_sha256],stdin=sys.stdin.buffer,
stdout=subprocess.PIPE,stderr=subprocess.PIPE,pass_fds=(write_fd,),close_fds=True)
os.close(write_fd);write_fd=None
raw=read_ready(read_fd);os.close(read_fd);read_fd=None
validate_marker(raw,nonce=args.session_nonce,executor_pin=args.child_sha256,
envelope_pin=args.envelope_sha256,envelope=envelope)
forward_ready(raw)
remaining=absolute_end-time.monotonic();need(remaining>0,'wrapper_session_window_expired')
with deadline(remaining):
rc=proc.wait(timeout=remaining)
# Bounded captures: even accidental child output is never forwarded.
unexpected=proc.stdout.read(MAX_READY+1) or proc.stderr.read(MAX_READY+1)
need(not unexpected,'wrapper_child_output_refused')
return 0 if rc==0 else 1
except BaseException:
return 1
finally:
for fd in (read_fd,write_fd):
if fd is not None:
try:os.close(fd)
except OSError:pass
if proc is not None:
if proc.poll() is None:
# Unknown/partial native outcomes remain owner recovery, never retry.
try:
with deadline(5):
proc.terminate();proc.wait(timeout=5)
except Exception:
try:
with deadline(2):
proc.kill();proc.wait(timeout=2)
except Exception:pass
for stream in (proc.stdout,proc.stderr):
if stream is not None:
try:
with deadline(0.02):stream.close()
except Exception:pass
def main():
parser=argparse.ArgumentParser(description=__doc__)
for name in ('child','child-sha256','envelope','envelope-sha256','receipt','session-nonce'):
parser.add_argument('--'+name,required=True)
return run(parser.parse_args())
if __name__=='__main__':raise SystemExit(main())

View file

@ -0,0 +1,860 @@
#!/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 fcntl
import stat
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-v9-ready-input'
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
PRIVATE_INPUT_MAX_BYTES = 65536
PRIVATE_TOKEN_MAX_BYTES = 16384
PRIVATE_INPUT_TOTAL_SECONDS = 15
READY_SCHEMA = 'key39-private-input-ready/v1'
READY_MAX_BYTES = 4096
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',
self.bao_origin + '/v1/sys/health'}
require(url in allowed, 'http_target_not_reviewed')
require(method == ('GET' if url.endswith(('/.well-known/openid-configuration', '/v1/sys/health')) 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 health_identity(self):
# Public endpoint only: no token/header, login, Secret or route creation.
status, raw = self.request(self.bao_origin + '/v1/sys/health', method='GET')
require(status == 200, 'bao_public_health_unavailable')
try:
body = read_json(raw)
require(isinstance(body, dict) and body.get('initialized') is True
and body.get('sealed') is False and body.get('standby') is False
and body.get('performance_standby', False) is False,
'bao_public_health_not_active')
cluster_id = body.get('cluster_id')
require(isinstance(cluster_id, str) and 0 < len(cluster_id) <= 128,
'bao_public_health_identity_required')
return digest(cluster_id.encode())
except (TypeError, ValueError):
raise Refused('bao_public_health_invalid') from None
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 verify_bao_route(transport, envelope, receipt):
expected = envelope.get('bao_health_identity_sha256')
require(isinstance(expected, str) and len(expected) == 64
and all(c in '0123456789abcdef' for c in expected),
'approved_bao_health_identity_required')
require(transport.health_identity() == expected, 'bao_public_health_identity_changed')
receipt['bao_public_route_verified'] = True
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)
verify_bao_route(transport, envelope, 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 emit_private_ready(fd, nonce, expected_envelope_sha256, envelope_sha256, envelope, receipt):
# A value-free synchronization marker, never authentication or a grant.
require(type(fd) is int and fd >= 3, 'private_ready_pipe_required')
require(isinstance(nonce, str) and len(nonce) == 64
and all(c in '0123456789abcdef' for c in nonce), 'ready_nonce_required')
require(expected_envelope_sha256 == envelope_sha256, 'ready_envelope_pin_changed')
validated = False
try:
current = os.fstat(fd)
require(stat.S_ISFIFO(current.st_mode)
and fcntl.fcntl(fd, fcntl.F_GETFL) & os.O_ACCMODE == os.O_WRONLY,
'private_ready_pipe_required')
identity = (current.st_dev, current.st_ino)
require(all(identity != (os.fstat(other).st_dev, os.fstat(other).st_ino)
for other in (0, 1, 2)), 'private_ready_stdio_alias_refused')
validated = True
check_deadline()
marker = {'schema': READY_SCHEMA, 'session_nonce': nonce,
'proposal_id': PROPOSAL, 'target': TARGET,
'executor_sha256': digest(Path(__file__).read_bytes()),
'envelope_sha256': envelope_sha256,
'bao_health_identity_sha256': envelope['bao_health_identity_sha256'],
'valid_until_epoch': envelope['valid_until_epoch'],
'receive_budget_seconds': request_budget(PRIVATE_INPUT_TOTAL_SECONDS)}
payload = json.dumps(marker, sort_keys=True, separators=(',', ':')).encode() + b'\n'
require(len(payload) <= READY_MAX_BYTES, 'private_ready_oversized')
with total_timeout(request_budget(2)):
offset = 0
while offset < len(payload):
written = os.write(fd, payload[offset:])
require(type(written) is int and written > 0, 'private_ready_delivery_failed')
offset += written
check_deadline()
receipt['private_ready_emitted'] = True
except Refused:
raise
except Exception:
raise Refused('private_ready_delivery_failed') from None
finally:
if validated:
os.close(fd)
def read_private_input(stream):
# Complete private JSON plus EOF, within this original approved action window.
# No unbounded read, text buffering, token logging or renewed deadline.
check_deadline()
budget = request_budget(PRIVATE_INPUT_TOTAL_SECONDS)
binary = getattr(stream, 'buffer', None)
require(binary is not None, 'private_binary_input_required')
chunks, size = [], 0
with total_timeout(budget):
while True:
chunk = binary.read(min(4096, PRIVATE_INPUT_MAX_BYTES + 1 - size))
require(isinstance(chunk, bytes), 'private_binary_input_required')
if not chunk:
break
size += len(chunk)
require(size <= PRIVATE_INPUT_MAX_BYTES, 'private_input_oversized')
chunks.append(chunk)
check_deadline()
require(size > 0, 'private_input_empty')
try:
private = read_json(b''.join(chunks))
require(isinstance(private, dict)
and set(private) == {'human_token', 'coding_agent_token'}
and all(isinstance(value, str) and
0 < len(value.encode('utf-8')) <= PRIVATE_TOKEN_MAX_BYTES
for value in private.values()), 'private_input_invalid')
except (TypeError, ValueError, UnicodeError, Refused):
raise Refused('private_input_invalid') from None
check_deadline()
return private
def start_execution_window(envelope):
global REQUEST_DEADLINE, BUSINESS_MONOTONIC_DEADLINE, CLEANUP_MONOTONIC_DEADLINE, CLEANING
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
def execute(envelope, private, receipt, checkpoint=lambda: None, window_started=False):
global CLEANING
leeway = verify_sources(envelope)
if not window_started:
start_execution_window(envelope)
else:
# main already began this exact window before consuming private stdin.
# Preserve its original monotonic budget; input waiting cannot renew it.
require(REQUEST_DEADLINE == envelope['valid_until_epoch']
and BUSINESS_MONOTONIC_DEADLINE is not None and not CLEANING,
'preinput_execution_window_required')
check_deadline()
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')
verify_bao_route(transport, envelope, receipt)
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()
verify_bao_route(transport, envelope, receipt)
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', 'route-preflight', 'founder-preflight', 'execute', 'recover'])
ap.add_argument('--envelope')
ap.add_argument('--bao-addr', choices=sorted(BAO_ORIGINS))
ap.add_argument('--ready-fd', type=int)
ap.add_argument('--session-nonce')
ap.add_argument('--ready-envelope-sha256')
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 == 'route-preflight':
require(args.bao_addr is not None, 'bao_origin_required')
transport = SessionTransport(args.bao_addr, receipt=receipt)
receipt.update(bao_health_identity_sha256=transport.health_identity(),
bao_public_route_verified=True, mode='public_route_preflight',
probe_location='current_process_only')
elif 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')
require(args.bao_addr is not None, 'bao_origin_required')
transport = SessionTransport(args.bao_addr, receipt=receipt)
receipt.update(bao_health_identity_sha256=transport.health_identity(),
bao_public_route_verified=True)
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_raw = Path(args.envelope).read_bytes()
envelope = read_json(envelope_raw)
envelope_sha256 = digest(envelope_raw)
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:
# Public route/source readiness precedes private token consumption.
verify_sources(envelope)
start_execution_window(envelope)
transport = SessionTransport(envelope['bao_addr'], receipt=receipt)
verify_bao_route(transport, envelope, receipt)
emit_private_ready(args.ready_fd, args.session_nonce, args.ready_envelope_sha256,
envelope_sha256, envelope, receipt)
private = read_private_input(__import__('sys').stdin)
execute(envelope, private, receipt, checkpoint=checkpoint, window_started=True)
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())

View file

@ -0,0 +1,176 @@
#!/usr/bin/env python3
"""Turn on KeyCape's sign-in diagnosis page for the openbao-admin client.
NK-WP-0051-T02 / KEY-WP-0041-T02. Adds one line, `signInDiagnosis: true`, to
the existing openbao-admin registration in sso/keycape-config. The field
contract is docs/sign-in-diagnosis.md; the image that honours the flag is
key-cape sha256:cdec9dd7 (source 8c6c071), live since 2026-10-15.
Modes default to non-mutating. `inspect` reads and plans; `dry-run` sends a
server-side dry-run patch; `apply` writes and is reserved for the attended
operator with `--attended` and the observed uid/resourceVersion. `--operation
remove` is the rollback: it removes only that one line. Either way the issuer
reads the change only after `kubectl -n sso rollout restart deploy/keycape`.
Exact-match edit: flag absent -> add; already true -> noop; any other value,
a missing or duplicated openbao-admin client -> fail closed. The signing key,
every other client and every other config byte are preserved. No Secret value,
parser message or Kubernetes error text leaves the process.
"""
from __future__ import annotations
import argparse
import base64
import copy
import importlib.util
import json
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
def module(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
result = importlib.util.module_from_spec(spec)
spec.loader.exec_module(result)
return result
# Reuse the owner lane's contained kubectl, refusal type and cluster pin.
lane = module('portal_rollout', 'portal-client-rollout.py')
pin = lane.pin
Refused = lane.Refused
require = lane.require
CLIENT_ID = 'openbao-admin'
FLAG = 'signInDiagnosis'
LINE = FLAG + ': true'
def _inspect(secret):
raw, config, _ = pin.issuer_document(secret)
require(config['authelia'].get('issuer') == pin.VERIFIED_UPSTREAM_ISSUER,
'verified_issuer_pin_required')
clients = config.get('clients')
require(isinstance(clients, list) and clients, 'client_sequence_required')
require(all(isinstance(c, dict) and 'clientId' in c for c in clients), 'client_shape_unexpected')
ids = [c['clientId'] for c in clients]
require(len(ids) == len(set(ids)), 'duplicate_client_id')
require(CLIENT_ID in ids, 'openbao_client_missing')
position = ids.index(CLIENT_ID)
value = clients[position].get(FLAG, None)
require(value in (None, True), 'existing_flag_differs_requires_reconciliation')
root = yaml.compose(raw)
sequence = next(v for k, v in root.value if k.value == 'clients')
require(isinstance(sequence, yaml.nodes.SequenceNode) and not sequence.flow_style,
'block_client_sequence_required')
item = sequence.value[position]
require(isinstance(item, yaml.nodes.MappingNode) and not item.flow_style,
'block_client_mapping_required')
return raw, config, position, item
def _line_end(raw, index):
end = raw.find('\n', index)
return len(raw) if end < 0 else end + 1
def enable(secret):
"""Return (base64 config, changed) with only the flag line added."""
raw, config, position, item = _inspect(secret)
if config['clients'][position].get(FLAG) is True:
return secret['data']['config.yaml'], False
key, value = item.value[0]
require(isinstance(value, yaml.nodes.ScalarNode)
and key.start_mark.line == value.end_mark.line, 'unexpected_item_layout')
index = _line_end(raw, value.end_mark.index)
prefix = '' if raw[index - 1:index] == '\n' else '\n'
updated = raw[:index] + prefix + ' ' * key.start_mark.column + LINE + '\n' + raw[index:]
expected = copy.deepcopy(config)
expected['clients'][position][FLAG] = True
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def disable(secret):
"""Rollback: return (base64 config, changed) with only the flag line removed."""
raw, config, position, item = _inspect(secret)
if FLAG not in config['clients'][position]:
return secret['data']['config.yaml'], False
key = next(k for k, _ in item.value if k.value == FLAG)
require(key is not item.value[0][0], 'unexpected_item_layout')
start = raw.rfind('\n', 0, key.start_mark.index) + 1
end = _line_end(raw, key.start_mark.index)
require(raw[start:end].strip() == LINE, 'unexpected_item_layout')
updated = raw[:start] + raw[end:]
expected = copy.deepcopy(config)
del expected['clients'][position][FLAG]
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def run(args, kubectl=None):
kubectl = kubectl or lane.kubectl
cluster = kubectl(['get', 'namespace', 'kube-system', '-o', 'json'])
require(cluster['metadata']['uid'] == lane.CLUSTER_UID, 'cluster_identity_changed')
before = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(not before['metadata'].get('ownerReferences'), 'controller_owned_secret')
metadata = pin.safe_metadata(before)
encoded, changed = (enable if args.operation == 'add' else disable)(before)
receipt = {'schema': 'netkingdom.keycape-openbao-signin-diagnosis.v1',
'target': 'sso/keycape-config', 'before': metadata, 'operation': args.operation,
'client_id': CLIENT_ID, 'flag': FLAG, 'change_needed': changed, 'mode': args.mode,
'values_emitted': False, 'changed': False}
if args.mode == 'inspect':
return receipt
if args.mode == 'apply':
require(args.attended, 'attended_operator_required')
require(metadata == {'uid': args.expected_uid, 'resource_version': args.expected_resource_version},
'observed_revision_changed')
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/config.yaml', 'value': encoded},
]
command = ['-n', 'sso', 'patch', 'secret', 'keycape-config', '--type=json',
'--patch-file=/dev/stdin', '-o', 'json']
if args.mode == 'dry-run':
command += ['--dry-run=server']
result = kubectl(command, patch)
require(result['data'] == dict(before['data'], **{'config.yaml': encoded}), 'patch_response_mismatch')
if args.mode == 'apply':
after = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(after['metadata']['uid'] == metadata['uid']
and after['data'] == dict(before['data'], **{'config.yaml': encoded}),
'readback_mismatch_stop_without_stale_replay')
receipt.update(changed=changed, after=pin.safe_metadata(after), restart_required=changed)
receipt.update(unrelated_config_bytes_preserved=True, other_secret_data_unchanged=True)
return receipt
def main(argv=None, kubectl=None):
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
parser.add_argument('mode', nargs='?', default='inspect', choices=['inspect', 'dry-run', 'apply'])
parser.add_argument('--operation', default='add', choices=['add', 'remove'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
parser.add_argument('--attended', action='store_true', help='required for apply')
args = parser.parse_args(argv)
try:
print(json.dumps(run(args, kubectl), sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except pin.IssuerPinError as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
# Parser and Kubernetes errors can contain full Secret material.
print(json.dumps({'status': 'failed', 'reason': 'contained_operation_failed'}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Legacy unguarded registry mutation withdrawn under NK-WP-0049.
set +x
printf '%s\n' 'Retired: use the reviewed KeyCape client registration/rollout adapter with exact source, object/version checks, contained diagnostics and owner authorization. No registry or credential is read or changed here.' >&2
exit 2

View file

@ -0,0 +1,236 @@
"""Private, digest-bound Playwright registry/Deployment patch preparation.
No CLI or Kubernetes client: the existing protected owner execution path must
provide fresh objects, an independently approved proposal digest, admitted
custody, exclusive maintenance, and readiness/login verification. Proposal and
patch bodies contain private registry data; never log or serialize them to chat.
Only summary() is an output receipt. This library grants no execution authority.
"""
from __future__ import annotations
import base64
import copy
import hashlib
import importlib.util
import json
from pathlib import Path
import re
import subprocess
spec = importlib.util.spec_from_file_location('playwright_registration', Path(__file__).with_name('playwright-registration.py'))
registration = importlib.util.module_from_spec(spec)
spec.loader.exec_module(registration)
require = registration.require
Refused = registration.Refused
def digest(value):
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':'),
allow_nan=False).encode()).hexdigest()
def target(obj, kind, name):
require(obj.get('kind') == kind and obj.get('metadata', {}).get('name') == name
and obj['metadata'].get('namespace') == 'sso', 'unexpected_target')
require(not obj['metadata'].get('deletionTimestamp') and
not obj['metadata'].get('ownerReferences'), 'target_not_independently_available')
return registration.registry.pin.safe_metadata(obj)
def container(specification):
items = specification['template']['spec']['containers']
matches = [item for item in items if item.get('name') == 'keycape']
require(len(matches) == 1, 'keycape_container_required')
return matches[0]
def stopped(specification):
result = copy.deepcopy(specification)
result['replicas'] = 0
return result
def build(secret, deployment, identities, source_revision):
secret_meta = target(secret, 'Secret', 'keycape-config')
deploy_meta = target(deployment, 'Deployment', 'keycape')
require(not secret.get('immutable'), 'mutable_registry_required')
require(re.fullmatch(r'[0-9a-f]{40}', source_revision or '') is not None, 'exact_source_revision_required')
original = deployment['spec']
require(original.get('replicas') == 1 and original.get('strategy', {}).get('type') == 'Recreate'
and not original.get('paused'), 'single_recreate_issuer_required')
current = container(original)
require(re.fullmatch(r'forgejo\.coulomb\.social/coulomb/key-cape@sha256:[0-9a-f]{64}',
current.get('image', '')) is not None, 'immutable_owner_image_required')
require(not current.get('envFrom'), 'explicit_env_bindings_required')
env = current.get('env', [])
require(isinstance(env, list) and all(isinstance(e, dict) and isinstance(e.get('name'), str) for e in env),
'env_shape_invalid')
names = [e['name'] for e in env]
require(len(names) == len(set(names)), 'duplicate_env_name')
require([e for e in env if e['name'] == 'KEYCAPE_CONFIG'] ==
[{'name': 'KEYCAPE_CONFIG', 'value': '/etc/keycape/config.yaml'}], 'config_path_drift')
volumes = original['template']['spec'].get('volumes', [])
mounts = current.get('volumeMounts', [])
expected = [v['name'] for v in volumes if v.get('secret', {}).get('secretName') == 'keycape-config']
require(len(expected) == 1 and any(m.get('name') == expected[0] and
m.get('mountPath') == '/etc/keycape' and m.get('readOnly') is True and
not m.get('subPath') and not m.get('subPathExpr') for m in mounts), 'registry_mount_drift')
encoded, created = registration.prepare_registry(secret, identities)
proposed_data = dict(secret['data'], **{'config.yaml': encoded})
proposed_spec = copy.deepcopy(original)
proposed_env = container(proposed_spec).setdefault('env', [])
added_env = []
records = registration.clients(identities)
for record in records[1:]:
binding = registration.registry.env_binding(record)
target_ref = binding['valueFrom']['secretKeyRef']
for entry in proposed_env:
reference = entry.get('valueFrom', {}).get('secretKeyRef', {})
require(entry['name'] == binding['name'] or
(reference.get('name'), reference.get('key')) != (target_ref['name'], target_ref['key']),
'delivery_secret_already_bound')
existing = [e for e in proposed_env if e['name'] == binding['name']]
require(not existing or existing == [binding], 'existing_binding_requires_reconciliation')
if not existing:
proposed_env.append(binding)
added_env.append(binding['name'])
return {'schema': 'netkingdom.playwright-activation.v1', 'source_revision': source_revision,
'secret_identity': secret_meta, 'deployment_identity': deploy_meta,
'before_data': copy.deepcopy(secret['data']), 'after_data': proposed_data,
'before_spec': copy.deepcopy(original), 'after_spec': proposed_spec,
'identity_inventory': copy.deepcopy(identities), 'created_client_ids': created,
'created_env_names': added_env}
def summary(proposal):
changed = proposal['before_data'] != proposal['after_data'] or proposal['before_spec'] != proposal['after_spec']
return {'schema': proposal['schema'], 'proposal_digest': digest(proposal),
'source_revision': proposal['source_revision'],
'created_client_ids': proposal['created_client_ids'],
'created_env_names': proposal['created_env_names'],
'change_needed': changed, 'maintenance_required': changed,
'live_activated': False, 'custody_admitted': False}
def check_registry(proposal, binary, expected_binary_sha256):
"""Run the exact admitted native verifier, no ambient env or raw diagnostics."""
path = Path(binary)
require(path.is_file() and not path.is_symlink() and
hashlib.sha256(path.read_bytes()).hexdigest() == expected_binary_sha256,
'native_verifier_digest_mismatch')
for stage in ('before', 'after'):
env_names = [e['name'] for e in container(proposal[stage + '_spec']).get('env', [])]
command = [str(path), 'check-config', '--config', '/dev/stdin', '--env-names',
','.join(env_names) or '__NO_ENV_NAMES__']
if 'KEYCAPE_POLICY_PATH' in env_names:
command.append('--policy-store')
try:
result = subprocess.run(command, input=base64.b64decode(proposal[stage + '_data']['config.yaml'], validate=True),
capture_output=True, timeout=60, env={'PATH': '/usr/bin:/bin'})
report = json.loads(result.stdout)
valid = result.returncode == 0 and report.get('valid') is True and not report.get('errors')
except Exception:
valid = False
require(valid, 'native_registry_check_failed')
return {'original_registry_valid': True, 'proposed_registry_valid': True,
'verifier_sha256': expected_binary_sha256}
def quiescent(deployment, pods):
"""Caller supplies an unfiltered complete sso PodList from the same cluster."""
status = deployment.get('status', {})
require(status.get('observedGeneration') == deployment['metadata'].get('generation')
and deployment['spec'].get('replicas') == 0 and
all(status.get(k, 0) == 0 for k in ['replicas', 'readyReplicas', 'availableReplicas', 'updatedReplicas']),
'issuer_not_stopped')
require(pods.get('kind') == 'PodList' and not pods.get('metadata', {}).get('continue')
and isinstance(pods.get('items'), list), 'complete_namespace_pod_list_required')
# Terminating pods are still consumers. Do not infer quiescence from readiness.
for pod in pods['items']:
require(pod.get('metadata', {}).get('namespace') == 'sso', 'pod_inventory_namespace_mismatch')
pod_spec = pod.get('spec', {})
uses_registry = any(v.get('secret', {}).get('secretName') == 'keycape-config' or
any(s.get('secret', {}).get('name') == 'keycape-config'
for s in v.get('projected', {}).get('sources', []))
for v in pod_spec.get('volumes', []))
for c in pod_spec.get('containers', []) + pod_spec.get('initContainers', []) + pod_spec.get('ephemeralContainers', []):
uses_registry = uses_registry or any(e.get('secretRef', {}).get('name') == 'keycape-config'
for e in c.get('envFrom', [])) or any(
e.get('valueFrom', {}).get('secretKeyRef', {}).get('name') == 'keycape-config'
for e in c.get('env', []))
require(not uses_registry, 'registry_consumer_still_present')
def propose_step(proposal, approved_digest, phase, secret, deployment, pods):
"""Return one private CAS patch or None for an exact no-op; never execute it.
The caller must freshly read BOTH objects before EACH phase, verify every
response, and stop on unknown outcomes. The digest binds content, not authority.
Kubernetes has no cross-object transaction; exclusive maintenance is required.
"""
require(digest(proposal) == approved_digest, 'proposal_revision_changed')
secret_meta = target(secret, 'Secret', 'keycape-config')
deploy_meta = target(deployment, 'Deployment', 'keycape')
require(secret_meta['uid'] == proposal['secret_identity']['uid'] and
deploy_meta['uid'] == proposal['deployment_identity']['uid'], 'target_identity_changed')
old_data, new_data = proposal['before_data'], proposal['after_data']
old_spec, new_spec = proposal['before_spec'], proposal['after_spec']
data, current = secret['data'], deployment['spec']
require(data == old_data or data == new_data, 'registry_drift_stop_without_stale_replay')
require(any(current == item for item in [old_spec, new_spec, stopped(old_spec), stopped(new_spec)]),
'deployment_drift_stop_without_stale_replay')
require(phase in ('stop', 'registry', 'bindings', 'resume', 'rollback-stop',
'rollback-registry', 'rollback-bindings', 'rollback-resume'), 'phase_invalid')
if phase.startswith('rollback-') and data == old_data and current == old_spec:
return None
if old_data == new_data and old_spec == new_spec:
require(data == old_data and current == old_spec, 'noop_state_changed')
return None
if phase == 'stop':
require(secret_meta == proposal['secret_identity'] and deploy_meta == proposal['deployment_identity']
and data == old_data and current == old_spec, 'initial_revision_changed')
status = deployment.get('status', {})
require(status.get('observedGeneration') == deployment['metadata'].get('generation') and
all(status.get(k) == 1 for k in ['replicas', 'readyReplicas', 'availableReplicas', 'updatedReplicas']),
'initial_issuer_not_ready')
obj, field, wanted = deployment, 'spec', stopped(old_spec)
elif phase == 'rollback-stop':
obj, field, wanted = deployment, 'spec', stopped(current)
else:
quiescent(deployment, pods)
if phase == 'registry':
require(current == stopped(old_spec) and data == old_data, 'registry_phase_out_of_order')
obj, field, wanted = secret, 'data', new_data
elif phase == 'bindings':
require(current == stopped(old_spec) and data == new_data, 'bindings_phase_out_of_order')
obj, field, wanted = deployment, 'spec', stopped(new_spec)
elif phase == 'resume':
require(current == stopped(new_spec) and data == new_data, 'resume_phase_out_of_order')
obj, field, wanted = deployment, 'spec', new_spec
elif phase == 'rollback-registry':
obj, field, wanted = secret, 'data', old_data
elif phase == 'rollback-bindings':
require(data == old_data, 'restore_registry_before_bindings')
obj, field, wanted = deployment, 'spec', stopped(old_spec)
else:
require(data == old_data and current == stopped(old_spec), 'rollback_resume_out_of_order')
obj, field, wanted = deployment, 'spec', old_spec
if obj[field] == wanted:
return None
metadata = registration.registry.pin.safe_metadata(obj)
patch = [{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'test', 'path': '/' + field, 'value': copy.deepcopy(obj[field])},
{'op': 'replace', 'path': '/' + field, 'value': copy.deepcopy(wanted)}]
return {'kind': obj['kind'], 'namespace': 'sso', 'name': obj['metadata']['name'],
'uid': metadata['uid'], 'field': field, 'expected': copy.deepcopy(wanted),
'proposal_digest': approved_digest, 'phase': phase, 'patch': patch}
def verify_response(operation, obj):
"""Also use on a fresh GET after an acknowledged patch; failures stop the caller."""
metadata = target(obj, operation['kind'], operation['name'])
require(metadata['uid'] == operation['uid'] and obj.get(operation['field']) == operation['expected'],
'mutation_readback_mismatch')
return {'phase': operation['phase'], 'proposal_digest': operation['proposal_digest'],
'uid': metadata['uid'], 'resource_version': metadata['resource_version'], 'readback_matches': True}

View file

@ -0,0 +1,162 @@
#!/usr/bin/env python3
"""Prepare Playwright client records; no credential access or live mutation.
The registry adapter below is for an owner-controlled activation ceremony. Its
encoded result contains the original private registry and must stay in memory.
The CLI emits only newly constructed non-secret client/binding proposals.
"""
from __future__ import annotations
import argparse
import copy
from decimal import Decimal
import importlib.util
import json
from pathlib import Path
import re
import sys
spec = importlib.util.spec_from_file_location('playwright_registry', Path(__file__).with_name('keycape-client.py'))
registry = importlib.util.module_from_spec(spec)
spec.loader.exec_module(registry)
require = registry.require
Refused = registry.Refused
AUDIENCE = 'playwright-kubernetes'
SCOPE = 'playwright:connect'
CALLBACK = 'http://127.0.0.1:18764/callback'
ISSUER = 'https://kc.coulomb.social'
SLUG = re.compile(r'[a-z0-9][a-z0-9-]{0,39}\Z')
IDENTIFIER = re.compile(r'[a-z0-9][a-z0-9:._-]{0,159}\Z')
def public_client():
return {'clientId': 'playwright-cli', 'audience': AUDIENCE,
'displayName': 'Playwright platform consumer CLI',
'redirectUris': [CALLBACK],
'allowedScopes': ['openid', 'profile', 'groups', SCOPE],
'grantTypes': ['authorization_code'], 'clientType': 'public'}
def agent_client(identity):
"""Reference an existing registered identity; never manufacture its subject/tenant."""
require(isinstance(identity, dict) and set(identity) ==
{'agent_id', 'service_subject', 'tenant', 'identity_record'}, 'agent_identity_fields_required')
require(all(isinstance(v, str) for v in identity.values()), 'agent_identity_strings_required')
require(SLUG.fullmatch(identity['agent_id']) is not None, 'agent_id_invalid')
require(identity['service_subject'].startswith('service:') and
IDENTIFIER.fullmatch(identity['service_subject']) is not None and
len(identity['service_subject']) > len('service:'), 'registered_service_subject_required')
require(identity['tenant'].startswith('tenant:') and
IDENTIFIER.fullmatch(identity['tenant']) is not None and
len(identity['tenant']) > len('tenant:'), 'registered_tenant_required')
# This is a reference for owner review, not proof that registration exists.
reference = identity['identity_record']
require(1 <= len(reference) <= 512 and all(32 < ord(c) < 127 for c in reference),
'identity_record_reference_required')
cid = 'playwright-agent-' + identity['agent_id']
return {'clientId': cid, 'audience': AUDIENCE,
'displayName': 'Playwright agent ' + identity['agent_id'],
'allowedScopes': [SCOPE], 'grantTypes': ['client_credentials'],
'clientType': 'confidential', 'secretRef': 'env:' + registry.env_name(cid),
'serviceSubject': identity['service_subject'], 'tenant': identity['tenant'],
'roles': ['coding-agent'], 'tokenLifetime': '15m'}
def clients(identities):
require(isinstance(identities, list) and len(identities) <= 100, 'agent_inventory_invalid')
result = [public_client()] + [agent_client(identity) for identity in identities]
for field in ['clientId', 'serviceSubject', 'secretRef']:
values = [c[field] for c in result if field in c]
require(len(values) == len(set(values)), 'duplicate_agent_identity_or_binding')
return result
def human_lifetime(config):
"""Bound the existing issuer setting; do not alter all humans' token lifetime."""
raw = config.get('tokenLifetime')
require(isinstance(raw, str) and 0 < len(raw) <= 128, 'explicit_human_token_lifetime_required')
units = {'h': Decimal(3600), 'm': Decimal(60), 's': Decimal(1),
'ms': Decimal('.001'), 'us': Decimal('.000001'),
'µs': Decimal('.000001'), 'μs': Decimal('.000001'), 'ns': Decimal('.000000001')}
parts = re.findall(r'([0-9]+(?:\.[0-9]*)?|\.[0-9]+)(ns|us|µs|μs|ms|s|m|h)', raw)
require(parts and ''.join(value + unit for value, unit in parts) == raw,
'human_token_lifetime_invalid')
seconds = sum((Decimal(value) * units[unit] for value, unit in parts), Decimal(0))
require(1 <= seconds <= 3600, 'human_token_lifetime_outside_cluster_bound')
return seconds
def prepare_registry(secret, identities, operation='add'):
"""Byte-preserving proposal only. Caller owns CAS, check-config and readback.
Refuse drift before constructing any result. A missing client during removal
is an idempotent no-op. Removal does not revoke issued tokens/connections.
"""
require(operation in ('add', 'remove'), 'operation_invalid')
wanted = clients(identities)
_, config, _ = registry.pin.issuer_document(secret)
require(config.get('issuer') == ISSUER, 'keycape_issuer_mismatch')
human_lifetime(config)
require(isinstance(config.get('clients'), list), 'client_sequence_required')
for existing in config['clients']:
require(isinstance(existing, dict), 'client_shape_unexpected')
if (existing.get('audience') or existing.get('clientId')) == AUDIENCE:
require(existing in wanted, 'unreviewed_playwright_audience_registration')
# Preflight every record against the same original, including collisions.
for client in wanted:
_, current, _ = registry._inspect(secret, client)
if 'serviceSubject' in client:
for other in current['clients']:
if other['clientId'] != client['clientId'] and (other.get('audience') or other.get('clientId')) == AUDIENCE:
require(other.get('serviceSubject') != client['serviceSubject'],
'playwright_subject_already_registered')
matching = [c for c in current['clients'] if c['clientId'] == client['clientId']]
require(not matching or matching == [client], 'existing_registration_differs_requires_reconciliation')
candidate = copy.deepcopy(secret)
changed_ids = []
merge = registry.replacement if operation == 'add' else registry.removal
for client in wanted:
encoded, changed = merge(candidate, client)
candidate['data']['config.yaml'] = encoded
if changed:
changed_ids.append(client['clientId'])
return candidate['data']['config.yaml'], changed_ids
def unique_object(pairs):
result = {}
for key, value in pairs:
require(key not in result, 'duplicate_input_field')
result[key] = value
return result
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
parser.add_argument('--agents', help='Non-secret JSON array of existing agent identity references')
args = parser.parse_args(argv)
try:
identities = []
if args.agents:
with Path(args.agents).open('rb') as stream:
raw = stream.read(65537)
require(len(raw) <= 65536, 'agent_inventory_too_large')
identities = json.loads(raw, object_pairs_hook=unique_object)
records = clients(identities)
print(json.dumps({'schema': 'netkingdom.playwright-registration.v1',
'status': 'source_proposal', 'clients': records,
'identity_references': {c['clientId']: identity['identity_record']
for c, identity in zip(records[1:], identities)},
'deployment_env': [registry.env_binding(c) for c in records if 'secretRef' in c],
'identity_evidence_verified': False,
'custody_admitted': False, 'live_activated': False}, indent=2))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
print(json.dumps({'status': 'refused', 'reason': 'invalid_registration_input'}))
return 1
if __name__ == '__main__':
sys.exit(main())

View file

@ -0,0 +1,159 @@
#!/usr/bin/env python3
"""Restore the approved public portal client without exporting Secret values."""
from __future__ import annotations
import argparse
import base64
import copy
import importlib.util
import json
from pathlib import Path
import subprocess
import yaml
ROOT = Path(__file__).resolve().parent
def module(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
result = importlib.util.module_from_spec(spec)
spec.loader.exec_module(result)
return result
pin = module('issuer_pin', 'openbao-client-config.py')
portal = module('portal_registration', 'register-user-engine-portal.py')
CLUSTER_UID = 'a553c742-0115-43d4-99a4-a5ca56fe0786'
class Refused(Exception):
"""Only fixed reason codes may leave the operation."""
def require(condition, reason):
if not condition:
raise Refused(reason)
def replacement(secret):
raw, config, _ = pin.issuer_document(secret)
clients = config.get('clients')
require(isinstance(clients, list) and clients, 'client_sequence_required')
ids = [client['clientId'] for client in clients]
require(len(ids) == len(set(ids)), 'duplicate_client_id')
if portal.CLIENT_ID in ids:
current = clients[ids.index(portal.CLIENT_ID)]
if current == portal.CLIENT:
return secret['data']['config.yaml'], False
legacy = dict(portal.CLIENT, redirectUris=[
'https://users.92-205-62-239.nip.io/oidc/callback'])
require(current == legacy, 'existing_registration_differs_requires_reconciliation')
# Only the exact reviewed legacy public client may gain this callback.
root = yaml.compose(raw)
node = next(value for key, value in root.value if key.value == 'clients')
item = node.value[ids.index(portal.CLIENT_ID)]
redirects = next(value for key, value in item.value if key.value == 'redirectUris')
require(isinstance(redirects, yaml.nodes.SequenceNode) and not redirects.flow_style,
'block_redirect_sequence_required')
index = redirects.end_mark.index
line_start = raw.rfind('\n', 0, index) + 1
if not raw[line_start:index].strip():
index = line_start
addition = ' ' * redirects.start_mark.column + '- https://users.coulomb.social/oidc/callback\n'
if index and raw[index - 1] != '\n':
addition = '\n' + addition
updated = raw[:index] + addition + raw[index:]
expected = copy.deepcopy(config)
expected['clients'][ids.index(portal.CLIENT_ID)] = portal.CLIENT
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected,
'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
root = yaml.compose(raw)
node = next(value for key, value in root.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
line_start = raw.rfind('\n', 0, index) + 1
if not raw[line_start:index].strip():
index = line_start
indent = ' ' * node.start_mark.column
addition = yaml.safe_dump([portal.CLIENT], sort_keys=False)
addition = ''.join(indent + line if line.strip() else line
for line in addition.splitlines(True))
if index and raw[index - 1] != '\n':
addition = '\n' + addition
updated = raw[:index] + addition + raw[index:]
expected = copy.deepcopy(config)
expected['clients'].append(portal.CLIENT)
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected,
'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def kubectl(arguments, payload=None):
args = ['kubectl', '--kubeconfig', '/home/worsch/.kube/config-hosteurope',
'--server', 'https://127.0.0.1:16444', '--request-timeout=20s', *arguments]
result = subprocess.run(args, input=None if payload is None else json.dumps(payload),
text=True, capture_output=True, timeout=30)
require(result.returncode == 0, 'kubernetes_operation_failed')
return json.loads(result.stdout)
def run(args):
cluster = kubectl(['get', 'namespace', 'kube-system', '-o', 'json'])
require(cluster['metadata']['uid'] == CLUSTER_UID, 'cluster_identity_changed')
before = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(not before['metadata'].get('ownerReferences'), 'controller_owned_secret')
metadata = pin.safe_metadata(before)
encoded, changed = replacement(before)
receipt = {'target': 'sso/keycape-config', 'before': metadata,
'client': portal.CLIENT, 'change_needed': changed,
'mode': args.mode, 'values_emitted': False, 'changed': False}
if args.mode == 'inspect':
return receipt
require(metadata == {'uid': args.expected_uid,
'resource_version': args.expected_resource_version},
'observed_revision_changed')
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/config.yaml', 'value': encoded},
]
command = ['-n', 'sso', 'patch', 'secret', 'keycape-config', '--type=json',
'--patch-file=/dev/stdin', '-o', 'json']
if args.mode == 'dry-run':
command += ['--dry-run=server']
result = kubectl(command, patch)
require(result['data'] == dict(before['data'], **{'config.yaml': encoded}),
'patch_response_mismatch')
if args.mode == 'apply':
after = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(after['metadata']['uid'] == metadata['uid'] and
after['data'] == dict(before['data'], **{'config.yaml': encoded}),
'readback_mismatch_stop_without_stale_replay')
receipt.update(changed=changed, after=pin.safe_metadata(after))
receipt.update(unrelated_config_bytes_preserved=True, other_secret_data_unchanged=True)
return receipt
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('mode', choices=['inspect', 'dry-run', 'apply'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
args = parser.parse_args()
try:
print(json.dumps(run(args), sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
# Parser and Kubernetes errors can contain full Secret material.
print(json.dumps({'status': 'failed', 'reason': 'contained_operation_failed'}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Retired: KeyCape uses the scoped, renewable factor-reader lane.
set +x
set -euo pipefail
printf '%s\n' 'ERROR: personal privacyIDEA admin-token delivery is retired.' 'Use railiance-platform RPF-WP-0040 scoped factor custody, ESO delivery and renewal recovery.' 'This helper performs no login, credential read, Secret change or restart.' >&2
exit 1

View file

@ -0,0 +1,44 @@
#!/usr/bin/env python3
"""Idempotently register coulomb.social in the live KeyCape Secret.
The complete Secret travels over stdin/stdout between kubectl and this
process. Secret values are never printed to the terminal or written to disk.
"""
from __future__ import annotations
import base64
import json
import sys
import yaml
CLIENT_ID = "coulomb-social"
CLIENT = {
"clientId": CLIENT_ID,
"displayName": "coulomb.social",
"redirectUris": [
"http://127.0.0.1:8008/auth/callback/",
"http://localhost:8008/auth/callback/",
# Parallel public host while apex coulomb.social remains Bubble
"https://app.coulomb.social/auth/callback/",
# Future apex cutover (keep registered; not the live app host yet)
"https://coulomb.social/auth/callback/",
],
"allowedScopes": ["openid", "profile", "email", "groups"],
"grantTypes": ["authorization_code"],
"clientType": "public",
# Ordinary coulomb.social sessions are AAL1 unless the client explicitly
# requests AAL2 through acr_values. Platform/admin clients keep their
# existing provider-driven mandatory MFA behavior.
"mfaRequired": False,
}
def main() -> None:
print("Retired: use the owner-reviewed KeyCape client registration adapter; raw registry output is disabled.", file=sys.stderr)
raise SystemExit(2)
if __name__ == "__main__":
main()

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Legacy unguarded registry mutation withdrawn under NK-WP-0049.
set +x
printf '%s\n' 'Retired: use the reviewed KeyCape client registration/rollout adapter with exact source, object/version checks, contained diagnostics and owner authorization. No registry or credential is read or changed here.' >&2
exit 2

View file

@ -0,0 +1,53 @@
#!/usr/bin/env python3
"""Idempotently register the user-engine portal in the live KeyCape Secret.
The complete Secret travels over stdin/stdout between kubectl and this
process. Secret values are never printed to the terminal or written to disk.
"""
from __future__ import annotations
import base64
import json
import sys
import yaml
CLIENT_ID = "user-engine-portal"
CLIENT = {
"clientId": CLIENT_ID,
"displayName": "User Engine Portal",
"redirectUris": ["https://users.92-205-62-239.nip.io/oidc/callback",
"https://users.coulomb.social/oidc/callback"],
"allowedScopes": ["openid", "profile", "email", "groups"],
"grantTypes": ["authorization_code"],
"clientType": "public",
}
def main() -> None:
secret = json.load(sys.stdin)
encoded = secret.get("data", {}).get("config.yaml")
if not encoded:
raise SystemExit("keycape-config does not contain config.yaml")
config = yaml.safe_load(base64.b64decode(encoded))
clients = config.setdefault("clients", [])
existing = next(
(index for index, client in enumerate(clients) if client.get("clientId") == CLIENT_ID),
None,
)
if existing is None:
clients.append(CLIENT)
else:
clients[existing] = CLIENT
rendered = yaml.safe_dump(config, sort_keys=False).encode()
secret["data"]["config.yaml"] = base64.b64encode(rendered).decode()
secret.pop("status", None)
metadata = secret.get("metadata", {})
for key in ("creationTimestamp", "managedFields", "resourceVersion", "uid"):
metadata.pop(key, None)
json.dump(secret, sys.stdout, separators=(",", ":"))
if __name__ == "__main__":
main()

View file

@ -0,0 +1,222 @@
#!/usr/bin/env python3
"""Owner-specific KeyCape registration of the secrets-engine-openbao service client.
KEY-WP-0037-T03 / NK-WP-0046. Source preparation only: the live operation
needs the founder's separate go (State Hub decision
caafe4be-4f68-47b4-9567-316d47b94992) and the platform-materialised Secret
keycape-secrets-engine-openbao-client (key client-secret). This helper merges
only the client registration into sso/keycape-config; the single Deployment
env binding is the separate proposed patch
secrets-engine-openbao-client.proposed.patch.yaml.
Modes default to non-mutating. `inspect` reads and plans; `dry-run` sends a
server-side dry-run patch; `apply` writes and is reserved for the attended
operator under the live go. `--operation remove` is the rollback: it removes
only this exact registration and nothing else.
Exact-match merge: absent -> add only this client; identical -> noop;
duplicates, drift or a shared secretRef -> fail closed. The signing key,
unrelated clients, policy and every other config byte are preserved. No Secret
value, parser message or Kubernetes error text leaves the process.
"""
from __future__ import annotations
import argparse
import base64
import copy
import importlib.util
import json
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent
def module(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
result = importlib.util.module_from_spec(spec)
spec.loader.exec_module(result)
return result
# Reuse the owner lane's contained kubectl, refusal type and cluster pin.
lane = module('portal_rollout', 'portal-client-rollout.py')
pin = lane.pin
Refused = lane.Refused
require = lane.require
LIVE_GO_DECISION = 'caafe4be-4f68-47b4-9567-316d47b94992'
CLIENT_ID = 'secrets-engine-openbao'
ENV_NAME = 'KEYCAPE_SECRETS_ENGINE_OPENBAO_CLIENT_SECRET'
SECRET_NAME = 'keycape-secrets-engine-openbao-client'
SECRET_KEY = 'client-secret'
# Verbatim from key-cape config/service-clients.example.yaml (reviewed at
# KeyCape e22029e). No `audience` key: the audience defaults to the clientId.
CLIENT = {
'clientId': CLIENT_ID,
'displayName': 'secrets-engine OpenBao login',
'allowedScopes': ['openbao:login'],
'grantTypes': ['client_credentials'],
'clientType': 'confidential',
'secretRef': 'env:' + ENV_NAME,
'serviceSubject': 'service:secrets-engine',
'tenant': 'tenant:platform',
'roles': ['secrets-engine'],
'tokenLifetime': '15m',
}
ENV_BINDING = {'name': ENV_NAME, 'valueFrom': {
'secretKeyRef': {'name': SECRET_NAME, 'key': SECRET_KEY}}}
def _client_node(raw):
root = yaml.compose(raw)
node = next(value for key, value in root.value if key.value == 'clients')
require(isinstance(node, yaml.nodes.SequenceNode) and not node.flow_style,
'block_client_sequence_required')
return node
def _line_start(raw, index):
start = raw.rfind('\n', 0, index) + 1
return start if not raw[start:index].strip() else index
def _inspect(secret):
raw, config, _ = pin.issuer_document(secret)
require(config['authelia'].get('issuer') == pin.VERIFIED_UPSTREAM_ISSUER,
'verified_issuer_pin_required')
clients = config.get('clients')
require(isinstance(clients, list) and clients, 'client_sequence_required')
require(all(isinstance(c, dict) and 'clientId' in c for c in clients), 'client_shape_unexpected')
ids = [c['clientId'] for c in clients]
require(len(ids) == len(set(ids)), 'duplicate_client_id')
others = [c for c in clients if c['clientId'] != CLIENT_ID]
require(all(c.get('secretRef') != CLIENT['secretRef'] for c in others), 'secret_ref_already_bound')
return raw, config, ids
def replacement(secret):
"""Return (base64 config, changed) with only this client added."""
raw, config, ids = _inspect(secret)
if CLIENT_ID in ids:
require(config['clients'][ids.index(CLIENT_ID)] == CLIENT,
'existing_registration_differs_requires_reconciliation')
return secret['data']['config.yaml'], False
node = _client_node(raw)
index = _line_start(raw, node.end_mark.index)
indent = ' ' * node.start_mark.column
fragment = yaml.safe_dump([CLIENT], sort_keys=False, width=120)
fragment = ''.join(indent + 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:]
expected = copy.deepcopy(config)
expected['clients'].append(CLIENT)
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def removal(secret):
"""Rollback: return (base64 config, changed) with only this exact client removed."""
raw, config, ids = _inspect(secret)
if CLIENT_ID not in ids:
return secret['data']['config.yaml'], False
position = ids.index(CLIENT_ID)
require(config['clients'][position] == CLIENT, 'existing_registration_differs_requires_reconciliation')
require(len(ids) > 1, 'removal_would_empty_client_sequence')
node = _client_node(raw)
item = node.value[position]
start = raw.rfind('\n', 0, item.start_mark.index) + 1
require(raw[start:item.start_mark.index].strip() == '-', 'unexpected_item_layout')
if position + 1 < len(node.value):
following = node.value[position + 1].start_mark.index
end = raw.rfind('\n', 0, following) + 1
else:
end = _line_start(raw, node.end_mark.index)
updated = raw[:start] + raw[end:]
expected = copy.deepcopy(config)
del expected['clients'][position]
require(yaml.load(updated, Loader=pin.UniqueLoader) == expected, 'unrelated_configuration_changed')
return base64.b64encode(updated.encode()).decode(), True
def deployment_binding(deployment):
"""Report whether the live Deployment already carries exactly the one binding."""
containers = deployment['spec']['template']['spec']['containers']
container = next(c for c in containers if c['name'] == 'keycape')
matches = [e for e in container.get('env', []) if e.get('name') == ENV_NAME]
require(len(matches) <= 1 and all(e == ENV_BINDING for e in matches), 'env_binding_drift')
return bool(matches)
def run(args):
kubectl = lane.kubectl
cluster = kubectl(['get', 'namespace', 'kube-system', '-o', 'json'])
require(cluster['metadata']['uid'] == lane.CLUSTER_UID, 'cluster_identity_changed')
before = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(not before['metadata'].get('ownerReferences'), 'controller_owned_secret')
metadata = pin.safe_metadata(before)
encoded, changed = (replacement if args.operation == 'add' else removal)(before)
deployment = kubectl(['-n', 'sso', 'get', 'deployment', 'keycape', '-o', 'json'])
bound = deployment_binding(deployment)
receipt = {'schema': 'netkingdom.keycape-secrets-engine-openbao-client.v1',
'target': 'sso/keycape-config', 'before': metadata, 'operation': args.operation,
'client_id': CLIENT_ID, 'change_needed': changed, 'mode': args.mode,
'deployment_env_bound': bound, 'values_emitted': False, 'changed': False}
if args.mode == 'inspect':
return receipt
if args.mode == 'apply':
# Attended only: the founder's live go must be named and the plan revision pinned.
require(args.live_go_decision == LIVE_GO_DECISION, 'founder_live_go_required')
if args.operation == 'add':
# Startup resolves secretRef; a registration without its env binding
# would make the next pod start fail closed.
require(bound, 'deployment_env_binding_required_first')
require(metadata == {'uid': args.expected_uid, 'resource_version': args.expected_resource_version},
'observed_revision_changed')
if changed:
patch = [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/config.yaml', 'value': encoded},
]
command = ['-n', 'sso', 'patch', 'secret', 'keycape-config', '--type=json',
'--patch-file=/dev/stdin', '-o', 'json']
if args.mode == 'dry-run':
command += ['--dry-run=server']
result = kubectl(command, patch)
require(result['data'] == dict(before['data'], **{'config.yaml': encoded}), 'patch_response_mismatch')
if args.mode == 'apply':
after = kubectl(['-n', 'sso', 'get', 'secret', 'keycape-config', '-o', 'json'])
require(after['metadata']['uid'] == metadata['uid']
and after['data'] == dict(before['data'], **{'config.yaml': encoded}),
'readback_mismatch_stop_without_stale_replay')
receipt.update(changed=changed, after=pin.safe_metadata(after), restart_required=changed)
receipt.update(unrelated_config_bytes_preserved=True, other_secret_data_unchanged=True)
return receipt
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
parser.add_argument('mode', nargs='?', default='inspect', choices=['inspect', 'dry-run', 'apply'])
parser.add_argument('--operation', default='add', choices=['add', 'remove'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
parser.add_argument('--live-go-decision', help='required for apply: ' + LIVE_GO_DECISION)
args = parser.parse_args(argv)
try:
print(json.dumps(run(args), sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except pin.IssuerPinError as error:
print(json.dumps({'status': 'refused', 'reason': str(error)}))
except Exception:
# Parser and Kubernetes errors can contain full Secret material.
print(json.dumps({'status': 'failed', 'reason': 'contained_operation_failed'}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,141 @@
import base64
import copy
import importlib.util
import json
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
import yaml
PATH = Path(__file__).with_name('approval-clients-rollout.py')
spec = importlib.util.spec_from_file_location('rollout', PATH)
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
def fixture(raw=None):
raw = raw or 'authelia:\n issuer: https://auth.coulomb.social\n clientSecret: synthetic-fixture\nclients:\n - clientId: existing-human\n allowedScopes: [openid]\n# trailing owner comment\nother: preserved\n'
return {'kind': 'Secret', 'metadata': {'name': 'keycape-config', 'namespace': 'sso',
'uid': '2e94519d-1550-41c7-9701-2efe47fe1fd3', 'resourceVersion': '100'},
'data': {'config.yaml': base64.b64encode(raw.encode()).decode(), 'key.pem': 'c3ludGhldGlj'}}
def deployment():
source = next(yaml.safe_load_all((PATH.parent / 'deployment.yaml').read_text()))
source['metadata'].update(uid='99ddd83c-cb3f-4847-bcf8-35f1aa87627f', resourceVersion='101')
# The helper is a one-shot rollout from PRIOR_IMAGE; deployment.yaml has
# since advanced past it, so reduce the fixture to the pre-rollout state:
# the prior image and none of the approval-client env the helper adds.
container = next(c for c in source['spec']['template']['spec']['containers'] if c['name'] == 'keycape')
container['image'] = m.PRIOR_IMAGE
added = {x['name'] for x in yaml.safe_load(
(m.KEYCAPE / 'docs/approval-clients-deployment.patch.yaml').read_text()
)['spec']['template']['spec']['containers'][0]['env']}
container['env'] = [x for x in container.get('env', []) if x['name'] not in added]
return source
class RolloutTests(unittest.TestCase):
def test_append_preserves_original_bytes_and_signing_key(self):
secret = fixture(); before = copy.deepcopy(secret)
actual = base64.b64decode(m.replacement(secret, m.registrations())).decode()
original = base64.b64decode(secret['data']['config.yaml']).decode()
insertion_start = original.index('other:')
self.assertTrue(actual.startswith(original[:insertion_start]))
self.assertTrue(actual.endswith(original[insertion_start:]))
self.assertEqual(secret, before)
self.assertEqual(len(yaml.safe_load(actual)['clients']), 3)
def test_indentless_and_final_client_sequence(self):
for text in ['authelia:\n issuer: https://auth.coulomb.social\nclients:\n- clientId: human\nother: keep\n',
'authelia:\n issuer: https://auth.coulomb.social\nclients:\n - clientId: human']:
parsed = yaml.safe_load(base64.b64decode(m.replacement(fixture(text), m.registrations())))
self.assertEqual(len(parsed['clients']), 3)
def test_reject_duplicate_existing_registration(self):
text = 'authelia:\n issuer: https://auth.coulomb.social\nclients:\n - clientId: secrets-engine-approval\n'
with self.assertRaisesRegex(m.LaneError, 'existing_client_registration'):
m.replacement(fixture(text), m.registrations())
def test_reject_ambiguous_yaml_and_wrong_issuer(self):
for raw in ['authelia: {}\nauthelia: {}\nclients: []',
'authelia:\n issuer: https://wrong.invalid\nclients:\n - clientId: human\n']:
with self.assertRaises((m.LaneError, m.pin.IssuerPinError)):
m.replacement(fixture(raw), m.registrations())
def test_candidate_preserves_existing_settings(self):
old = deployment(); result = m.candidate_spec(old)
before = old['spec']['template']['spec']['containers'][0]
after = result['template']['spec']['containers'][0]
self.assertEqual(result['strategy'], {'type': 'Recreate'})
self.assertEqual(after['env'][:len(before['env'])], before['env'])
for field in ['livenessProbe', 'resources', 'volumeMounts', 'startupProbe']:
self.assertEqual(after[field], before[field])
self.assertEqual(after['image'], m.IMAGE)
def test_candidate_refuses_current_declared_image(self):
current = next(yaml.safe_load_all((PATH.parent / 'deployment.yaml').read_text()))
with self.assertRaisesRegex(m.LaneError, 'prior_image_drift'):
m.candidate_spec(current)
def test_cas_patch_contains_both_preconditions_and_stdin_only(self):
with patch.object(m, 'command') as command:
command.return_value.stdout = b'{}'
m.patch_object(['kubectl'], 'secret', fixture(), '/data/config.yaml', 'synthetic-payload')
args, kwargs = command.call_args
self.assertNotIn('synthetic-payload', args[0])
self.assertEqual([p['path'] for p in kwargs['payload'][:2]], ['/metadata/uid', '/metadata/resourceVersion'])
def test_failed_acceptance_restores_compatible_pair(self):
state = {'secret': fixture(), 'deployment': deployment()}
original = copy.deepcopy(state)
def fake_patch(kube, kind, obj, path, value, dry=False):
changed = copy.deepcopy(obj)
if path == '/spec': changed['spec'] = value
else: changed['data']['config.yaml'] = value
if not dry:
changed['metadata']['resourceVersion'] = str(int(changed['metadata']['resourceVersion']) + 1)
state[kind] = changed
return copy.deepcopy(changed)
with tempfile.TemporaryDirectory() as directory, patch.object(m, 'assert_cluster'), patch.object(m, 'verify_artifact'), \
patch.object(m, 'get', side_effect=lambda kube, kind, name: copy.deepcopy(state[kind])), \
patch.object(m, 'patch_object', side_effect=fake_patch), patch.object(m, 'ready', return_value={}), \
patch.object(m, 'acceptance', side_effect=m.LaneError('synthetic_acceptance_failure')):
receipt = {}
with self.assertRaisesRegex(m.LaneError, 'synthetic_acceptance_failure'):
m.rollout([], receipt, Path(directory) / 'recovery.json')
self.assertTrue(receipt['compatible_pair_restored'])
self.assertEqual(state['secret']['data'], original['secret']['data'])
self.assertEqual(state['deployment']['spec'], original['deployment']['spec'])
self.assertEqual((Path(directory) / 'recovery.json').stat().st_mode & 0o777, 0o600)
def test_ready_matches_manifest_imageid_not_runtime_config_id(self):
dep = deployment(); dep['metadata']['generation'] = 30
dep['status'] = {'observedGeneration': 30, 'updatedReplicas': 1, 'readyReplicas': 1, 'availableReplicas': 1, 'replicas': 1}
pod = {'metadata': {'uid': 'fixture-pod'}, 'status': {'containerStatuses': [
{'name': 'keycape', 'ready': True, 'image': 'sha256:runtime-config-id', 'imageID': m.IMAGE}]}}
from types import SimpleNamespace
with patch.object(m, 'get', return_value=dep), patch.object(m, 'command', return_value=SimpleNamespace(stdout=json.dumps({'items': [pod]}).encode())):
self.assertTrue(m.ready([], m.IMAGE, timeout=1)['single_ready_replica'])
def test_iat_skew_matches_native_contract_without_extending_expiry(self):
from cryptography.hazmat.primitives.asymmetric import rsa
key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
now = int(m.time.time())
claims = {'iss': m.ISSUER, 'aud': 'approval-engine', 'sub': 'synthetic-service', 'iat': now + 2, 'exp': now + 902}
encoded = m.jwt.encode(claims, key, algorithm='RS256')
self.assertEqual(m.verified_claims(encoded, key.public_key())['iat'], now + 2)
future = m.jwt.encode(dict(claims, iat=now+60), key, algorithm='RS256')
with self.assertRaisesRegex(m.LaneError, 'issued_at_binding_failed'):
m.verified_claims(future, key.public_key())
expired = m.jwt.encode(dict(claims, iat=now-900, exp=now-1), key, algorithm='RS256')
with self.assertRaises(m.jwt.ExpiredSignatureError):
m.verified_claims(expired, key.public_key())
def test_unrelated_refusal_never_passes(self):
for status, body in [(500, {}), (400, {'error': 'invalid_profile_usage', 'feature': 'client_id'}),
(401, {'error': 'invalid_profile_usage', 'feature': 'scope'})]:
with self.assertRaises(m.LaneError):
m.denied((status, body), 400, 'scope')
if __name__ == '__main__': unittest.main()

View file

@ -0,0 +1,35 @@
import base64,importlib.util,json,unittest
from pathlib import Path
import yaml
spec=importlib.util.spec_from_file_location('rollout',Path(__file__).with_name('authentication-policy-rollout.py'))
module=importlib.util.module_from_spec(spec);spec.loader.exec_module(module)
class PolicyMigration(unittest.TestCase):
def secret(self):
raw='''# exact bytes outside reviewed fields must survive
issuer: https://fixture.test
authelia:
issuer: https://primary.test
clients:
- clientId: user-engine-portal
clientType: public
grantTypes: [authorization_code]
- clientId: untouched
mfaRequired: true
clientSecret: fixture-secret
- clientId: vergabe-demo-company
clientType: public
grantTypes: [authorization_code]
privacyidea:
requireForAll: true
'''
return {'data':{'config.yaml':base64.b64encode(raw.encode()).decode(),'key.pem':'fixture-key'}}
def test_exact_migration_preserves_siblings_and_retries(self):
secret=self.secret();value,changed=module.replacement(secret);self.assertTrue(changed)
raw=base64.b64decode(value).decode();self.assertIn('# exact bytes',raw);self.assertIn(' clientSecret: fixture-secret\n',raw)
config=yaml.safe_load(raw);self.assertTrue(config['privacyidea']['requireForAll'])
self.assertTrue(config['clients'][0]['mfaOptional']);self.assertTrue(config['clients'][2]['mfaOptional'])
secret['data']['config.yaml']=value;again,changed=module.replacement(secret);self.assertFalse(changed);self.assertEqual(value,again)
def test_explicit_or_duplicate_registration_refused(self):
for edit in [lambda s:s.replace(' clientType: public',' mfaRequired: false\n clientType: public',1),lambda s:s.replace('clientId: untouched','clientId: user-engine-portal')]:
secret=self.secret();secret['data']['config.yaml']=base64.b64encode(edit(base64.b64decode(secret['data']['config.yaml']).decode()).encode()).decode()
with self.assertRaises(module.Refused):module.replacement(secret)

View file

@ -0,0 +1,115 @@
"""Issuer-only mutation, concurrency and output-containment regression checks."""
import argparse
import base64
import copy
import importlib.util
import io
import json
import subprocess
import unittest
from contextlib import redirect_stdout, redirect_stderr
from pathlib import Path
from unittest.mock import patch
spec = importlib.util.spec_from_file_location('owner_config', Path(__file__).with_name('openbao-client-config.py'))
m = importlib.util.module_from_spec(spec)
spec.loader.exec_module(m)
UID = '2e94519d-1550-41c7-9701-2efe47fe1fd3'
SENTINEL = 'DO-NOT-EMIT-fixture-private-value'
RAW = '# retained comment\nauthelia:\n baseURL: https://auth.coulomb.social\n clientSecret: '+SENTINEL+'\nclients:\n - clientId: existing\n secretRef: env:EXISTING\n'
def secret(raw=RAW):
return {'kind': 'Secret', 'metadata': {'name': 'keycape-config', 'namespace': 'sso', 'uid': UID, 'resourceVersion': '12'},
'data': {'config.yaml': base64.b64encode(raw.encode()).decode(), 'key.pem': base64.b64encode(SENTINEL.encode()).decode()}}
class IssuerPinTest(unittest.TestCase):
def test_add_preserves_every_other_byte(self):
encoded, changed, prior = m.issuer_replacement(secret())
updated = base64.b64decode(encoded).decode()
self.assertTrue(changed)
self.assertEqual(prior, 'absent')
self.assertEqual(updated.replace(' issuer: "https://auth.coulomb.social"\n', ''), RAW)
def test_replace_retains_comments_and_other_fields(self):
raw = RAW.replace('authelia:\n', 'authelia:\n issuer: "http://old.example" # keep\n')
encoded, changed, prior = m.issuer_replacement(secret(raw))
self.assertTrue(changed)
self.assertEqual(prior, 'different')
self.assertEqual(base64.b64decode(encoded).decode(), raw.replace('"http://old.example"', '"https://auth.coulomb.social"'))
def test_matching_is_byte_identical_noop(self):
raw = RAW.replace('authelia:\n', 'authelia:\n issuer: https://auth.coulomb.social\n')
source = secret(raw)
self.assertEqual(m.issuer_replacement(source), (source['data']['config.yaml'], False, 'matching'))
def test_ambiguous_documents_and_aliases_are_refused(self):
for raw in [RAW+'authelia: {}\n', RAW.replace(' baseURL:', ' issuer: a\n issuer: b\n baseURL:'),
'authelia: {issuer: old}\n', RAW+'---\nauthelia: {}\n', RAW+'alias: &shared value\ncopy: *shared\n']:
with self.subTest(raw=raw), self.assertRaises(Exception):
m.issuer_replacement(secret(raw))
def test_check_never_patches_or_reports_old_value(self):
source = secret(RAW.replace('authelia:\n', 'authelia:\n issuer: '+SENTINEL+'\n'))
with patch.object(m, 'contained_kube', return_value=source) as kube:
result = m.issuer_live('issuer-check-live')
self.assertEqual(kube.call_count, 1)
self.assertFalse(result['issuer_matches'])
self.assertNotIn(SENTINEL, json.dumps(result))
def test_pin_checks_observed_revision_before_mutation(self):
with patch.object(m, 'contained_kube', return_value=secret()) as kube:
with self.assertRaisesRegex(m.IssuerPinError, 'observed_revision_changed'):
m.issuer_live('issuer-pin-live', UID, '11')
self.assertEqual(kube.call_count, 1)
def test_pin_uses_cas_and_proves_other_secret_data_unchanged(self):
state = secret()
calls = []
def kube(args, payload=None):
calls.append((args, payload))
if args[0] == 'patch':
self.assertEqual(payload[:2], [{'op': 'test', 'path': '/metadata/uid', 'value': UID},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': '12'}])
self.assertEqual(payload[2]['path'], '/data/config.yaml')
state['data']['config.yaml'] = payload[2]['value']
state['metadata']['resourceVersion'] = '13'
return copy.deepcopy(state)
with patch.object(m, 'contained_kube', side_effect=kube):
result = m.issuer_live('issuer-pin-live', UID, '12')
self.assertEqual(len(calls), 3)
self.assertTrue(result['issuer_matches'])
self.assertTrue(result['other_secret_data_unchanged'])
self.assertNotIn(SENTINEL, json.dumps(result))
def test_readback_race_is_not_retried_or_overwritten(self):
after = secret()
after['data']['key.pem'] = 'changed-by-another-actor'
with patch.object(m, 'contained_kube', side_effect=[secret(), {}, after]) as kube:
with self.assertRaisesRegex(m.IssuerPinError, 'readback_mismatch'):
m.issuer_live('issuer-pin-live', UID, '12')
self.assertEqual(kube.call_count, 3)
def test_subprocess_error_output_and_parser_errors_stay_contained(self):
args = argparse.Namespace(mode='issuer-check-live', expected_uid=None, expected_resource_version=None)
failures = [ValueError(SENTINEL), subprocess.TimeoutExpired(['kubectl'], 30, output=SENTINEL)]
for failure in failures:
output = io.StringIO()
with patch.object(m, 'contained_kube', side_effect=failure), redirect_stdout(output), redirect_stderr(output):
self.assertEqual(m.issuer_main(args), 1)
self.assertNotIn(SENTINEL, output.getvalue())
self.assertEqual(json.loads(output.getvalue())['reason'], 'contained_operation_failed')
def test_patch_payload_only_goes_to_captured_child_stdin(self):
with patch.object(m.subprocess, 'run', return_value=subprocess.CompletedProcess([], 1, SENTINEL, SENTINEL)) as run:
with self.assertRaisesRegex(m.IssuerPinError, '^kubernetes_operation_failed$'):
m.contained_kube(['patch', 'secret', 'keycape-config'], {'data': SENTINEL})
args, kwargs = run.call_args
self.assertNotIn(SENTINEL, json.dumps(args))
self.assertIn(SENTINEL, kwargs['input'])
self.assertTrue(kwargs['capture_output'])
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,66 @@
"""Opt-in real-checker boundary regressions; no production inputs or checker stub.
Set KEYCAPE_TEST_CHECKER_BINARY and KEYCAPE_TEST_CHECKER_SHA256 to an independently
built artifact. Ordinary helper-only runs skip this class when none is supplied.
"""
from contextlib import chdir
import hashlib
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from test_keycape_client import kc
VALID = '''issuer: https://issuer.example.invalid
port: 8080
privateKeyPem: /nonexistent/synthetic-key.pem
clients:
- clientId: synthetic-client
displayName: Synthetic client
redirectUris: [https://client.example.invalid/cb]
allowedScopes: [openid]
grantTypes: [authorization_code]
clientType: public
'''
class RealCheckerEnvironmentTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
source = os.environ.get('KEYCAPE_TEST_CHECKER_BINARY')
if not source:
raise unittest.SkipTest('exact real checker artifact not supplied')
data = Path(source).read_bytes()
expected = os.environ.get('KEYCAPE_TEST_CHECKER_SHA256')
if expected != 'sha256:' + hashlib.sha256(data).hexdigest():
raise ValueError('real checker artifact digest differs')
cls.directory = tempfile.TemporaryDirectory()
cls.addClassCleanup(cls.directory.cleanup)
cls.binary = Path(cls.directory.name) / 'keycape'
cls.binary.write_bytes(data)
cls.binary.chmod(0o700)
def check(self, raw, binary=None):
return kc.check_config(str(binary or self.binary), raw, [], False)
def test_ambient_overrides_cannot_reject_supplied_valid_config(self):
with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'http://polluted.example.invalid',
'KEYCAPE_BROWSER_LOGOUT_URL': 'http://polluted.example.invalid/logout'}):
self.assertTrue(self.check(VALID)['valid'])
def test_ambient_overrides_cannot_admit_supplied_invalid_config(self):
invalid = VALID + 'accountPortalURL: http://invalid.example.invalid\nbrowserLogoutURL: http://invalid.example.invalid/logout\n'
with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'https://polluted.example.invalid',
'KEYCAPE_BROWSER_LOGOUT_URL': 'https://polluted.example.invalid/logout'}):
self.assertFalse(self.check(invalid)['valid'])
def test_relative_selected_artifact_is_not_replaced_by_path_decoy(self):
decoy = Path(self.directory.name) / 'decoy'
decoy.mkdir()
trap = decoy / 'keycape'
trap.write_text('#!/bin/sh\nexit 27\n')
trap.chmod(0o700)
with chdir(self.directory.name), patch.dict(os.environ, {'PATH': str(decoy)}):
self.assertTrue(self.check(VALID, binary=Path('keycape'))['valid'])

View file

@ -0,0 +1,122 @@
"""Linux executable-byte custody fixtures; no credentials or native authority."""
import fcntl
import hashlib
import mmap
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from test_keycape_registration_adapter import a
from test_keycape_checker_environment import VALID
class SnapshotTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.addCleanup(self.temp.cleanup)
self.source = Path(self.temp.name) / 'checker'
self.source.write_bytes(b'synthetic artifact')
self.expected = a.digest(self.source.read_bytes())
def test_snapshot_bytes_sealed_and_descriptor_closed_on_exit(self):
with a.checker_snapshot(self.source, self.expected) as fd:
self.assertEqual(Path(f'/proc/self/fd/{fd}').read_bytes(), self.source.read_bytes())
for action in (lambda: os.write(fd, b'x'), lambda: os.ftruncate(fd, 0),
lambda: os.ftruncate(fd, 128)):
with self.assertRaises(OSError):
action()
# Even if this process restores write permission, seals forbid writes.
os.fchmod(fd, 0o700)
reopened = os.open(f'/proc/self/fd/{fd}', os.O_RDWR)
try:
with self.assertRaises(OSError):
os.write(reopened, b'x')
with self.assertRaises(OSError):
mmap.mmap(reopened, len(b'synthetic artifact'), access=mmap.ACCESS_WRITE)
finally:
os.close(reopened)
with self.assertRaises(OSError):
os.fstat(fd)
def test_hash_mismatch_refused_before_memfd(self):
self.source.write_bytes(b'changed')
with patch.object(a.os, 'memfd_create') as create:
with self.assertRaises(a.Refused):
with a.checker_snapshot(self.source, self.expected):
self.fail('unexpected snapshot')
create.assert_not_called()
def test_source_symlink_refused(self):
link = self.source.with_name('link')
link.symlink_to(self.source)
with self.assertRaises(OSError):
with a.checker_snapshot(link, self.expected):
self.fail('unexpected snapshot')
def test_fifo_refused_without_blocking(self):
fifo = self.source.with_name('fifo')
os.mkfifo(fifo)
with self.assertRaises(a.Refused):
with a.checker_snapshot(fifo, self.expected):
self.fail('unexpected snapshot')
def test_sealing_failure_closes_snapshot(self):
original = a.os.memfd_create
opened = []
def create(*args):
fd = original(*args)
opened.append(fd)
return fd
with patch.object(a.os, 'memfd_create', side_effect=create), patch.object(a.fcntl, 'fcntl', side_effect=OSError('synthetic failure')):
with self.assertRaises(OSError):
with a.checker_snapshot(self.source, self.expected):
self.fail('unexpected snapshot')
with self.assertRaises(OSError):
os.fstat(opened[0])
def test_seal_readback_mismatch_refused(self):
original = a.fcntl.fcntl
def invoke(fd, command, *args):
return 0 if command == fcntl.F_GET_SEALS else original(fd, command, *args)
with patch.object(a.fcntl, 'fcntl', side_effect=invoke):
with self.assertRaises(a.Refused):
with a.checker_snapshot(self.source, self.expected):
self.fail('unexpected snapshot')
class RealSnapshotTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
source = os.environ.get('KEYCAPE_TEST_CHECKER_BINARY')
if not source:
raise unittest.SkipTest('exact real checker artifact not supplied')
cls.data = Path(source).read_bytes()
cls.expected = os.environ.get('KEYCAPE_TEST_CHECKER_SHA256')
if cls.expected != 'sha256:' + hashlib.sha256(cls.data).hexdigest():
raise ValueError('real checker artifact digest differs')
def test_real_checker_executes_pinned_snapshot_after_selected_path_replaced(self):
with tempfile.TemporaryDirectory() as directory:
source = Path(directory) / 'keycape'
source.write_bytes(self.data)
source.chmod(0o700)
with a.checker_snapshot(source, self.expected) as fd:
replacement = source.with_name('replacement')
replacement.write_text('#!/bin/sh\nexit 27\n')
replacement.chmod(0o700)
replacement.replace(source)
result = a.helper.check_config(source, VALID, [], False, executable_fd=fd)
self.assertTrue(result['valid'])
invalid = VALID + 'accountPortalURL: http://invalid.example.invalid\n'
self.assertFalse(a.helper.check_config(source, invalid, [], False, executable_fd=fd)['valid'])
def test_real_checker_ignores_in_place_source_overwrite_after_snapshot(self):
with tempfile.TemporaryDirectory() as directory:
source = Path(directory) / 'keycape'
source.write_bytes(self.data)
with a.checker_snapshot(source, self.expected) as fd:
source.write_bytes(b'no executable remains at the selected path')
with patch.dict(os.environ, {'KEYCAPE_ACCOUNT_PORTAL_URL': 'http://polluted.example.invalid'}):
self.assertTrue(a.helper.check_config(source, VALID, [], False, executable_fd=fd)['valid'])

View file

@ -0,0 +1,317 @@
"""Actual helper/CAS execution against synthetic Kubernetes objects only."""
from dataclasses import asdict, replace
import copy
import importlib.util
import json
import os
from pathlib import Path
import tempfile
import unittest
from unittest.mock import patch
from warden.case_run import Invocation, digest
from warden.case_receipt_state import Outcome, Pins, Refused
import yaml
ROOT = Path(__file__).parent
spec = importlib.util.spec_from_file_location('registration_adapter', ROOT / 'keycape-registration-adapter.py')
a = importlib.util.module_from_spec(spec)
spec.loader.exec_module(a)
spec = importlib.util.spec_from_file_location('registration_fixture', ROOT / 'test_keycape_client.py')
f = importlib.util.module_from_spec(spec)
spec.loader.exec_module(f)
class RegistrationTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.binary = Path(self.temp.name) / 'keycape'
self.binary.write_bytes(b'synthetic checker binary')
self.case = yaml.safe_dump({'spec': {'type': 'oidc-browser-relying-party@1.1',
'parameters': f.FORGEJO_PARAMS}}).encode()
desc = a.descriptor(digest(self.binary.read_bytes()))
self.desc_bytes = json.dumps(asdict(desc), sort_keys=True, separators=(',', ':')).encode()
pins = Pins(digest(self.case), digest(b'type'), digest(self.desc_bytes),
digest(b'catalog'), digest(b'case-acceptance:test'), digest(b'run sheet'))
self.admitted = Invocation('register-client', 'net-kingdom', 'forgejo', 'human',
'founder', 'key-cape-oidc-login', pins)
self.inv = replace(self.admitted, attempt_id='attempt-one', operation='run')
self.secret, self.raw = f.fixture()
self.effects = []
self.evidence = {}
self.revocations = []
self.kw = dict(admitted=self.admitted, case_source=self.case,
expected_uid=f.UID, expected_resource_version='7', case_acceptance='case-acceptance:test',
check_config=self.binary, check_config_digest=digest(self.binary.read_bytes()),
publish=self.publish, authenticate=lambda ref, payload: self.evidence.get(ref) == payload,
revoke_session=self.revoke)
self.check_patch = patch.object(a.helper, 'check_config', return_value={'valid': True})
self.kube_patch = patch.object(a.helper.lane, 'kubectl', side_effect=self.kube)
self.check_patch.start()
self.kube_patch.start()
self.addCleanup(self.check_patch.stop)
self.addCleanup(self.kube_patch.stop)
self.addCleanup(self.temp.cleanup)
def publish(self, payload):
ref = 'owner-evidence:attempt-' + str(len(self.evidence))
self.evidence[ref] = payload
return ref
def revoke(self, inv):
self.revocations.append(inv)
return 'session-revoked:' + inv.operation + ':' + inv.attempt_id
def kube(self, args, payload=None):
if args[:3] == ['get', 'namespace', 'kube-system']:
return {'metadata': {'uid': a.helper.lane.CLUSTER_UID}}
if args[:5] == ['-n', 'sso', 'get', 'secret', 'keycape-config']:
return copy.deepcopy(self.secret)
if args[:5] == ['-n', 'sso', 'get', 'deployment', 'keycape']:
return {'spec': {'template': {'spec': {'containers': [{'name': 'keycape',
'env': [a.helper.env_binding(f.FORGEJO)]}]}}}}
if args[:5] == ['-n', 'sso', 'patch', 'secret', 'keycape-config']:
self.assertEqual(payload[:2], [
{'op': 'test', 'path': '/metadata/uid', 'value': self.secret['metadata']['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': self.secret['metadata']['resourceVersion']}])
self.effects.append(copy.deepcopy(payload))
self.secret['data']['config.yaml'] = payload[2]['value']
self.secret['metadata']['resourceVersion'] = str(int(self.secret['metadata']['resourceVersion']) + 1)
return copy.deepcopy(self.secret)
raise AssertionError('unexpected tooling operation')
def test_actual_add_and_created_only_rollback_preserve_original_bytes(self):
adapter = a.build(**self.kw)
original = copy.deepcopy(self.secret['data'])
result = adapter.invoke(self.inv)
self.assertEqual(result.outcome, Outcome.SUCCESS)
self.assertEqual(result.created, ('keycape-client',))
self.assertTrue(adapter.verify(self.inv, result))
undone = adapter.rollback(replace(self.inv, attempt_id='attempt-two', operation='rollback'), result.created)
self.assertEqual(undone.outcome, Outcome.ROLLED_BACK)
self.assertEqual(self.secret['data'], original)
self.assertEqual(len(self.effects), 2)
self.assertEqual(len(self.revocations), 2)
for payload in self.evidence.values():
self.assertNotIn(b'synthetic-marker', payload)
self.assertNotIn(b'config.yaml', payload)
self.assertNotIn(b'signing-key', payload)
def test_adapter_refuses_symlink_fifo_and_oversized_source_before_helper(self):
symlink = self.binary.with_name('symlink')
symlink.symlink_to(self.binary)
fifo = self.binary.with_name('fifo')
os.mkfifo(fifo)
oversized = self.binary.with_name('oversized')
with oversized.open('wb') as output:
output.truncate(64 * 1024 * 1024 + 1)
for source in (symlink, fifo, oversized):
with self.subTest(source=source.name), patch.object(a.helper, 'run') as run:
adapter = a.build(**dict(self.kw, check_config=source))
with self.assertRaises(Refused):
adapter.invoke(self.inv)
run.assert_not_called()
self.assertEqual(self.revocations, [self.inv] * 3)
self.assertFalse(self.effects)
self.assertFalse(self.evidence)
def test_snapshot_unavailable_refuses_before_tooling_and_revokes_session(self):
adapter = a.build(**self.kw)
with patch.object(a.os, 'memfd_create', side_effect=OSError('synthetic policy denial')):
with self.assertRaises(Refused):
adapter.invoke(self.inv)
self.assertFalse(self.effects)
self.assertFalse(self.evidence)
self.assertEqual(self.revocations, [self.inv])
def test_identical_client_is_no_change_and_cannot_be_removed(self):
self.secret, _ = f.fixture(f.EXISTING + [f.FORGEJO])
adapter = a.build(**self.kw)
result = adapter.invoke(self.inv)
self.assertEqual(result.outcome, Outcome.NO_CHANGE)
self.assertEqual(result.created, ())
with self.assertRaises(Refused):
adapter.rollback(replace(self.inv, operation='rollback'), ('keycape-client',))
self.assertFalse(self.effects)
def test_existing_drift_refused_and_session_revoked(self):
self.secret, _ = f.fixture(f.EXISTING + [dict(f.FORGEJO, displayName='Drift')])
with self.assertRaises(Refused):
a.build(**self.kw).invoke(self.inv)
self.assertFalse(self.effects)
self.assertEqual(self.revocations, [self.inv])
def test_invocation_rebinding_refused_before_tooling(self):
for changes in ({'target': 'other'}, {'principal': 'other'}, {'actor': 'agent'},
{'lane': 'other'}, {'operation': 'rollback'}, {'attempt_id': ''},
{'pins': replace(self.inv.pins, case=digest(b'other'))}):
with self.subTest(changes=changes), self.assertRaises(Refused):
a.build(**self.kw).invoke(replace(self.inv, **changes))
self.assertFalse(self.effects)
def test_initial_case_descriptor_and_target_pins_are_required(self):
for changes in ({'case_source': b'{}'}, {'case_acceptance': 'other'}, {'admitted': replace(self.admitted, target='other')},
{'admitted': replace(self.admitted, actor='agent')},
{'admitted': replace(self.admitted, pins=replace(self.admitted.pins, adapter=digest(b'wrong')))}):
with self.subTest(changes=changes), self.assertRaises(Refused):
a.build(**dict(self.kw, **changes))
self.assertFalse(self.effects)
def test_binary_or_source_drift_refused_before_tooling(self):
adapter = a.build(**self.kw)
self.binary.write_bytes(b'changed')
with self.assertRaises(Refused):
adapter.invoke(self.inv)
self.binary.write_bytes(b'synthetic checker binary')
with patch.object(a, 'source_digest', return_value=digest(b'changed')):
with self.assertRaises(Refused):
adapter.invoke(self.inv)
self.assertFalse(self.effects)
def test_substituting_caller_selected_checker_under_same_descriptor_is_refused(self):
substitute = Path(self.temp.name) / 'other-checker'
substitute.write_bytes(b'caller-selected executable')
with self.assertRaisesRegex(Refused, 'descriptor pin differs'):
a.build(**dict(self.kw, check_config=substitute,
check_config_digest=digest(substitute.read_bytes())))
self.assertFalse(self.effects)
def test_stale_revision_refuses_mutation(self):
adapter = a.build(**self.kw)
self.secret['metadata']['resourceVersion'] = '8'
with self.assertRaises(Refused):
adapter.invoke(self.inv)
self.assertFalse(self.effects)
def test_rollback_requires_exact_provenance_and_postcreation_revision(self):
adapter = a.build(**self.kw)
result = adapter.invoke(self.inv)
rollback = replace(self.inv, attempt_id='attempt-two', operation='rollback')
for created in ((), ('other',), ('keycape-client', 'other')):
with self.subTest(created=created), self.assertRaises(Refused):
adapter.rollback(rollback, created)
self.secret['metadata']['resourceVersion'] = '9'
with self.assertRaises(Refused):
adapter.rollback(rollback, result.created)
self.assertEqual(len(self.effects), 1)
def test_missing_or_tampered_evidence_never_verifies_or_authorizes_undo(self):
adapter = a.build(**self.kw)
result = adapter.invoke(self.inv)
self.assertFalse(adapter.verify(replace(self.inv, attempt_id='other'), result))
self.assertFalse(adapter.verify(self.inv, replace(result, revoked=False)))
self.evidence[result.evidence] = b'forged'
self.assertFalse(adapter.verify(self.inv, result))
with self.assertRaises(Refused):
adapter.rollback(replace(self.inv, operation='rollback'), result.created)
self.assertEqual(len(self.effects), 1)
def test_process_loss_does_not_reconstruct_created_authority(self):
result = a.build(**self.kw).invoke(self.inv)
fresh = a.build(**self.kw)
with self.assertRaises(Refused):
fresh.rollback(replace(self.inv, operation='rollback'), result.created)
self.assertIsNone(fresh.reconcile)
self.assertEqual(len(self.effects), 1)
def test_failed_readback_is_uncertain_and_never_provides_creation(self):
original_kube = self.kube
def lost(args, payload=None):
if self.effects and args[:5] == ['-n', 'sso', 'get', 'secret', 'keycape-config']:
raise RuntimeError('synthetic sensitive detail')
return original_kube(args, payload)
with patch.object(a.helper.lane, 'kubectl', side_effect=lost):
adapter = a.build(**self.kw)
with self.assertRaisesRegex(Refused, '^owner callback failed$'):
adapter.invoke(self.inv)
self.assertEqual(len(self.effects), 1)
self.assertFalse(self.evidence)
self.assertEqual(self.revocations, [self.inv])
def test_revocation_or_publication_failure_cannot_produce_verified_success(self):
for capability in ('publish', 'revoke_session'):
self.secret, _ = f.fixture()
with self.subTest(capability=capability):
kw = dict(self.kw, **{capability: lambda *args: (_ for _ in ()).throw(RuntimeError('private'))})
adapter = a.build(**kw)
with self.assertRaisesRegex(Refused, '^owner callback failed$'):
adapter.invoke(self.inv)
self.assertFalse(self.evidence)
def kernel_fixture(self):
# Fixture-only proposed type/case; canonical files are never edited.
# Warden's published producer pins apply unchanged. Owner trust and
# proposed descriptor/type/case are fixture-only, never native admission.
import warden.case_inputs as inputs
from warden.case_run import Executor, Journal
root = ROOT.parents[2]
case = yaml.safe_load((root / 'security-cases/forgejo-netkingdom-oidc.yaml').read_bytes())
case['spec']['status'] = 'materialized'
case_source = yaml.safe_dump(case).encode()
typ = yaml.safe_load((root / 'security-case-types/oidc-browser-relying-party/type.yaml').read_bytes())
typ['spec']['execution_descriptors'] = {'register-client': digest(self.desc_bytes)}
type_source = yaml.safe_dump(typ).encode()
catalog = b'fixture-only-catalog'
sheet = json.dumps(['register-client', 'net-kingdom', 'forgejo', 'human',
'founder', 'key-cape-oidc-login', 'fixture-acceptance', 'fixture-materialization',
'fixture-descriptor', 'fixture-session'], separators=(',', ':')).encode()
pins = Pins(digest(case_source), digest(type_source), digest(self.desc_bytes),
digest(catalog), digest(b'fixture-acceptance'), digest(sheet))
invocation = replace(self.admitted, pins=pins)
adapter = a.build(**dict(self.kw, admitted=invocation, case_source=case_source,
case_acceptance='fixture-acceptance'))
def fixture_verifier(purpose, source, evidence):
if purpose.startswith('session-revocation:'):
_, lane, operation, attempt = purpose.split(':')
return source == digest(case_source) and lane == invocation.lane and evidence == (
'session-revoked:' + operation + ':' + attempt)
return evidence in {'fixture-acceptance', 'fixture-materialization',
'fixture-descriptor', 'fixture-session'}
executor = Executor(Journal(Path(self.temp.name) / 'private'),
{adapter.reference: adapter}, fixture_verifier, browser_inputs=inputs.BrowserInputs(root))
admitted = executor.admit(case_source, type_source, self.desc_bytes, catalog,
principal='founder', actor='human', lane=invocation.lane,
acceptance_evidence='fixture-acceptance', materialization_evidence='fixture-materialization',
descriptor_evidence='fixture-descriptor', session_evidence='fixture-session')
self.assertEqual(admitted[1], invocation)
return executor, admitted
def test_actual_kernel_admission_dispatch_and_rollback_with_fixture_only_trust(self):
executor, admitted = self.kernel_fixture()
result = executor.run(*admitted)
self.assertEqual(result.last.outcome, Outcome.SUCCESS)
self.assertEqual(result.last.created, ('keycape-client',))
undone = executor.rollback(*admitted)
self.assertEqual(undone.last.outcome, Outcome.ROLLED_BACK)
self.assertEqual(len(self.effects), 2)
self.assertEqual(len(self.revocations), 2)
def test_kernel_lost_readback_retains_uncertain_and_refuses_replay_and_undo(self):
executor, admitted = self.kernel_fixture()
original_kube = self.kube
def lost(args, payload=None):
if self.effects and args[:5] == ['-n', 'sso', 'get', 'secret', 'keycape-config']:
raise RuntimeError('synthetic sensitive detail')
return original_kube(args, payload)
with patch.object(a.helper.lane, 'kubectl', side_effect=lost):
result = executor.run(*admitted)
self.assertEqual(result.last.outcome, Outcome.UNCERTAIN)
self.assertFalse(result.last.created)
with self.assertRaises(Refused):
executor.run(*admitted, retry=True)
with self.assertRaises(Refused):
executor.rollback(*admitted)
self.assertEqual(len(self.effects), 1)
self.assertEqual(len(self.revocations), 1)
def test_kernel_external_revision_change_retains_uncertain_rollback(self):
executor, admitted = self.kernel_fixture()
self.assertEqual(executor.run(*admitted).last.outcome, Outcome.SUCCESS)
self.secret['metadata']['resourceVersion'] = '9'
self.assertEqual(executor.rollback(*admitted).last.outcome, Outcome.ROLLBACK_UNCERTAIN)
self.assertEqual(len(self.effects), 1)
self.assertEqual(len(self.revocations), 2)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Focused regression tests for the code-defined OpenBao KeyCape client."""
from __future__ import annotations
import importlib.util
import unittest
from pathlib import Path
MODULE_PATH = Path(__file__).with_name("openbao-client-config.py")
SPEC = importlib.util.spec_from_file_location("openbao_client_config", MODULE_PATH)
assert SPEC and SPEC.loader
MODULE = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(MODULE)
LOOPBACK_CALLBACK = "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback"
class OpenBaoClientConfigTest(unittest.TestCase):
def test_operator_tunnel_callback_is_required(self) -> None:
self.assertIn(LOOPBACK_CALLBACK, MODULE.OPENBAO_CLIENT["redirectUris"])
def test_missing_operator_tunnel_callback_fails_verification(self) -> None:
client = dict(MODULE.OPENBAO_CLIENT)
client["redirectUris"] = [
uri for uri in MODULE.OPENBAO_CLIENT["redirectUris"] if uri != LOOPBACK_CALLBACK
]
errors = MODULE.client_errors({"clients": [client]}, MODULE.OPENBAO_CLIENT)
self.assertEqual(
errors,
[f"openbao-admin.redirectUris missing: {LOOPBACK_CALLBACK}"],
)
def test_retired_public_callbacks_are_not_declared(self) -> None:
for uri in MODULE.RETIRED_OPENBAO_REDIRECT_URIS:
self.assertNotIn(uri, MODULE.OPENBAO_CLIENT["redirectUris"])
def test_retired_public_callback_fails_verification(self) -> None:
client = dict(MODULE.OPENBAO_CLIENT)
retired = MODULE.RETIRED_OPENBAO_REDIRECT_URIS[0]
client["redirectUris"] = [*MODULE.OPENBAO_CLIENT["redirectUris"], retired]
self.assertEqual(
MODULE.retired_redirect_errors({"clients": [client]}),
[f"openbao-admin.redirectUris retired: {retired}"],
)
def test_upsert_removes_retired_callbacks(self) -> None:
stale = dict(MODULE.OPENBAO_CLIENT)
stale["redirectUris"] = [*MODULE.OPENBAO_CLIENT["redirectUris"], *MODULE.RETIRED_OPENBAO_REDIRECT_URIS]
updated = MODULE.upsert_client({"clients": [stale]}, MODULE.OPENBAO_CLIENT)
self.assertEqual(MODULE.retired_redirect_errors(updated), [])
def test_upsert_preserves_unrelated_clients(self) -> None:
config = {"clients": [{"clientId": "unrelated", "clientType": "public"}]}
updated = MODULE.upsert_client(config, MODULE.OPENBAO_CLIENT)
self.assertEqual(updated["clients"][0]["clientId"], "unrelated")
self.assertIn(LOOPBACK_CALLBACK, updated["clients"][1]["redirectUris"])
if __name__ == "__main__":
unittest.main()

View file

@ -0,0 +1,605 @@
import base64
import copy
import importlib.util
import json
from pathlib import Path
import time
import io
import os
import urllib.error
import signal
import threading
import unittest
from unittest.mock import patch
spec = importlib.util.spec_from_file_location('session', Path(__file__).with_name('openbao-negative-session.py'))
s = importlib.util.module_from_spec(spec)
spec.loader.exec_module(s)
def baseline():
reference = dict(s.registrations()[-1], clientId='secrets-engine-openbao',
secretRef='env:PRODUCTION', tokenLifetime='15m')
return '# preserve this comment\nclients:\n' + s.yaml.safe_dump([reference], sort_keys=False) + 'other: unchanged\n'
def envelope():
return {'proposal_id': s.PROPOSAL, 'target': s.TARGET, 'disposition': 'approved',
'founder_decision_id': 'fixture-decision',
'source_hashes': {str(Path(s.__file__).resolve()): s.digest(Path(s.__file__).read_bytes())},
'owner_acknowledgments': {o: 'fixture-owner-review' for o in
['net-kingdom', 'key-cape', 'railiance-platform']},
'valid_until_epoch': time.time() + 900, 'maximum_session_seconds': 900,
'bao_addr': 'http://openbao.openbao.svc:8200',
'bao_health_identity_sha256': s.digest(b'fixture-health-cluster'),
'role_observation': {'mount': 'keycape-services', 'role': 'secrets-engine-openbao',
'issuer': 'https://kc.coulomb.social', 'algorithm': 'RS256',
'bindings_exact': True, 'policy_exact': True, 'raw_expiration_leeway': 0,
'effective_expiration_leeway': 150, 'observed_at_epoch': time.time()}}
class Tests(unittest.TestCase):
def setUp(self):
s.REQUEST_DEADLINE = None
s.BUSINESS_MONOTONIC_DEADLINE = None
s.CLEANUP_MONOTONIC_DEADLINE = None
s.CLEANING = False
def test_preserve_original_bytes(self):
raw = baseline()
result = s.candidate_config(raw)
start = result.index('clientId: openbao-probe-aud')
self.assertIn('# preserve this comment', result[:start])
parsed = s.yaml.safe_load(result)
self.assertEqual(parsed['clients'][:-6], s.yaml.safe_load(raw)['clients'])
self.assertEqual(parsed['other'], 'unchanged')
def test_isolation_and_sixth_matching_expiry(self):
clients = s.registrations()
self.assertNotIn('audience', clients[0])
for c in clients[1:]:
self.assertEqual(c['audience'], 'secrets-engine-openbao')
for key in ['roles', 'tenant', 'serviceSubject', 'allowedScopes']:
self.assertEqual(clients[-1][key], s.yaml.safe_load(baseline())['clients'][0][key])
def test_preexisting_probe_refused(self):
raw = baseline().replace('other: unchanged', '- clientId: openbao-probe-aud\nother: unchanged')
with self.assertRaises(s.Refused):
s.candidate_config(raw)
def test_duplicate_yaml_refused(self):
with self.assertRaises(s.Refused):
s.candidate_config(baseline() + 'other: shadow\n')
def test_production_drift_refused(self):
with self.assertRaises(s.Refused):
s.candidate_config(baseline().replace('tenant:platform', 'tenant:other'))
def test_env_collision_refused(self):
spec = {'replicas': 1, 'strategy': {'type': 'Recreate'}, 'template': {'spec': {'containers': [
{'name': 'keycape', 'env': [{'name': s.ENVS[0], 'value': 'fixture'}]}]}}}
with self.assertRaises(s.Refused):
s.candidate_spec(spec)
def test_recovery_roundtrip_preserves_every_baseline_byte(self):
raw = baseline()
self.assertEqual(s.recovered_config(s.candidate_config(raw)), raw)
self.assertEqual(s.recovered_config(raw), raw)
def test_recovery_claim_drift_and_partial_registration_refused(self):
raw = s.candidate_config(baseline())
with self.assertRaises(s.Refused):
s.recovered_config(raw.replace('service:openbao-probe', 'service:changed'))
with self.assertRaises(s.Refused):
s.recovered_config(raw.replace('clientId: openbao-probe-aud', 'clientId: different-probe'))
def test_recovery_env_roundtrip(self):
spec = {'replicas': 1, 'strategy': {'type': 'Recreate'}, 'template': {'spec': {'containers': [
{'name': 'keycape', 'env': [{'name': 'ORIGINAL', 'value': 'fixture'}]}]}}}
self.assertEqual(s.recovered_spec(s.candidate_spec(spec)), spec)
def test_approval_is_not_optional(self):
e = envelope()
del e['founder_decision_id']
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_source_change_refused(self):
e = envelope()
e['source_hashes'][str(Path(s.__file__).resolve())] = '0' * 64
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_owner_missing_refused(self):
e = envelope()
del e['owner_acknowledgments']['net-kingdom']
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_leeway_default_and_disabled(self):
e = envelope()
self.assertEqual(s.verify_sources(e), 150)
e['role_observation'].update(raw_expiration_leeway=-1, effective_expiration_leeway=0)
self.assertEqual(s.verify_sources(e), 0)
def test_stale_role_refused(self):
e = envelope()
e['role_observation']['observed_at_epoch'] -= 301
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_wrong_effective_leeway_refused(self):
e = envelope()
e['role_observation']['effective_expiration_leeway'] = 0
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_bao_credentials_in_origin_refused(self):
e = envelope()
e['bao_addr'] = 'https://user:password@example.invalid'
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_future_role_observation_refused(self):
e = envelope()
e['role_observation']['observed_at_epoch'] = time.time() + 60
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_arbitrary_https_origin_refused(self):
e = envelope()
e['bao_addr'] = 'https://attacker.invalid'
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_hostile_proxy_is_disabled(self):
build = s.urllib.request.build_opener
with patch.dict(os.environ, {'HTTPS_PROXY':'http://hostile-proxy.invalid:8000',
'HTTP_PROXY':'http://hostile-proxy.invalid:8000'}), \
patch.object(s.urllib.request, 'build_opener', wraps=build) as observed:
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
handlers = observed.call_args.args
self.assertIsInstance(handlers[0], s.urllib.request.ProxyHandler)
self.assertEqual(handlers[0].proxies, {})
self.assertIsInstance(handlers[1], s.NoRedirect)
self.assertIsNone(handlers[1].redirect_request(None, None, 302, '', {}, 'https://hostile.invalid'))
def test_redirect_never_follows_or_counts_as_refusal(self):
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
response = urllib.error.HTTPError('https://kc.coulomb.social/token', 302,
'redirect', {}, io.BytesIO(b'{}'))
with patch.object(transport.opener, 'open', side_effect=response) as observed:
status, raw = transport.post('https://kc.coulomb.social/token', form={})
self.assertEqual(status, 302)
self.assertEqual(observed.call_count, 1)
def test_oversized_http_body_is_closed_and_refused(self):
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
response = type('Response', (), {'getcode':lambda self: 200,
'read':lambda self, size: b'x' * size, 'close':lambda self: setattr(self, 'closed', True)})()
with patch.object(transport.opener, 'open', return_value=response):
with self.assertRaisesRegex(s.Refused, 'http_response_oversized'):
transport.post('https://kc.coulomb.social/token', form={})
self.assertTrue(response.closed)
def test_expired_window_prevents_protected_kubernetes_and_http(self):
s.REQUEST_DEADLINE = time.time() - 1
with patch.object(s.subprocess, 'run') as backend:
with self.assertRaisesRegex(s.Refused, 'approved_window_expired'):
s.k(['create', '-f', '/dev/stdin'], {})
backend.assert_not_called()
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
with patch.object(transport.opener, 'open') as backend:
with self.assertRaisesRegex(s.Refused, 'approved_window_expired'):
transport.post('https://kc.coulomb.social/token', form={})
backend.assert_not_called()
def test_cleanup_revoke_allowed_after_window_expiry(self):
s.REQUEST_DEADLINE = time.time() - 1
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
response = type('Response', (), {'getcode':lambda self: 204,
'read':lambda self, size: b'', 'close':lambda self: None})()
with patch.object(transport.opener, 'open', return_value=response):
self.assertEqual(transport.post('http://openbao.openbao.svc:8200/v1/auth/token/revoke-self'), (204, b''))
def test_http_read_has_finite_size(self):
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
sizes = []
response = type('Response', (), {'getcode':lambda self: 200,
'read':lambda self, size: sizes.append(size) or b'{}', 'close':lambda self: None})()
with patch.object(transport.opener, 'open', return_value=response):
transport.post('https://kc.coulomb.social/token')
self.assertEqual(sizes, [s.MAX_RESPONSE + 1])
def test_nonfinite_and_boolean_timestamps_refused(self):
for value in [float('inf'), float('-inf'), float('nan'), True, False, 'Infinity', 10 ** 1000]:
for field in ['valid_until_epoch', 'observed_at_epoch']:
with self.subTest(field=field, value_type=type(value).__name__):
e = envelope()
if field == 'valid_until_epoch':
e[field] = value
else:
e['role_observation'][field] = value
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_finite_far_future_and_noninteger_window_refused(self):
e = envelope()
e['valid_until_epoch'] = time.time() + 86400
with self.assertRaises(s.Refused):
s.verify_sources(e)
for value in [True, 900.0, float('inf'), float('nan'), '900']:
e = envelope()
e['maximum_session_seconds'] = value
with self.assertRaises(s.Refused):
s.verify_sources(e)
def test_recovery_also_requires_finite_typed_window(self):
e = envelope()
e['disposition'] = 'recovery-approved'
e['valid_until_epoch'] = float('inf')
with self.assertRaises(s.Refused):
s.verify_sources(e, recovery=True)
def test_nonfinite_json_constants_refused(self):
for constant in ['Infinity', '-Infinity', 'NaN']:
with self.assertRaises(s.Refused):
s.read_json('{"valid_until_epoch":' + constant + '}')
self.assertFalse(s.finite_number(s.read_json('{"valid_until_epoch":1e999}')['valid_until_epoch']))
def test_hard_timer_covers_slow_trickle_body_and_closes_response(self):
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
class Trickle:
def getcode(self):
return 200
def read(self, size):
for _ in range(100):
time.sleep(0.01)
return b'{}'
def close(self):
self.closed = True
response = Trickle()
previous = signal.getsignal(signal.SIGALRM)
started = time.monotonic()
with patch.object(transport.opener, 'open', return_value=response):
with self.assertRaisesRegex(s.Refused, 'request_total_timeout'):
transport.post('https://kc.coulomb.social/token', timeout=0.04)
self.assertLess(time.monotonic() - started, 0.25)
self.assertTrue(response.closed)
self.assertIs(signal.getsignal(signal.SIGALRM), previous)
self.assertEqual(signal.getitimer(signal.ITIMER_REAL), (0.0, 0.0))
def test_hard_timer_spans_headers_and_clips_to_business_window(self):
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
s.REQUEST_DEADLINE = time.time() + 60
s.BUSINESS_MONOTONIC_DEADLINE = time.monotonic() + 0.04
def slow_headers(*args, **kwargs):
time.sleep(1)
raise AssertionError('inactivity timeout would have permitted this')
started = time.monotonic()
with patch.object(transport.opener, 'open', side_effect=slow_headers):
with self.assertRaisesRegex(s.Refused, 'request_total_timeout'):
transport.post('https://kc.coulomb.social/token')
self.assertLess(time.monotonic() - started, 0.25)
def test_late_full_acceptance_is_revoked_then_refused(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
token = 'not-a-real-late-token'
class Late:
def getcode(self):
return 200
def read(self, size):
s.REQUEST_DEADLINE = time.time() - 1
return json.dumps({'auth': {'client_token': token}}).encode()
def close(self):
pass
revoke = type('Response', (), {'getcode':lambda self: 204,
'read':lambda self, size: b'', 'close':lambda self: None})()
with patch.object(transport.opener, 'open', side_effect=[Late(), revoke]) as calls:
with self.assertRaisesRegex(s.Refused, 'approved_window_expired'):
transport.post('http://openbao.openbao.svc:8200/v1/auth/keycape-services/login')
self.assertEqual(calls.call_count, 2)
self.assertEqual(calls.call_args_list[1].args[0].full_url,
'http://openbao.openbao.svc:8200/v1/auth/token/revoke-self')
self.assertTrue(receipt['late_acceptance_revoke_attempted'])
self.assertTrue(receipt['late_acceptance_revoke_succeeded'])
self.assertNotIn(token, json.dumps(receipt))
def test_late_acceptance_failed_revoke_remains_unconfirmed(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
class Late:
def getcode(self):
return 200
def read(self, size):
s.REQUEST_DEADLINE = time.time() - 1
return b'{"auth":{"client_token":"fake-token"}}'
def close(self):
pass
failed = type('Response', (), {'getcode':lambda self: 403,
'read':lambda self, size: b'{}', 'close':lambda self: None})()
with patch.object(transport.opener, 'open', side_effect=[Late(), failed]):
with self.assertRaises(s.Refused):
transport.post('http://openbao.openbao.svc:8200/v1/auth/keycape-services/login')
self.assertTrue(receipt['late_acceptance_revoke_attempted'])
self.assertFalse(receipt['late_acceptance_revoke_succeeded'])
def test_partial_accepted_response_timeout_retains_unknown_outcome(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
response = type('Response', (), {'getcode':lambda self: 200,
'read':lambda self, size: time.sleep(1), 'close':lambda self: None})()
with patch.object(transport.opener, 'open', return_value=response):
with self.assertRaisesRegex(s.Refused, 'request_total_timeout'):
transport.post('http://openbao.openbao.svc:8200/v1/auth/keycape-services/login', timeout=0.04)
self.assertTrue(receipt['openbao_login_outcome_unknown'])
def test_cleanup_has_finite_aggregate_deadline_and_restoration(self):
s.REQUEST_DEADLINE = time.time() - 1
s.begin_cleanup()
first = s.CLEANUP_MONOTONIC_DEADLINE
s.begin_cleanup()
self.assertEqual(s.CLEANUP_MONOTONIC_DEADLINE, first)
s.CLEANUP_MONOTONIC_DEADLINE = time.monotonic() - 1
s.CLEANING = True
with self.assertRaisesRegex(s.Refused, 'cleanup_budget_expired'):
s.check_deadline()
transport = s.SessionTransport('http://openbao.openbao.svc:8200')
with patch.object(transport.opener, 'open') as backend:
with self.assertRaises(s.Refused):
transport.post('http://openbao.openbao.svc:8200/v1/auth/token/revoke-self')
backend.assert_not_called()
def test_expired_cleanup_cannot_issue_native_secret_delete(self):
s.CLEANING = True
s.CLEANUP_MONOTONIC_DEADLINE = time.monotonic() - 1
obj = {'metadata': {'uid':'temporary', 'resourceVersion':'7'}}
with patch.object(s.subprocess, 'run') as native:
with self.assertRaisesRegex(s.Refused, 'cleanup_budget_expired'):
s.delete_temporary(obj)
native.assert_not_called()
def test_secret_delete_total_timeout_clipped_to_cleanup_budget(self):
s.CLEANING = True
s.CLEANUP_MONOTONIC_DEADLINE = time.monotonic() + 0.04
obj = {'metadata': {'uid':'temporary', 'resourceVersion':'7'}}
captured = {}
def slow_delete(args, **kwargs):
captured.update(kwargs)
time.sleep(1)
return type('Result', (), {'returncode':0, 'stdout':'{}'})()
started = time.monotonic()
with patch.object(s.subprocess, 'run', side_effect=slow_delete):
with self.assertRaisesRegex(s.Refused, 'request_total_timeout'):
s.delete_temporary(obj)
self.assertLess(time.monotonic() - started, 0.25)
self.assertLessEqual(captured['timeout'], 0.04)
self.assertEqual(json.loads(captured['input'])['preconditions'], {'uid':'temporary', 'resourceVersion':'7'})
def test_early_read_failure_slow_close_is_bounded_once(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
class Response:
closes = 0
def getcode(self):
return 200
def read(self, size):
raise s.Refused('synthetic_read_failure')
def close(self):
self.closes += 1
time.sleep(0.07)
response = Response()
previous = signal.getsignal(signal.SIGALRM)
started = time.monotonic()
with patch.object(transport.opener, 'open', return_value=response):
with self.assertRaisesRegex(s.Refused, 'http_response_abort_unconfirmed'):
transport.post('http://openbao.openbao.svc:8200/v1/auth/keycape-services/login', timeout=0.01)
self.assertLess(time.monotonic() - started, 0.05)
self.assertEqual(response.closes, 1)
self.assertTrue(receipt['openbao_login_outcome_unknown'])
self.assertTrue(receipt['http_abort_close_attempted'])
self.assertFalse(receipt['http_abort_close_succeeded'])
self.assertTrue(receipt['http_response_abort_unconfirmed'])
self.assertIs(signal.getsignal(signal.SIGALRM), previous)
self.assertEqual(signal.getitimer(signal.ITIMER_REAL), (0.0, 0.0))
def test_total_read_timeout_then_slow_close_uses_only_abort_cap(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
class Response:
closes = 0
def getcode(self):
return 200
def read(self, size):
time.sleep(0.1)
def close(self):
self.closes += 1
time.sleep(0.1)
response = Response()
started = time.monotonic()
with patch.object(transport.opener, 'open', return_value=response):
with self.assertRaisesRegex(s.Refused, 'http_response_abort_unconfirmed'):
transport.post('http://openbao.openbao.svc:8200/v1/auth/keycape-services/login', timeout=0.01)
self.assertLess(time.monotonic() - started, 0.06)
self.assertEqual(response.closes, 1)
self.assertTrue(receipt['openbao_login_outcome_unknown'])
self.assertFalse(receipt['http_abort_close_succeeded'])
def test_oversized_body_slow_abort_remains_unproved_and_bounded(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
response = type('Response', (), {'getcode':lambda self: 200,
'read':lambda self, size: b'x' * size,
'close':lambda self: time.sleep(0.1)})()
started = time.monotonic()
with patch.object(transport.opener, 'open', return_value=response):
with self.assertRaisesRegex(s.Refused, 'http_response_abort_unconfirmed'):
transport.post('http://openbao.openbao.svc:8200/v1/auth/keycape-services/login', timeout=0.01)
self.assertLess(time.monotonic() - started, 0.05)
self.assertTrue(receipt['openbao_login_outcome_unknown'])
self.assertFalse(receipt['http_abort_close_succeeded'])
def test_raising_normal_close_then_slow_abort_is_private_bounded_unknown(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
class Response:
closes = 0
def getcode(self):
return 200
def read(self, size):
return b'{}'
def close(self):
self.closes += 1
if self.closes == 1:
raise RuntimeError('private protected error must not escape')
time.sleep(0.1)
response = Response()
previous = signal.getsignal(signal.SIGALRM)
started = time.monotonic()
with patch.object(transport.opener, 'open', return_value=response):
with self.assertRaisesRegex(s.Refused, '^http_response_abort_unconfirmed$'):
transport.post('http://openbao.openbao.svc:8200/v1/auth/keycape-services/login', timeout=0.01)
self.assertLess(time.monotonic() - started, 0.05)
self.assertEqual(response.closes, 2)
self.assertTrue(receipt['openbao_login_outcome_unknown'])
self.assertFalse(receipt['http_abort_close_succeeded'])
self.assertIs(signal.getsignal(signal.SIGALRM), previous)
self.assertEqual(signal.getitimer(signal.ITIMER_REAL), (0.0, 0.0))
def test_expired_cleanup_never_attempts_exceptional_close_or_resets(self):
receipt = {}
transport = s.SessionTransport('http://openbao.openbao.svc:8200', receipt=receipt)
deadline = time.monotonic() - 1
s.CLEANUP_MONOTONIC_DEADLINE = deadline
response = type('Response', (), {'close':lambda self: self.fail('must not close')})()
with patch.object(response, 'close') as closes:
with self.assertRaisesRegex(s.Refused, 'http_response_abort_unconfirmed'):
transport.abort_response(response, time.monotonic() - 1)
closes.assert_not_called()
self.assertEqual(s.CLEANUP_MONOTONIC_DEADLINE, deadline)
self.assertFalse(receipt['http_abort_close_attempted'])
self.assertTrue(receipt['http_response_abort_unconfirmed'])
def test_thread_or_existing_timer_refused_without_overwrite(self):
results = []
def in_thread():
try:
with s.total_timeout(0.1):
results.append('unreachable')
except s.Refused:
results.append('refused')
thread = threading.Thread(target=in_thread)
thread.start()
thread.join()
self.assertEqual(results, ['refused'])
signal.setitimer(signal.ITIMER_REAL, 1)
try:
with self.assertRaisesRegex(s.Refused, 'existing_alarm_timer_refused'):
with s.total_timeout(0.1):
pass
self.assertGreater(signal.getitimer(signal.ITIMER_REAL)[0], 0.5)
finally:
signal.setitimer(signal.ITIMER_REAL, 0)
def test_cleanup_refuses_concurrent_registry_change(self):
self.lifecycle(drift=True)
def test_deleted_client_contract_is_specific(self):
self.assertTrue(s.removed_client_denied(401, b'{"error":"invalid_profile_usage","feature":"client_id"}'))
for status, body in [(503, b'{}'), (401, b'{"error":"invalid_profile_usage","feature":"Authorization"}'),
(401, b'{"error":"invalid_client"}'), (401, b'not-json')]:
self.assertFalse(s.removed_client_denied(status, body))
def test_cleanup_even_when_probe_fails(self):
self.lifecycle(probe_pass=False)
def test_complete_lifecycle(self):
self.lifecycle()
def lifecycle(self, drift=False, probe_pass=True):
raw = baseline()
dep_spec = {'replicas': 1, 'strategy': {'type': 'Recreate'}, 'template': {'spec': {'containers': [
{'name': 'keycape', 'image': 'fixture@sha256:abc', 'env': []}]}}}
objects = {'keycape-config': {'metadata': {'name': 'keycape-config', 'uid': 'cfg', 'resourceVersion': '1'},
'data': {'config.yaml': base64.b64encode(raw.encode()).decode(),
'key.pem': 'FAKE_KEY_NOT_READ'}},
'keycape': {'metadata': {'name': 'keycape', 'uid': 'dep', 'resourceVersion': '1'},
'spec': dep_spec}}
original = copy.deepcopy(objects)
e = envelope()
e.update(runner_path='fake-runner', config_metadata=s.meta(objects['keycape-config']),
deployment_metadata=s.meta(objects['keycape']), deployment_spec_sha256=s.canonical(dep_spec),
registry_sha256=s.digest(raw.encode()))
e['source_hashes']['fake-runner'] = 'fixture'
receipt = {'cleanup_verified': False}
class Probe:
def exchange(self, *args):
return 'fixture-token'
def http_post(self, *args, **kwargs):
return 401, b'{"error":"invalid_profile_usage","feature":"client_id"}'
def run(self, *args, **kwargs):
if drift:
objects['keycape-config']['data']['unrelated'] = 'concurrent-owner-change'
return {'all_refused': probe_pass}
def kube(args, payload=None):
if args[0] == 'get':
return {'metadata': {'uid': s.CLUSTER}}
if args[0] == 'create':
if '--dry-run=server' in args:
return copy.deepcopy(payload)
created = copy.deepcopy(payload)
created['metadata'].update(uid='temp', resourceVersion='1')
objects[s.TEMP] = created
return created
if args[0] == 'delete':
self.assertEqual(payload['preconditions'], {'uid':'temp', 'resourceVersion':'1'})
del objects[s.TEMP]
return {'kind':'Status', 'status':'Success'}
if '--ignore-not-found' in args:
return objects.get(s.TEMP)
raise AssertionError('unexpected kube call')
def patched(kind, obj, path, value, dry=False):
result = copy.deepcopy(obj)
if path == '/spec':
result['spec'] = copy.deepcopy(value)
else:
result['data']['config.yaml'] = value
if not dry:
objects[obj['metadata']['name']] = result
return result
def delete(*args, **kwargs):
del objects[s.TEMP]
return type('Result', (), {'returncode': 0})()
class Discovery:
def __enter__(self):
import io
return io.StringIO('{"issuer":"https://kc.coulomb.social","token_endpoint":"https://kc.coulomb.social/token"}')
def __exit__(self, *args):
pass
with patch.object(s, 'verify_sources', return_value=150), patch.object(s, 'load_probe', return_value=Probe()), \
patch.object(s, 'SessionTransport') as transport_factory, \
patch.object(s, 'k', side_effect=kube), patch.object(s, 'get', side_effect=lambda kind, name: copy.deepcopy(objects[name])), \
patch.object(s, 'patch', side_effect=patched), patch.object(s, 'ready', return_value=True), \
patch.object(s.subprocess, 'run', side_effect=delete):
transport_factory.return_value.discovery.return_value = {
'issuer':'https://kc.coulomb.social', 'token_endpoint':'https://kc.coulomb.social/token'}
transport_factory.return_value.health_identity.return_value = e['bao_health_identity_sha256']
transport_factory.return_value.post.side_effect = Probe().http_post
if drift or not probe_pass:
with self.assertRaises(s.Refused):
s.execute(e, {'human_token': 'fake', 'coding_agent_token': 'fake'}, receipt)
else:
s.execute(e, {'human_token': 'fake', 'coding_agent_token': 'fake'}, receipt)
if drift:
self.assertFalse(receipt['cleanup_verified'])
self.assertIn(s.TEMP, objects)
self.assertEqual(objects['keycape-config']['data']['unrelated'], 'concurrent-owner-change')
else:
self.assertTrue(receipt['cleanup_verified'])
self.assertNotIn(s.TEMP, objects)
self.assertEqual(objects['keycape-config']['data'], original['keycape-config']['data'])
self.assertEqual(objects['keycape']['spec'], original['keycape']['spec'])
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,198 @@
"""Offline route readiness: public responses and lifecycle mocks only."""
import importlib.util
import json
from pathlib import Path
import unittest
from unittest.mock import patch
import copy
import time
import tempfile
import sys
import signal
import io
spec=importlib.util.spec_from_file_location('fixtures',Path(__file__).with_name('test_openbao_negative_session.py'))
f=importlib.util.module_from_spec(spec);spec.loader.exec_module(f);s=f.s
HEALTH={'initialized':True,'sealed':False,'standby':False,'performance_standby':False,'cluster_id':'synthetic-public-cluster'}
PIN=s.digest(HEALTH['cluster_id'].encode())
class NodeRouteTests(unittest.TestCase):
def setUp(self):
s.CLEANING=False;s.REQUEST_DEADLINE=None
s.BUSINESS_MONOTONIC_DEADLINE=None;s.CLEANUP_MONOTONIC_DEADLINE=None
def test_public_health_get_has_no_authentication_and_only_digest_retained(self):
transport=s.SessionTransport('http://127.0.0.1:18200');receipt={}
response=type('Response',(),{'getcode':lambda self:200,'read':lambda self,size:json.dumps(HEALTH).encode(),'close':lambda self:None})()
with patch.object(transport.opener,'open',return_value=response) as calls:
s.verify_bao_route(transport,{'bao_health_identity_sha256':PIN},receipt)
req=calls.call_args.args[0]
self.assertEqual(req.full_url,'http://127.0.0.1:18200/v1/sys/health')
self.assertEqual(req.method,'GET');self.assertEqual(req.headers,{})
self.assertEqual(receipt,{'bao_public_route_verified':True})
self.assertNotIn(HEALTH['cluster_id'],json.dumps(receipt))
def test_unreachable_redirect_sealed_standby_uninitialized_and_malformed_health_refuse(self):
transport=s.SessionTransport('http://openbao.openbao.svc:8200')
cases=[(None,b''),(302,b'{}'),(503,json.dumps(HEALTH).encode()),(200,b'not-json'),
(200,b'[]'),(200,json.dumps(dict(HEALTH,sealed=True)).encode()),
(200,json.dumps(dict(HEALTH,initialized=False)).encode()),
(200,json.dumps(dict(HEALTH,standby=True)).encode()),
(200,json.dumps(dict(HEALTH,sealed=0)).encode()),
(200,json.dumps(dict(HEALTH,cluster_id='')).encode())]
for status,body in cases:
with self.subTest(status=status,body_length=len(body)),patch.object(transport,'request',return_value=(status,body)):
with self.assertRaises(s.Refused):transport.health_identity()
def test_identity_drift_or_missing_approved_pin_refuse(self):
transport=s.SessionTransport('http://127.0.0.1:18200')
with patch.object(transport,'health_identity',return_value=PIN) as health:
with self.assertRaisesRegex(s.Refused,'bao_public_health_identity_changed'):
s.verify_bao_route(transport,{'bao_health_identity_sha256':'0'*64},{})
health.reset_mock()
with self.assertRaisesRegex(s.Refused,'approved_bao_health_identity_required'):
s.verify_bao_route(transport,{}, {})
health.assert_not_called()
def setup_execute(self):
raw=f.baseline()
spec={'replicas':1,'strategy':{'type':'Recreate'},'template':{'spec':{'containers':[{'name':'keycape','env':[]}]}}}
config={'metadata':{'name':'keycape-config','uid':'cfg','resourceVersion':'1'},'data':{'config.yaml':s.base64.b64encode(raw.encode()).decode()}}
dep={'metadata':{'name':'keycape','uid':'dep','resourceVersion':'1'},'spec':spec}
e=f.envelope();e.update(runner_path='fixture-runner',bao_health_identity_sha256=PIN,
config_metadata=s.meta(config),deployment_metadata=s.meta(dep),registry_sha256=s.digest(raw.encode()),deployment_spec_sha256=s.canonical(spec))
e['source_hashes']['fixture-runner']='fixture'
return e,config,dep
def test_unavailable_initial_node_route_stops_before_any_native_or_secret_access(self):
e,config,dep=self.setup_execute()
with patch.object(s,'verify_sources',return_value=150),patch.object(s,'load_probe'), \
patch.object(s,'SessionTransport') as factory,patch.object(s,'k') as k,patch.object(s,'get') as get:
factory.return_value.discovery.return_value={'issuer':'https://kc.coulomb.social','token_endpoint':'https://kc.coulomb.social/token'}
factory.return_value.health_identity.side_effect=s.Refused('bao_public_health_unavailable')
with self.assertRaisesRegex(s.Refused,'bao_public_health_unavailable'):
s.execute(e,{'human_token':'synthetic','coding_agent_token':'synthetic'}, {})
k.assert_not_called();get.assert_not_called()
def test_route_loss_after_dry_runs_stops_before_temporary_create_or_restarts(self):
e,config,dep=self.setup_execute()
with patch.object(s,'verify_sources',return_value=150),patch.object(s,'load_probe'), \
patch.object(s,'SessionTransport') as factory, \
patch.object(s,'k',return_value={'metadata':{'uid':s.CLUSTER}}) as k, \
patch.object(s,'get',side_effect=[config,dep]),patch.object(s,'patch') as patches,patch.object(s,'ready',return_value=True):
factory.return_value.discovery.return_value={'issuer':'https://kc.coulomb.social','token_endpoint':'https://kc.coulomb.social/token'}
factory.return_value.health_identity.side_effect=[PIN,s.Refused('bao_public_health_unavailable')]
with self.assertRaisesRegex(s.Refused,'bao_public_health_unavailable'):
s.execute(e,{'human_token':'synthetic','coding_agent_token':'synthetic'}, {})
self.assertEqual(factory.return_value.health_identity.call_count,2)
self.assertTrue(all(c.kwargs.get('dry') is True for c in patches.call_args_list))
self.assertTrue(all(c.args[0][0]!='create' or '--dry-run=server' in c.args[0] for c in k.call_args_list))
def test_recovery_route_refusal_precedes_native_reads(self):
e,config,dep=self.setup_execute();e['disposition']='recovery-approved'
with patch.object(s,'verify_sources',return_value=None),patch.object(s,'SessionTransport') as factory, \
patch.object(s,'k') as k,patch.object(s,'get') as get:
factory.return_value.health_identity.side_effect=s.Refused('bao_public_health_unavailable')
with self.assertRaisesRegex(s.Refused,'bao_public_health_unavailable'):
s.recover(e,{})
k.assert_not_called();get.assert_not_called()
def test_public_health_uses_total_elapsed_limit(self):
transport=s.SessionTransport('http://127.0.0.1:18200')
class Slow:
def getcode(self):return 200
def read(self,size):time.sleep(0.5);return json.dumps(HEALTH).encode()
def close(self):pass
started=time.monotonic()
with patch.object(s,'HTTP_TOTAL_SECONDS',0.008),patch.object(transport.opener,'open',return_value=Slow()):
with self.assertRaisesRegex(s.Refused,'request_total_timeout'):transport.health_identity()
self.assertLess(time.monotonic()-started,0.08)
def test_public_route_mode_has_no_native_secret_or_private_input(self):
with tempfile.TemporaryDirectory() as folder:
receipt=Path(folder)/'receipt.json'
argv=['session','route-preflight','--bao-addr','http://127.0.0.1:18200','--receipt',str(receipt)]
with patch.object(sys,'argv',argv),patch.object(s,'SessionTransport') as factory, \
patch.object(s,'k') as k,patch.object(s,'get') as get,patch.object(sys,'stdin') as stdin:
factory.return_value.health_identity.return_value=PIN
self.assertEqual(s.main(),0)
k.assert_not_called();get.assert_not_called();stdin.read.assert_not_called()
data=json.loads(receipt.read_text())
self.assertEqual(data['bao_health_identity_sha256'],PIN)
self.assertEqual(data['probe_location'],'current_process_only')
self.assertFalse(data['native_accepted'])
def main_fixture(self,folder,mode):
envelope=f.envelope()
envelope['bao_health_identity_sha256']=PIN
if mode=='recover':envelope['disposition']='recovery-approved'
path=Path(folder)/'approved.json';path.write_text(json.dumps(envelope));path.chmod(0o600)
receipt=Path(folder)/'receipt.json'
argv=['session',mode,'--envelope',str(path),'--receipt',str(receipt)]
return envelope,receipt,argv
def test_actual_main_health_failure_never_reads_private_stdin_execute_or_recover(self):
previous=signal.getsignal(signal.SIGTERM)
try:
for mode in ['execute','recover']:
with self.subTest(mode=mode),tempfile.TemporaryDirectory() as folder:
e,receipt,argv=self.main_fixture(folder,mode)
with patch.object(sys,'argv',argv),patch.object(s,'SessionTransport') as factory, \
patch.object(sys,'stdin') as stdin,patch.object(s,'k') as k,patch.object(s,'get') as get:
factory.return_value.health_identity.side_effect=s.Refused('bao_public_health_unavailable')
self.assertEqual(s.main(),1)
stdin.read.assert_not_called();stdin.buffer.read.assert_not_called();k.assert_not_called();get.assert_not_called()
data=json.loads(receipt.read_text())
self.assertEqual(data['reason'],'bao_public_health_unavailable')
self.assertFalse(data['cleanup_verified'])
s.CLEANING=False;s.CLEANUP_MONOTONIC_DEADLINE=None
finally:signal.signal(signal.SIGTERM,previous)
def test_actual_main_missing_approved_health_pin_never_reads_stdin(self):
previous=signal.getsignal(signal.SIGTERM)
try:
with tempfile.TemporaryDirectory() as folder:
e,receipt,argv=self.main_fixture(folder,'execute')
del e['bao_health_identity_sha256']
Path(argv[3]).write_text(json.dumps(e))
with patch.object(sys,'argv',argv),patch.object(sys,'stdin') as stdin, \
patch.object(s,'SessionTransport') as factory,patch.object(s,'k') as k:
self.assertEqual(s.main(),1)
stdin.read.assert_not_called();stdin.buffer.read.assert_not_called();factory.return_value.health_identity.assert_not_called();k.assert_not_called()
self.assertEqual(json.loads(receipt.read_text())['reason'],'approved_bao_health_identity_required')
finally:signal.signal(signal.SIGTERM,previous)
def test_actual_main_health_succeeds_before_stdin_and_preserves_started_window(self):
previous=signal.getsignal(signal.SIGTERM);events=[];started={}
try:
with tempfile.TemporaryDirectory() as folder:
e,receipt,argv=self.main_fixture(folder,'execute')
def health():
events.append('health');started['deadline']=s.BUSINESS_MONOTONIC_DEADLINE;return PIN
payloads=iter([b'{"human_token":"synthetic","coding_agent_token":"synthetic"}',b''])
def private_read(size):
if 'stdin' not in events:events.append('stdin')
self.assertGreater(size,0);return next(payloads)
def execute(*args,**kwargs):
events.append('execute');self.assertTrue(kwargs['window_started'])
self.assertEqual(s.BUSINESS_MONOTONIC_DEADLINE,started['deadline'])
with patch.object(sys,'argv',argv),patch.object(s,'SessionTransport') as factory, \
patch.object(sys,'stdin') as stdin,patch.object(s,'emit_private_ready'),patch.object(s,'execute',side_effect=execute), \
patch.object(s,'k') as k:
factory.return_value.health_identity.side_effect=health
stdin.buffer.read.side_effect=private_read
self.assertEqual(s.main(),0);k.assert_not_called()
self.assertEqual(events,['health','stdin','execute'])
finally:signal.signal(signal.SIGTERM,previous)
def test_execute_does_not_renew_expired_preinput_monotonic_window(self):
e=f.envelope();s.start_execution_window(e)
original=s.BUSINESS_MONOTONIC_DEADLINE
with patch.object(s.time,'monotonic',return_value=original+0.01),patch.object(s,'load_probe') as load, \
patch.object(s,'k') as k:
with self.assertRaisesRegex(s.Refused,'session_business_budget_expired'):
s.execute(e,{'human_token':'synthetic','coding_agent_token':'synthetic'}, {},window_started=True)
load.assert_not_called();k.assert_not_called()
self.assertEqual(s.BUSINESS_MONOTONIC_DEADLINE,original)
if __name__=='__main__':unittest.main(verbosity=2)

View file

@ -0,0 +1,111 @@
"""Actual main bounded private receive; synthetic local binary pipes only."""
import importlib.util
from pathlib import Path
import io
import json
import os
import signal
import sys
import tempfile
import time
import unittest
from unittest.mock import patch
spec=importlib.util.spec_from_file_location('routes',Path(__file__).with_name('test_openbao_node_route.py'))
r=importlib.util.module_from_spec(spec);spec.loader.exec_module(r);s=r.s;f=r.f
PAYLOAD=b'{"human_token":"synthetic-human","coding_agent_token":"synthetic-agent"}'
class PrivateInputTests(unittest.TestCase):
def setUp(self):
s.CLEANING=False;s.REQUEST_DEADLINE=None
s.BUSINESS_MONOTONIC_DEADLINE=None;s.CLEANUP_MONOTONIC_DEADLINE=None
def main_with(self,stream,*,lifetime=0.05,health=None):
previous=signal.getsignal(signal.SIGTERM)
try:
with tempfile.TemporaryDirectory() as folder:
envelope=f.envelope();envelope['valid_until_epoch']=time.time()+lifetime
envelope['bao_health_identity_sha256']=r.PIN
path=Path(folder)/'approved.json';path.write_text(json.dumps(envelope));path.chmod(0o600)
receipt=Path(folder)/'receipt.json'
argv=['session','execute','--envelope',str(path),'--receipt',str(receipt)]
with patch.object(sys,'argv',argv),patch.object(sys,'stdin',stream), \
patch.object(s,'SessionTransport') as factory,patch.object(s,'emit_private_ready'),patch.object(s,'execute') as execute, \
patch.object(s,'k') as k,patch.object(s,'get') as get:
factory.return_value.health_identity.side_effect=health
factory.return_value.health_identity.return_value=r.PIN
rc=s.main()
k.assert_not_called();get.assert_not_called()
return rc,json.loads(receipt.read_text()),execute.call_args,execute.call_count
finally:signal.signal(signal.SIGTERM,previous)
def test_actual_main_real_pipe_stall_has_original_window_total_bound(self):
read_fd,write_fd=os.pipe()
binary=os.fdopen(read_fd,'rb',buffering=0)
stream=type('Input',(),{'buffer':binary})()
started=time.monotonic();previous=signal.getsignal(signal.SIGALRM)
try:
rc,receipt,args,count=self.main_with(stream,lifetime=0.02)
finally:os.close(write_fd);binary.close()
self.assertEqual(rc,1);self.assertEqual(count,0)
self.assertEqual(receipt['reason'],'request_total_timeout')
self.assertLess(time.monotonic()-started,0.15)
self.assertEqual(signal.getitimer(signal.ITIMER_REAL),(0.0,0.0))
self.assertEqual(signal.getsignal(signal.SIGALRM),previous)
def test_actual_main_complete_json_without_eof_still_refuses(self):
read_fd,write_fd=os.pipe();os.write(write_fd,PAYLOAD)
binary=os.fdopen(read_fd,'rb',buffering=0);stream=type('Input',(),{'buffer':binary})()
try:rc,receipt,args,count=self.main_with(stream,lifetime=0.02)
finally:os.close(write_fd);binary.close()
self.assertEqual(rc,1);self.assertEqual(count,0)
self.assertEqual(receipt['reason'],'request_total_timeout')
def test_actual_main_oversized_receive_refuses_without_execution(self):
stream=type('Input',(),{'buffer':io.BytesIO(b'x'*(s.PRIVATE_INPUT_MAX_BYTES+1))})()
rc,receipt,args,count=self.main_with(stream)
self.assertEqual(rc,1);self.assertEqual(count,0)
self.assertEqual(receipt['reason'],'private_input_oversized')
def test_actual_main_empty_partial_invalid_or_excessive_token_refuses(self):
inputs=[(b'','private_input_empty'),(b'{"human_token":','private_input_invalid'),
(b'not-json','private_input_invalid'),(b'{"human_token":true,"coding_agent_token":"a"}','private_input_invalid'),
(json.dumps({'human_token':'x'*(s.PRIVATE_TOKEN_MAX_BYTES+1),'coding_agent_token':'a'}).encode(),'private_input_invalid')]
for raw,reason in inputs:
with self.subTest(reason=reason,length=len(raw)):
stream=type('Input',(),{'buffer':io.BytesIO(raw)})()
rc,receipt,args,count=self.main_with(stream)
self.assertEqual(rc,1);self.assertEqual(count,0);self.assertEqual(receipt['reason'],reason)
def test_actual_main_expiry_after_health_never_starts_private_receive(self):
stream=type('Input',(),{'buffer':io.BytesIO(PAYLOAD)})()
def late_health():
s.REQUEST_DEADLINE=time.time()-1
return r.PIN
with patch.object(stream.buffer,'read',wraps=stream.buffer.read) as read:
rc,receipt,args,count=self.main_with(stream,health=late_health)
self.assertEqual(rc,1);self.assertEqual(count,0)
self.assertEqual(receipt['reason'],'approved_window_expired');read.assert_not_called()
def test_actual_main_late_eof_never_decodes_or_executes(self):
class LateInput:
reads=0
def read(self,size):
self.reads+=1
if self.reads==1:return PAYLOAD
s.BUSINESS_MONOTONIC_DEADLINE=time.monotonic()-1
return b''
stream=type('Input',(),{'buffer':LateInput()})()
rc,receipt,args,count=self.main_with(stream)
self.assertEqual(rc,1);self.assertEqual(count,0)
self.assertEqual(receipt['reason'],'session_business_budget_expired')
def test_actual_main_complete_bounded_json_plus_eof_reaches_execution_once(self):
stream=type('Input',(),{'buffer':io.BytesIO(PAYLOAD)})()
rc,receipt,args,count=self.main_with(stream)
self.assertEqual(rc,0);self.assertEqual(count,1)
self.assertEqual(args.args[1],json.loads(PAYLOAD))
self.assertTrue(args.kwargs['window_started'])
self.assertNotIn('synthetic-human',json.dumps(receipt))
if __name__=='__main__':unittest.main(verbosity=2)

View file

@ -0,0 +1,165 @@
"""Synthetic ready FD/control transport tests; no SSH/native/credentials."""
import importlib.util
from pathlib import Path
import fcntl
import io
import json
import os
import signal
import sys
import tempfile
import time
import unittest
from unittest.mock import patch
spec=importlib.util.spec_from_file_location('routes',Path(__file__).with_name('test_openbao_node_route.py'))
r=importlib.util.module_from_spec(spec);spec.loader.exec_module(r);s=r.s;f=r.f
spec=importlib.util.spec_from_file_location('wrapper',Path(__file__).with_name('openbao-negative-node-wrapper.py'))
w=importlib.util.module_from_spec(spec);spec.loader.exec_module(w)
NONCE='a'*64
class ReadyTests(unittest.TestCase):
def setUp(self):
s.CLEANING=False;s.REQUEST_DEADLINE=None;s.BUSINESS_MONOTONIC_DEADLINE=None;s.CLEANUP_MONOTONIC_DEADLINE=None
def marker(self,envelope=None):
e=envelope or f.envelope()
return {'schema':s.READY_SCHEMA,'session_nonce':NONCE,'proposal_id':s.PROPOSAL,'target':s.TARGET,
'executor_sha256':s.digest(Path(s.__file__).read_bytes()),'envelope_sha256':'b'*64,
'bao_health_identity_sha256':e['bao_health_identity_sha256'],'valid_until_epoch':e['valid_until_epoch'],
'receive_budget_seconds':15}
def test_emit_exact_ready_marker_to_distinct_local_pipe_and_close_endpoint(self):
e=f.envelope();s.start_execution_window(e);read_fd,write_fd=os.pipe();receipt={}
s.emit_private_ready(write_fd,NONCE,'b'*64,'b'*64,e,receipt)
raw=os.read(read_fd,4097);self.assertEqual(os.read(read_fd,1),b'');os.close(read_fd)
obj=json.loads(raw)
self.assertEqual(set(obj),set(self.marker(e)))
self.assertEqual(obj['session_nonce'],NONCE)
self.assertEqual(obj['executor_sha256'],s.digest(Path(s.__file__).read_bytes()))
self.assertTrue(receipt['private_ready_emitted'])
self.assertNotIn('human_token',obj);self.assertNotIn('coding_agent_token',obj)
def test_stdout_alias_pipe_fd_above_two_refused_without_marker(self):
e=f.envelope();s.start_execution_window(e);read_fd,write_fd=os.pipe();saved=os.dup(1);alias=None
try:
os.dup2(write_fd,1);alias=os.dup(1)
with self.assertRaisesRegex(s.Refused,'private_ready_stdio_alias_refused'):
s.emit_private_ready(alias,NONCE,'b'*64,'b'*64,e,{})
finally:
os.dup2(saved,1);os.close(saved);os.close(write_fd)
if alias is not None:os.close(alias)
self.assertEqual(os.read(read_fd,4096),b'');os.close(read_fd)
def test_nonce_or_envelope_binding_failure_emits_no_ready(self):
e=f.envelope();s.start_execution_window(e)
for nonce,pin in [('wrong','b'*64),(NONCE,'c'*64)]:
read_fd,write_fd=os.pipe()
try:
with self.assertRaises(s.Refused):s.emit_private_ready(write_fd,nonce,pin,'b'*64,e,{})
finally:
try:os.close(write_fd)
except OSError:pass
self.assertEqual(os.read(read_fd,4096),b'');os.close(read_fd)
def test_actual_full_ack_pipe_write_times_out_inside_original_window(self):
e=f.envelope();s.start_execution_window(e)
s.BUSINESS_MONOTONIC_DEADLINE=time.monotonic()+0.015
read_fd,write_fd=os.pipe();fcntl.fcntl(write_fd,fcntl.F_SETPIPE_SZ,4096)
flags=fcntl.fcntl(write_fd,fcntl.F_GETFL);fcntl.fcntl(write_fd,fcntl.F_SETFL,flags|os.O_NONBLOCK)
try:
while True:os.write(write_fd,b'x'*4096)
except BlockingIOError:pass
fcntl.fcntl(write_fd,fcntl.F_SETFL,flags)
started=time.monotonic();handler=signal.getsignal(signal.SIGALRM)
with self.assertRaisesRegex(s.Refused,'request_total_timeout'):
s.emit_private_ready(write_fd,NONCE,'b'*64,'b'*64,e,{})
self.assertLess(time.monotonic()-started,0.15);os.close(read_fd)
self.assertEqual(signal.getsignal(signal.SIGALRM),handler)
self.assertEqual(signal.getitimer(signal.ITIMER_REAL),(0.0,0.0))
def test_actual_main_health_ready_then_private_read_exact_order(self):
previous=signal.getsignal(signal.SIGTERM);read_fd,write_fd=os.pipe();events=[];received={}
try:
with tempfile.TemporaryDirectory() as folder:
e=f.envelope();path=Path(folder)/'approved.json';path.write_text(json.dumps(e));path.chmod(0o600)
pin=s.digest(path.read_bytes());receipt=Path(folder)/'receipt.json'
argv=['session','execute','--envelope',str(path),'--receipt',str(receipt),'--ready-fd',str(write_fd),
'--session-nonce',NONCE,'--ready-envelope-sha256',pin]
class Input:
first=True
def read(self,size):
if not self.first:return b''
self.first=False
received.update(json.loads(os.read(read_fd,4096)))
self_test.assertEqual(received['envelope_sha256'],pin)
events.append('private-read')
return b'{"human_token":"synthetic","coding_agent_token":"synthetic"}'
self_test=self;stream=type('Stream',(),{'buffer':Input()})()
def health():events.append('health');return e['bao_health_identity_sha256']
def execute(*args,**kwargs):events.append('execute');self.assertTrue(kwargs['window_started'])
with patch.object(sys,'argv',argv),patch.object(sys,'stdin',stream), \
patch.object(s,'SessionTransport') as factory,patch.object(s,'execute',side_effect=execute),patch.object(s,'k') as k:
factory.return_value.health_identity.side_effect=health
self.assertEqual(s.main(),0);k.assert_not_called()
self.assertEqual(events,['health','private-read','execute'])
self.assertEqual(received['session_nonce'],NONCE)
finally:
signal.signal(signal.SIGTERM,previous);os.close(read_fd)
try:os.close(write_fd)
except OSError:pass
def test_actual_main_missing_ack_fd_never_reads_private_input(self):
previous=signal.getsignal(signal.SIGTERM)
try:
with tempfile.TemporaryDirectory() as folder:
e=f.envelope();path=Path(folder)/'approved.json';path.write_text(json.dumps(e));path.chmod(0o600)
receipt=Path(folder)/'receipt.json';argv=['session','execute','--envelope',str(path),'--receipt',str(receipt)]
with patch.object(sys,'argv',argv),patch.object(sys,'stdin') as stdin,patch.object(s,'SessionTransport') as factory,patch.object(s,'k') as k:
factory.return_value.health_identity.return_value=e['bao_health_identity_sha256']
self.assertEqual(s.main(),1);stdin.buffer.read.assert_not_called();k.assert_not_called()
self.assertEqual(json.loads(receipt.read_text())['reason'],'private_ready_pipe_required')
finally:signal.signal(signal.SIGTERM,previous)
def test_wrapper_marker_pin_nonce_fields_and_window_failures_refuse(self):
e=f.envelope();marker=self.marker(e)
for key,value in [('session_nonce','c'*64),('executor_sha256','c'*64),('envelope_sha256','c'*64),
('target','other'),('proposal_id','other'),('bao_health_identity_sha256','c'*64),
('valid_until_epoch',True),('receive_budget_seconds',True),('receive_budget_seconds',float('inf'))]:
bad=dict(marker,**{key:value})
with self.subTest(key=key),self.assertRaises(w.Refused):
w.validate_marker(json.dumps(bad).encode()+b'\n',nonce=NONCE,executor_pin=marker['executor_sha256'],envelope_pin='b'*64,envelope=e)
with self.assertRaises(w.Refused):
w.validate_marker(json.dumps(dict(marker,extra=True)).encode()+b'\n',nonce=NONCE,executor_pin=marker['executor_sha256'],envelope_pin='b'*64,envelope=e)
def test_wrapper_ready_pipe_no_eof_is_bounded(self):
read_fd,write_fd=os.pipe();os.write(write_fd,b'{"incomplete":true}')
started=time.monotonic()
try:
with self.assertRaisesRegex(w.Refused,'wrapper_timeout'):
with w.deadline(0.015):w.read_ready(read_fd)
finally:os.close(write_fd);os.close(read_fd)
self.assertLess(time.monotonic()-started,0.15)
def test_wrapper_pinned_node_child_launch_is_local_pipe_not_ssh_fd_forward(self):
with tempfile.TemporaryDirectory() as folder:
child=Path(folder)/'child.py';child.write_text('# synthetic child, never executed\n')
envelope=f.envelope();envelope['source_hashes']={str(child):w.hash_bytes(child.read_bytes()),str(Path(w.__file__).resolve()):w.hash_bytes(Path(w.__file__).read_bytes())}
env=Path(folder)/'approved.json';env.write_text(json.dumps(envelope));env.chmod(0o600)
args=type('Args',(),{'child':str(child),'child_sha256':w.hash_bytes(child.read_bytes()),'envelope':str(env),'envelope_sha256':w.hash_bytes(env.read_bytes()),'receipt':str(Path(folder)/'receipt.json'),'session_nonce':NONCE})()
class Process:
stdout=io.BytesIO();stderr=io.BytesIO()
def wait(self,timeout):return 0
def poll(self):return 0
def launch(command,**kwargs):
self.assertEqual(command[0],sys.executable)
self.assertEqual(command[1],str(child));self.assertNotIn('ssh',command)
self.assertEqual(kwargs['stdin'],sys.stdin.buffer)
fd=int(command[command.index('--ready-fd')+1]);self.assertEqual(kwargs['pass_fds'],(fd,))
marker=self.marker(envelope);marker.update(executor_sha256=args.child_sha256,envelope_sha256=args.envelope_sha256)
os.write(fd,json.dumps(marker).encode()+b'\n')
return Process()
with patch.object(w.subprocess,'Popen',side_effect=launch),patch.object(w,'forward_ready') as forward:
self.assertEqual(w.run(args),0);self.assertEqual(forward.call_count,1)
self.assertNotIn('human_token',json.loads(forward.call_args.args[0]))
if __name__=='__main__':unittest.main(verbosity=2)

View file

@ -0,0 +1,156 @@
import base64
import contextlib
import importlib.util
import io
import json
from pathlib import Path
import unittest
spec = importlib.util.spec_from_file_location(
'openbao_signin_diagnosis_rollout', Path(__file__).with_name('openbao-signin-diagnosis-rollout.py'))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
UID = '00000000-0000-4000-8000-000000000001'
CLUSTER = {'metadata': {'uid': module.lane.CLUSTER_UID}}
OPENBAO = (' - clientId: openbao-admin\n'
' displayName: Railiance OpenBao Admin\n'
' redirectUris:\n'
' - http://localhost:8250/oidc/callback\n'
' clientType: public\n')
OTHER = (' - clientId: user-engine-portal\n'
' clientType: public\n')
def fixture(clients=OTHER + OPENBAO, tail='tokenLifetime: 15m\n'):
# Synthetic marker values only; no live Secret is loaded by this suite.
raw = ('# preserve this comment\nissuer: https://kc.coulomb.social\n'
'authelia:\n issuer: https://auth.coulomb.social\n clientSecret: synthetic\n'
'clients:\n' + clients + tail)
secret = {'metadata': {'uid': UID, 'resourceVersion': '7'},
'data': {'config.yaml': base64.b64encode(raw.encode()).decode(),
'signing-key.pem': base64.b64encode(b'synthetic').decode()}}
return secret, raw
def decode(encoded):
return base64.b64decode(encoded).decode()
class EditTests(unittest.TestCase):
def test_enable_adds_exactly_one_line_after_client_id(self):
secret, raw = fixture()
encoded, changed = module.enable(secret)
self.assertTrue(changed)
expected = raw.replace(' - clientId: openbao-admin\n',
' - clientId: openbao-admin\n signInDiagnosis: true\n')
self.assertEqual(decode(encoded), expected)
def test_enable_is_noop_when_already_true(self):
secret, _ = fixture(OPENBAO.replace('clientType: public', 'clientType: public\n signInDiagnosis: true'))
encoded, changed = module.enable(secret)
self.assertFalse(changed)
self.assertEqual(encoded, secret['data']['config.yaml'])
def test_other_flag_value_fails_closed(self):
secret, _ = fixture(OPENBAO + ' signInDiagnosis: false\n')
for operation in (module.enable, module.disable):
with self.assertRaises(module.Refused):
operation(secret)
def test_missing_or_duplicate_client_fails_closed(self):
for clients in (OTHER, OPENBAO + OPENBAO):
with self.assertRaises(module.Refused):
module.enable(fixture(clients)[0])
def test_unverified_upstream_issuer_refused(self):
secret, raw = fixture()
raw = raw.replace('https://auth.coulomb.social', 'https://evil.test')
secret['data']['config.yaml'] = base64.b64encode(raw.encode()).decode()
with self.assertRaises(module.Refused):
module.enable(secret)
def test_rollback_restores_exact_bytes(self):
secret, raw = fixture()
enabled, _ = module.enable(secret)
secret['data']['config.yaml'] = enabled
restored, changed = module.disable(secret)
self.assertTrue(changed)
self.assertEqual(decode(restored), raw)
def test_rollback_absent_is_noop(self):
secret, _ = fixture()
encoded, changed = module.disable(secret)
self.assertFalse(changed)
self.assertEqual(encoded, secret['data']['config.yaml'])
class RunTests(unittest.TestCase):
"""Drive run() against a fake kubectl; nothing reaches a cluster."""
def setUp(self):
self.secret, _ = fixture()
self.calls = []
def fake(self, arguments, payload=None):
self.calls.append((arguments, payload))
if arguments[:2] == ['get', 'namespace']:
return CLUSTER
if 'patch' in arguments:
data = dict(self.secret['data'], **{'config.yaml': payload[-1]['value']})
if '--dry-run=server' not in arguments:
self.secret = {'metadata': {'uid': UID, 'resourceVersion': '8'}, 'data': data}
return {'data': data}
return self.secret
def main(self, *argv):
out = io.StringIO()
with contextlib.redirect_stdout(out):
code = module.main(list(argv), kubectl=self.fake)
return code, out.getvalue()
def patches(self):
return [c for c in self.calls if 'patch' in c[0]]
def test_default_is_inspect_and_never_patches(self):
code, out = self.main()
self.assertEqual(code, 0)
receipt = json.loads(out)
self.assertTrue(receipt['change_needed'])
self.assertEqual(receipt['before'], {'uid': UID, 'resource_version': '7'})
self.assertEqual(self.patches(), [])
self.assertNotIn('synthetic', out)
def test_dry_run_requires_observed_revision_and_is_server_side(self):
code, out = self.main('dry-run')
self.assertEqual((code, json.loads(out)['reason']), (1, 'observed_revision_changed'))
code, out = self.main('dry-run', '--expected-uid', UID, '--expected-resource-version', '7')
self.assertEqual(code, 0)
self.assertIn('--dry-run=server', self.patches()[-1][0])
self.assertNotIn('synthetic', out)
def test_apply_requires_attended_and_reports_restart(self):
code, out = self.main('apply', '--expected-uid', UID, '--expected-resource-version', '7')
self.assertEqual((code, json.loads(out)['reason']), (1, 'attended_operator_required'))
self.assertEqual(self.patches(), [])
code, out = self.main('apply', '--attended', '--expected-uid', UID, '--expected-resource-version', '7')
receipt = json.loads(out)
self.assertEqual(code, 0)
self.assertTrue(receipt['changed'] and receipt['restart_required'])
self.assertEqual(receipt['after']['resource_version'], '8')
self.assertEqual(self.patches()[-1][1][:2], [
{'op': 'test', 'path': '/metadata/uid', 'value': UID},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': '7'}])
self.assertNotIn('synthetic', out)
def test_failures_never_print_secret_material(self):
def broken(arguments, payload=None):
raise RuntimeError('synthetic value leaked in error text')
out = io.StringIO()
with contextlib.redirect_stdout(out):
self.assertEqual(module.main([], kubectl=broken), 1)
self.assertNotIn('synthetic', out.getvalue())
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,228 @@
import base64
import copy
import hashlib
import importlib.util
import json
from pathlib import Path
import subprocess
import tempfile
import unittest
from unittest.mock import patch
import jsonpatch
ROOT = Path(__file__).parent
def load(name, filename):
spec = importlib.util.spec_from_file_location(name, ROOT / filename)
module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
return module
a = load('activation', 'playwright-activation.py')
f = load('registration_fixture', 'test_playwright_registration.py')
REVISION = '5dd74dfbdf3fd38adb1bfc1bdc1326181435f0e9'
IDENTITIES = [f.IDENTITY, dict(f.IDENTITY, agent_id='second', service_subject='service:second')]
def fixtures():
secret, raw = f.fixture()
secret.update(kind='Secret', apiVersion='v1')
secret['metadata'].update(name='keycape-config', namespace='sso', uid='00000000-0000-4000-8000-000000000001')
deployment = {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {
'name': 'keycape', 'namespace': 'sso', 'uid': '00000000-0000-4000-8000-000000000002',
'resourceVersion': '12', 'generation': 5}, 'spec': {'replicas': 1, 'strategy': {'type': 'Recreate'},
'template': {'metadata': {'labels': {'app': 'keycape'}}, 'spec': {
'containers': [{'name': 'keycape', 'image': 'forgejo.coulomb.social/coulomb/key-cape@sha256:' + 'a' * 64,
'env': [{'name': 'KEYCAPE_CONFIG', 'value': '/etc/keycape/config.yaml'}],
'volumeMounts': [{'name': 'registry', 'mountPath': '/etc/keycape', 'readOnly': True}]}],
'volumes': [{'name': 'registry', 'secret': {'secretName': 'keycape-config'}}]}}}}
controller(deployment)
return secret, deployment
def controller(deployment):
# Simulates observed controller completion, separate from patch admission.
replicas = deployment['spec']['replicas']
deployment['status'] = {'observedGeneration': deployment['metadata']['generation'],
**{k: replicas for k in ['replicas', 'readyReplicas', 'availableReplicas', 'updatedReplicas']}}
class World:
def __init__(self, secret, deployment):
self.secret, self.deployment = copy.deepcopy(secret), copy.deepcopy(deployment)
self.pods = {'kind': 'PodList', 'metadata': {}, 'items': []}
self.history = []
def step(self, proposal, phase, observe=True):
operation = a.propose_step(proposal, a.digest(proposal), phase, self.secret, self.deployment, self.pods)
if operation is None: return None
attr = 'secret' if operation['kind'] == 'Secret' else 'deployment'
# Independent RFC 6902 implementation verifies actual generated CAS tests.
updated = jsonpatch.apply_patch(getattr(self, attr), operation['patch'], in_place=False)
updated['metadata']['resourceVersion'] = str(int(updated['metadata']['resourceVersion']) + 1)
if attr == 'deployment': updated['metadata']['generation'] += 1
self.history.append(phase)
setattr(self, attr, updated)
receipt = a.verify_response(operation, updated)
if attr == 'deployment' and observe: controller(updated)
return receipt
class ActivationTests(unittest.TestCase):
def setUp(self):
self.secret, self.deployment = fixtures()
self.proposal = a.build(self.secret, self.deployment, IDENTITIES, REVISION)
def test_exact_sequence_keeps_original_source_and_returns_safe_receipts(self):
world = World(self.secret, self.deployment)
original = copy.deepcopy((self.secret, self.deployment))
for phase in ['stop', 'registry', 'bindings', 'resume']:
receipt = world.step(self.proposal, phase)
self.assertTrue(receipt['readback_matches'])
self.assertNotIn('synthetic-private-marker', json.dumps(receipt))
self.assertEqual(world.deployment['spec']['replicas'], 1)
self.assertEqual(world.secret['data'], self.proposal['after_data'])
self.assertEqual((self.secret, self.deployment), original)
self.assertNotIn('synthetic-private-marker', json.dumps(a.summary(self.proposal)))
def test_rollback_preserves_preexisting_public_and_agent_registrations(self):
encoded, _ = a.registration.prepare_registry(self.secret, [IDENTITIES[0]])
self.secret['data']['config.yaml'] = encoded
binding = a.registration.registry.env_binding(a.registration.agent_client(IDENTITIES[0]))
a.container(self.deployment['spec'])['env'].append(binding)
proposal = a.build(self.secret, self.deployment, IDENTITIES, REVISION)
self.assertEqual(proposal['created_client_ids'], ['playwright-agent-second'])
world = World(self.secret, self.deployment)
for phase in ['stop', 'registry', 'bindings', 'resume', 'rollback-stop',
'rollback-registry', 'rollback-bindings', 'rollback-resume']:
world.step(proposal, phase)
self.assertEqual(world.secret['data'], self.secret['data'])
self.assertEqual(world.deployment['spec'], self.deployment['spec'])
def test_each_partial_state_recovers_without_removing_original_entries(self):
phases = ['stop', 'registry', 'bindings', 'resume']
for count in range(5):
world = World(self.secret, self.deployment)
for phase in phases[:count]: world.step(self.proposal, phase)
for phase in ['rollback-stop', 'rollback-registry', 'rollback-bindings', 'rollback-resume']:
world.step(self.proposal, phase)
self.assertEqual(world.secret['data'], self.secret['data'])
self.assertEqual(world.deployment['spec'], self.deployment['spec'])
def test_identical_complete_registration_is_noop_without_outage(self):
world = World(self.secret, self.deployment)
for phase in ['stop', 'registry', 'bindings', 'resume']: world.step(self.proposal, phase)
proposal = a.build(world.secret, world.deployment, IDENTITIES, REVISION)
self.assertFalse(a.summary(proposal)['change_needed'])
before = len(world.history)
for phase in ['stop', 'registry', 'bindings', 'resume']: self.assertIsNone(world.step(proposal, phase))
self.assertEqual(len(world.history), before)
def test_rollback_before_any_execution_never_stops_the_issuer(self):
world = World(self.secret, self.deployment)
for phase in ['rollback-stop', 'rollback-registry', 'rollback-bindings', 'rollback-resume']:
self.assertIsNone(world.step(self.proposal, phase))
self.assertEqual(world.history, [])
def test_reordered_phases_and_unobserved_controller_are_refused(self):
world = World(self.secret, self.deployment)
for phase in ['registry', 'bindings', 'resume']:
with self.subTest(phase=phase), self.assertRaises(a.Refused): world.step(self.proposal, phase)
world.step(self.proposal, 'stop', observe=False)
with self.assertRaises(a.Refused): world.step(self.proposal, 'registry')
controller(world.deployment)
with self.assertRaises(a.Refused): world.step(self.proposal, 'resume')
def test_terminating_direct_projected_and_env_consumers_prevent_registry_write(self):
specs = [{'volumes': [{'secret': {'secretName': 'keycape-config'}}]},
{'volumes': [{'projected': {'sources': [{'secret': {'name': 'keycape-config'}}]}}]},
{'containers': [{'envFrom': [{'secretRef': {'name': 'keycape-config'}}]}]},
{'initContainers': [{'env': [{'valueFrom': {'secretKeyRef': {'name': 'keycape-config'}}}]}]}]
for spec in specs:
world = World(self.secret, self.deployment); world.step(self.proposal, 'stop')
world.pods['items'] = [{'metadata': {'namespace': 'sso', 'deletionTimestamp': 'synthetic'}, 'spec': spec}]
with self.subTest(spec=spec), self.assertRaises(a.Refused): world.step(self.proposal, 'registry')
def test_partial_pod_inventory_refused(self):
world = World(self.secret, self.deployment); world.step(self.proposal, 'stop')
world.pods['metadata']['continue'] = 'another-page'
with self.assertRaises(a.Refused): world.step(self.proposal, 'registry')
def test_original_revision_and_proposal_digest_are_bound(self):
for attr in ['secret', 'deployment']:
world = World(self.secret, self.deployment)
getattr(world, attr)['metadata']['resourceVersion'] = '999'
with self.subTest(attr=attr), self.assertRaises(a.Refused): world.step(self.proposal, 'stop')
changed = copy.deepcopy(self.proposal); changed['after_data']['other'] = 'changed'
with self.assertRaises(a.Refused):
a.propose_step(changed, a.digest(self.proposal), 'stop', self.secret, self.deployment, {'kind': 'PodList', 'items': []})
def test_independent_jsonpatch_rejects_racing_version_or_spec(self):
operation = a.propose_step(self.proposal, a.digest(self.proposal), 'stop',
self.secret, self.deployment, {'kind': 'PodList', 'items': []})
for field in ['version', 'spec']:
raced = copy.deepcopy(self.deployment)
if field == 'version': raced['metadata']['resourceVersion'] = '999'
else: raced['spec']['replicas'] = 2
with self.subTest(field=field), self.assertRaises(jsonpatch.JsonPatchTestFailed):
jsonpatch.apply_patch(raced, operation['patch'])
def test_private_registry_patch_checks_complete_data_and_uid(self):
world = World(self.secret, self.deployment); world.step(self.proposal, 'stop')
operation = a.propose_step(self.proposal, a.digest(self.proposal), 'registry', world.secret, world.deployment, world.pods)
for field in ['data', 'uid']:
raced = copy.deepcopy(world.secret)
if field == 'data': raced['data']['other'] = 'racing-write'
else: raced['metadata']['uid'] = 'recreated'
with self.subTest(field=field), self.assertRaises(jsonpatch.JsonPatchTestFailed):
jsonpatch.apply_patch(raced, operation['patch'])
def test_unrelated_registry_or_deployment_drift_refuses_rollback(self):
for field in ['data', 'spec']:
world = World(self.secret, self.deployment)
for phase in ['stop', 'registry', 'bindings']: world.step(self.proposal, phase)
if field == 'data': world.secret['data']['unrelated-new-field'] = 'keep'
else: world.deployment['spec']['template']['metadata']['labels']['new'] = 'keep'
with self.subTest(field=field), self.assertRaises(a.Refused): world.step(self.proposal, 'rollback-registry')
def test_readback_mismatch_refused_without_raw_output(self):
operation = a.propose_step(self.proposal, a.digest(self.proposal), 'stop', self.secret, self.deployment, {})
with self.assertRaisesRegex(a.Refused, '^mutation_readback_mismatch$'):
a.verify_response(operation, self.deployment)
def test_unsupported_deployment_and_binding_drift_refused(self):
changes = [lambda d: d['spec'].update(replicas=2),
lambda d: d['spec']['strategy'].update(type='RollingUpdate'),
lambda d: a.container(d['spec']).update(image='unreviewed:latest'),
lambda d: a.container(d['spec']).update(envFrom=[{'secretRef': {'name': 'other'}}]),
lambda d: a.container(d['spec'])['env'].append({'name': 'KEYCAPE_CONFIG', 'value': 'other'}),
lambda d: a.container(d['spec'])['env'].append({'name': 'KEYCAPE_PLAYWRIGHT_AGENT_TEST_AGENT_CLIENT_SECRET', 'value': 'synthetic-private-marker'})]
for change in changes:
deployment = copy.deepcopy(self.deployment); change(deployment)
with self.subTest(change=change), self.assertRaises(a.Refused):
a.build(self.secret, deployment, IDENTITIES, REVISION)
def test_native_verifier_is_pinned_contained_and_ignores_ambient_environment(self):
with tempfile.TemporaryDirectory() as folder:
binary = Path(folder) / 'verifier'; binary.write_bytes(b'synthetic-binary')
sha = hashlib.sha256(binary.read_bytes()).hexdigest()
with patch.object(a.subprocess, 'run') as run:
run.return_value = subprocess.CompletedProcess([], 0, b'{"valid": true, "errors": []}', b'')
self.assertTrue(a.check_registry(self.proposal, binary, sha)['proposed_registry_valid'])
self.assertEqual(run.call_count, 2)
self.assertEqual(run.call_args.kwargs['env'], {'PATH': '/usr/bin:/bin'})
self.assertIn('synthetic-private-marker', run.call_args.kwargs['input'].decode())
self.assertNotIn('synthetic-private-marker', str(run.call_args.args))
run.return_value = subprocess.CompletedProcess([], 1, b'{"valid": false, "errors": ["synthetic-private-marker"]}', b'synthetic-private-marker')
with self.assertRaisesRegex(a.Refused, '^native_registry_check_failed$'):
a.check_registry(self.proposal, binary, sha)
with self.assertRaisesRegex(a.Refused, '^native_verifier_digest_mismatch$'):
a.check_registry(self.proposal, binary, 'wrong')
def test_other_env_name_cannot_alias_a_proposed_agent_secret(self):
binding = a.registration.registry.env_binding(a.registration.agent_client(IDENTITIES[0]))
binding['name'] = 'OTHER_CLIENT_SECRET'
a.container(self.deployment['spec'])['env'].append(binding)
with self.assertRaisesRegex(a.Refused, 'delivery_secret_already_bound'):
a.build(self.secret, self.deployment, IDENTITIES, REVISION)
if __name__ == '__main__': unittest.main()

View file

@ -0,0 +1,158 @@
import base64
import contextlib
import copy
import importlib.util
import io
import json
from pathlib import Path
import tempfile
import unittest
import yaml
spec = importlib.util.spec_from_file_location('playwright_registration', Path(__file__).with_name('playwright-registration.py'))
p = importlib.util.module_from_spec(spec)
spec.loader.exec_module(p)
IDENTITY = {'agent_id': 'test-agent', 'service_subject': 'service:test-agent:platform',
'tenant': 'tenant:test', 'identity_record': 'synthetic/agent-record.json'}
def fixture():
raw = ('# preserve the issuer and existing clients\nissuer: https://kc.coulomb.social\n'
'port: 8080\nprivateKeyPem: /synthetic/key.pem\ntokenLifetime: 15m\n'
'authelia:\n issuer: https://auth.coulomb.social\n clientSecret: synthetic-private-marker\n'
'clients:\n - clientId: existing\n clientType: public\n'
' redirectUris: [https://example.test/callback]\n'
' allowedScopes: [openid]\n grantTypes: [authorization_code]\n'
'# trailing configuration comment\nlogLevel: info\n')
return {'metadata': {'uid': 'synthetic', 'resourceVersion': '7'},
'data': {'config.yaml': base64.b64encode(raw.encode()).decode(), 'other': 'preserve'}}, raw
def decode(value):
return base64.b64decode(value).decode()
def altered(secret, change):
result = copy.deepcopy(secret)
config = yaml.safe_load(decode(result['data']['config.yaml']))
change(config)
result['data']['config.yaml'] = base64.b64encode(yaml.safe_dump(config, sort_keys=False).encode()).decode()
return result
class RegistrationTests(unittest.TestCase):
def test_public_matches_platform_and_agent_has_distinct_delivery(self):
client = p.public_client()
self.assertEqual(client['redirectUris'], ['http://127.0.0.1:18764/callback'])
self.assertNotIn('tokenLifetime', client)
self.assertNotIn('secretRef', client)
agent = p.agent_client(IDENTITY)
self.assertEqual(agent['allowedScopes'], ['playwright:connect'])
self.assertEqual(agent['roles'], ['coding-agent'])
self.assertEqual(agent['tenant'], IDENTITY['tenant'])
self.assertEqual(agent['serviceSubject'], IDENTITY['service_subject'])
self.assertEqual(agent['tokenLifetime'], '15m')
self.assertEqual(p.registry.env_binding(agent)['valueFrom']['secretKeyRef'],
{'name': 'keycape-playwright-agent-test-agent-client', 'key': 'client-secret'})
def test_unknown_or_missing_identity_fields_refused(self):
for identity in [{}, [], dict(IDENTITY, secret='not-emitted'), dict(IDENTITY, tenant=''),
dict(IDENTITY, service_subject='human:someone'),
dict(IDENTITY, identity_record='bad\nreference'),
dict(IDENTITY, agent_id='bad_$agent'), dict(IDENTITY, tenant=True)]:
with self.subTest(identity=identity), self.assertRaises(p.Refused):
p.agent_client(identity)
def test_duplicate_agent_or_subject_refused(self):
for second in [IDENTITY, dict(IDENTITY, agent_id='second'),
dict(IDENTITY, service_subject='service:second')]:
with self.subTest(second=second), self.assertRaises(p.Refused):
p.clients([IDENTITY, second])
def test_go_lifetimes_are_bounded_without_global_change(self):
for value in ['15m', '1h', '1h0m0s', '3599.5s', '1000ms', '1.5m', '.5m']:
with self.subTest(value=value): p.human_lifetime({'tokenLifetime': value})
for value in [None, 900, '', '0s', '-1m', '1h1ns', '0.5s', 'NaN', '15mgarbage', '1d']:
with self.subTest(value=value), self.assertRaises(p.Refused):
p.human_lifetime({'tokenLifetime': value})
def test_add_preserves_bytes_and_is_repeatable_then_exact_remove(self):
secret, raw = fixture()
original = copy.deepcopy(secret)
encoded, changed = p.prepare_registry(secret, [IDENTITY])
self.assertEqual(changed, ['playwright-cli', 'playwright-agent-test-agent'])
updated = decode(encoded)
prefix, suffix = raw.split('logLevel:')
self.assertTrue(updated.startswith(prefix))
self.assertTrue(updated.endswith('logLevel:' + suffix))
self.assertEqual(secret, original)
current = copy.deepcopy(secret); current['data']['config.yaml'] = encoded
self.assertEqual(p.prepare_registry(current, [IDENTITY]), (encoded, []))
restored, removed = p.prepare_registry(current, [IDENTITY], 'remove')
self.assertEqual(decode(restored), raw)
self.assertEqual(removed, changed)
self.assertEqual(p.prepare_registry(secret, [IDENTITY], 'remove'),
(secret['data']['config.yaml'], []))
def test_refuses_drift_conflicts_and_unreviewed_audience(self):
base, _ = fixture()
public = p.public_client(); agent = p.agent_client(IDENTITY)
cases = [lambda c: c.update(issuer='https://other.test'),
lambda c: c.update(tokenLifetime='2h'),
lambda c: c['clients'].append(dict(public, allowedScopes=['openid'])),
lambda c: c['clients'].append(dict(public, clientId='other-consumer')),
lambda c: c['clients'].append(dict(agent, clientId='duplicate-subject')),
lambda c: c['clients'].append({'clientId': 'other', 'secretRef': agent['secretRef']}),
lambda c: c['clients'].append(copy.deepcopy(c['clients'][0]))]
for change in cases:
for operation in ['add', 'remove']:
with self.subTest(change=change, operation=operation), self.assertRaises(p.Refused):
p.prepare_registry(altered(base, change), [IDENTITY], operation)
def test_same_agent_can_keep_separate_existing_openbao_client(self):
base, _ = fixture()
existing = dict(p.agent_client(IDENTITY), clientId='existing-openbao-agent',
audience='openbao', allowedScopes=['openbao:login'],
secretRef='env:EXISTING_OPENBAO_SECRET')
secret = altered(base, lambda c: c['clients'].append(existing))
encoded, _ = p.prepare_registry(secret, [IDENTITY])
self.assertIn(existing, yaml.safe_load(decode(encoded))['clients'])
def test_default_audience_client_id_cannot_bypass_dedicated_inventory(self):
base, _ = fixture()
default_audience = dict(p.public_client(), clientId=p.AUDIENCE)
del default_audience['audience']
secret = altered(base, lambda c: c['clients'].append(default_audience))
with self.assertRaises(p.Refused):
p.prepare_registry(secret, [IDENTITY])
def test_cli_outputs_only_proposal_and_no_false_admission(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'agents.json'; path.write_text(json.dumps([IDENTITY]))
out = io.StringIO()
with contextlib.redirect_stdout(out): code = p.main(['--agents', str(path)])
self.assertEqual(code, 0)
result = json.loads(out.getvalue())
self.assertEqual(result['status'], 'source_proposal')
for flag in ['identity_evidence_verified', 'custody_admitted', 'live_activated']:
self.assertFalse(result[flag])
self.assertEqual(len(result['clients']), 2)
self.assertEqual(result['identity_references']['playwright-agent-test-agent'],
IDENTITY['identity_record'])
def test_cli_contains_invalid_private_or_duplicate_input(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'input.json'
for raw in ['synthetic-private-marker', '[{"agent_id":"a","agent_id":"b"}]',
json.dumps([dict(IDENTITY, secret='synthetic-private-marker')]), 'x' * 65537]:
path.write_text(raw)
out = io.StringIO()
with contextlib.redirect_stdout(out): code = p.main(['--agents', str(path)])
self.assertEqual(code, 1)
self.assertNotIn('synthetic-private-marker', out.getvalue())
self.assertEqual(json.loads(out.getvalue())['status'], 'refused')
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,71 @@
import base64
import importlib.util
import json
from pathlib import Path
import unittest
import yaml
spec = importlib.util.spec_from_file_location('portal_rollout', Path(__file__).with_name('portal-client-rollout.py'))
rollout = importlib.util.module_from_spec(spec)
spec.loader.exec_module(rollout)
class PortalClientTests(unittest.TestCase):
def fixture(self, clients=None):
# Synthetic marker values only; no live Secret is loaded by this suite.
raw = ('# preserve this comment\nauthelia:\n issuer: https://auth.example.invalid\n'
' clientSecret: synthetic-marker\nclients:\n' +
yaml.safe_dump(json.loads(json.dumps(clients or [{'clientId': 'existing', 'clientType': 'public'}])), sort_keys=False) +
'tokenLifetime: 15m\n')
return {'data': {'config.yaml': base64.b64encode(raw.encode()).decode(), 'key.pem': 'synthetic-key'}}, raw
def test_add_preserves_other_values_and_original_bytes(self):
secret, raw = self.fixture()
encoded, changed = rollout.replacement(secret)
updated = base64.b64decode(encoded).decode()
self.assertTrue(changed)
self.assertTrue(updated.startswith(raw[:raw.index('tokenLifetime:')]))
self.assertTrue(updated.endswith('tokenLifetime: 15m\n'))
expected = yaml.safe_load(raw)
expected['clients'].append(rollout.portal.CLIENT)
self.assertEqual(yaml.safe_load(updated), expected)
self.assertEqual(secret['data']['key.pem'], 'synthetic-key')
def test_legacy_callback_upgrade_preserves_bytes_and_is_idempotent(self):
legacy = dict(rollout.portal.CLIENT, redirectUris=[
'https://users.92-205-62-239.nip.io/oidc/callback'])
secret, raw = self.fixture([{'clientId': 'existing', 'clientType': 'public'}, legacy])
encoded, changed = rollout.replacement(secret)
updated = base64.b64decode(encoded).decode()
self.assertTrue(changed)
self.assertEqual(updated.replace(' - https://users.coulomb.social/oidc/callback\n', ''), raw)
self.assertEqual(yaml.safe_load(updated)['clients'][-1], rollout.portal.CLIENT)
secret['data']['config.yaml'] = encoded
self.assertEqual(rollout.replacement(secret), (encoded, False))
def test_upgrade_refuses_extra_scope_or_unreviewed_callback(self):
for extra in [dict(allowedScopes=['openid', 'admin']),
dict(redirectUris=['https://users.coulomb.social/oidc/callback']),
dict(clientType='confidential')]:
with self.subTest(extra=extra), self.assertRaises(rollout.Refused):
rollout.replacement(self.fixture([dict(rollout.portal.CLIENT, **extra)])[0])
def test_exact_existing_registration_is_noop(self):
secret, _ = self.fixture([rollout.portal.CLIENT])
self.assertEqual(rollout.replacement(secret), (secret['data']['config.yaml'], False))
def test_differing_or_duplicate_registration_refused(self):
for clients in [[dict(rollout.portal.CLIENT, redirectUris=['https://other.invalid/callback'])],
[rollout.portal.CLIENT, rollout.portal.CLIENT]]:
with self.subTest(clients=len(clients)), self.assertRaises(rollout.Refused):
rollout.replacement(self.fixture(clients)[0])
def test_bootstrap_retains_same_public_registration(self):
clients = json.loads(Path(__file__).with_name('bootstrap-config.json').read_text())['clients']
self.assertEqual([c for c in clients if c['clientId'] == rollout.portal.CLIENT_ID],
[rollout.portal.CLIENT])
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,34 @@
import base64
import importlib.util
from pathlib import Path
import unittest
import yaml
spec = importlib.util.spec_from_file_location('vergabe', Path(__file__).with_name('vergabe-client-rollout.py'))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
class RegistrationTests(unittest.TestCase):
def secret(self, clients):
self.config = {'issuer': 'https://kc.coulomb.social', 'authelia': {'issuer': 'https://auth.coulomb.social'},
'clients': clients}
raw = yaml.safe_dump(self.config, sort_keys=False)
return {'data': {'config.yaml': base64.b64encode(raw.encode()).decode()}}
def test_adds_only_exact_public_client_and_is_idempotent(self):
prior = {'clientId': 'existing', 'clientType': 'public',
'redirectUris': ['https://existing.example/callback']}
secret = self.secret([prior])
encoded, changed = module.replacement(secret)
self.assertTrue(changed)
result = yaml.safe_load(base64.b64decode(encoded))
self.assertEqual(dict(self.config, clients=[prior, module.CLIENT]), result)
secret['data']['config.yaml'] = encoded
self.assertEqual((encoded, False), module.replacement(secret))
def test_no_existing_client_rewrite_and_new_binary_invalid_fields_refused(self):
for client in [dict(module.CLIENT, redirectUris=['https://wrong.example/']),
{'clientId': 'existing', 'roles': ['operator']}]:
with self.assertRaises(module.rollout.Refused):
module.replacement(self.secret([client]))

View file

@ -0,0 +1,39 @@
#!/usr/bin/env python3
"""Fixed Vergabe registration using the existing guarded KeyCape rollout lane."""
import importlib.util
from pathlib import Path
from types import SimpleNamespace
ROOT = Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location('portal_rollout', ROOT / 'portal-client-rollout.py')
rollout = importlib.util.module_from_spec(spec)
spec.loader.exec_module(rollout)
CLIENT = {
'clientId': 'vergabe-demo-company',
'displayName': 'Vergabe Demo Company',
'redirectUris': ['https://vergabe-teilnahme.coulomb.social/demo-company/accounts/oidc/callback/'],
'allowedScopes': ['openid', 'profile', 'groups'],
'grantTypes': ['authorization_code'],
'clientType': 'public',
}
# Reuse the owner's byte-preserving insertion, CAS, cluster pin and safe receipts.
# The portal-specific legacy migration branch is explicitly unreachable here.
original_replacement = rollout.replacement
rollout.portal = SimpleNamespace(CLIENT_ID=CLIENT['clientId'], CLIENT=CLIENT)
def replacement(secret):
_, config, _ = rollout.pin.issuer_document(secret)
for client in config.get('clients', []):
if 'client_credentials' not in client.get('grantTypes', []):
rollout.require(not client.get('roles') and not client.get('serviceSubject'),
'browser_service_identity_fields_block_new_binary')
if client.get('clientId') == CLIENT['clientId']:
rollout.require(client == CLIENT, 'existing_vergabe_registration_differs')
return original_replacement(secret)
rollout.replacement = replacement
if __name__ == '__main__':
raise SystemExit(rollout.main())

View file

@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Verify the live KeyCape config carries the OpenBao admin client and KeyCape
# is serving OIDC discovery after rollout.
set +x
set -euo pipefail
umask 077
NAMESPACE="${KEYCAPE_NAMESPACE:-sso}"
SECRET="${KEYCAPE_CONFIG_SECRET:-keycape-config}"
KUBECTL="${KUBECTL:-kubectl}"
PORT="${KEYCAPE_VERIFY_PORT:-18080}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
"$KUBECTL" get secret "$SECRET" -n "$NAMESPACE" -o json \
| python3 "$SCRIPT_DIR/openbao-client-config.py" verify
PUBLIC_URL="${KEYCAPE_PUBLIC_URL:-https://kc.coulomb.social}"
PUBLIC_AUTHORIZE_URL="${PUBLIC_URL%/}/authorize"
probe_redirect() {
local label="$1"
local redirect_uri="$2"
local output
output=$(
curl -sS -i -G "$PUBLIC_AUTHORIZE_URL" \
--data-urlencode "client_id=openbao-admin" \
--data-urlencode "redirect_uri=$redirect_uri" \
--data-urlencode "response_type=code" \
--data-urlencode "scope=openid profile email groups" \
--data-urlencode "state=netkingdom-openbao-client-probe" \
--data-urlencode "code_challenge=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQ" \
--data-urlencode "code_challenge_method=S256" \
2>&1 || true
)
if grep -q '"unknown client_id"' <<<"$output"; then
echo "[FAIL] $PUBLIC_AUTHORIZE_URL rejects openbao-admin with unknown client_id" >&2
echo " Check DNS for kc.coulomb.social and ensure it reaches the KeyCape ingress that was patched." >&2
exit 1
fi
if ! grep -qE '^HTTP/[0-9.]+ 302 ' <<<"$output"; then
echo "[FAIL] $PUBLIC_AUTHORIZE_URL did not accept the $label redirect URI for openbao-admin" >&2
echo " Redirect URI: $redirect_uri" >&2
echo " Provider response withheld; use owner-scoped metadata diagnostics." >&2
exit 1
fi
echo "[PASS] public KeyCape authorize endpoint accepts $label redirect"
}
probe_redirect "CLI" "http://localhost:8250/oidc/callback"
probe_redirect "operator-tunneled browser UI netkingdom mount" "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback"
KC_POD=$("$KUBECTL" get pod -n "$NAMESPACE" \
-l app.kubernetes.io/name=keycape \
--field-selector=status.phase=Running \
-o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true)
if [[ -z "$KC_POD" ]]; then
echo "[FAIL] no running KeyCape pod found in namespace $NAMESPACE" >&2
exit 1
fi
PF_LOG=$(mktemp "${TMPDIR:-/tmp}/netkingdom-keycape-verify-XXXXXXXX")
"$KUBECTL" -n "$NAMESPACE" port-forward --address 127.0.0.1 svc/keycape "$PORT:8080" \
>"$PF_LOG" 2>&1 &
PF_PID=$!
cleanup() {
kill "$PF_PID" 2>/dev/null || true
wait "$PF_PID" 2>/dev/null || true
rm -f -- "$PF_LOG"
}
trap cleanup EXIT
for _ in $(seq 1 30); do
if python3 - "$PORT" <<'PY' >/dev/null 2>&1
import json
import sys
import urllib.request
port = sys.argv[1]
with urllib.request.urlopen(f"http://127.0.0.1:{port}/.well-known/openid-configuration", timeout=2) as response:
payload = json.load(response)
if not payload.get("issuer"):
raise SystemExit("missing issuer")
PY
then
echo "[PASS] KeyCape discovery endpoint responds via local port-forward to pod $KC_POD"
exit 0
fi
if ! kill -0 "$PF_PID" 2>/dev/null; then
echo "[FAIL] KeyCape port-forward exited before discovery responded" >&2
echo "Port-forward diagnostics withheld." >&2
exit 1
fi
sleep 1
done
echo "[FAIL] KeyCape discovery endpoint did not respond via local port-forward" >&2
echo "Port-forward diagnostics withheld." >&2
exit 1

View file

@ -0,0 +1,56 @@
#!/usr/bin/env python3
"""Read-only native authorize checks; print no state, cookies or auth tokens."""
import base64
import hashlib
import json
import secrets
from urllib.error import HTTPError
from urllib.parse import urlencode, urlsplit
from urllib.request import HTTPRedirectHandler, build_opener
class NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
def verify():
opener = build_opener(NoRedirect)
base = {
"client_id": "user-engine-portal", "response_type": "code",
"redirect_uri": "https://users.92-205-62-239.nip.io/oidc/callback",
"scope": "openid profile email groups", "state": secrets.token_urlsafe(24),
"code_challenge": base64.urlsafe_b64encode(
hashlib.sha256(secrets.token_bytes(32)).digest()
).rstrip(b"=").decode(), "code_challenge_method": "S256",
}
results = []
for name in ("valid", "wrong_callback", "missing_pkce"):
params = dict(base)
if name == "wrong_callback":
params["redirect_uri"] = "https://invalid.example/oidc/callback"
if name == "missing_pkce":
params.pop("code_challenge")
params.pop("code_challenge_method")
try:
response = opener.open("https://kc.coulomb.social/authorize?" + urlencode(params), timeout=20)
except HTTPError as error:
response = error
with response:
row = {"check": name, "status": response.code}
if name == "valid":
location = urlsplit(response.headers.get("Location", ""))
row["redirect_origin"] = location.scheme + "://" + location.netloc
row["passed"] = response.code == 302 and row["redirect_origin"] == "https://auth.coulomb.social"
else:
body = json.loads(response.read(4096))
row.update(error=body.get("error"), feature=body.get("feature"))
expected = "redirect_uri" if name == "wrong_callback" else "missing_pkce"
row["passed"] = response.code == 400 and row["error"] == "invalid_profile_usage" and row["feature"] == expected
results.append(row)
print(json.dumps(results, indent=2))
return all(row["passed"] for row in results)
if __name__ == "__main__":
raise SystemExit(0 if verify() else 1)

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Explicit private input and preview/readback adapter; see lldap/GROUP-MAINTENANCE.md.
set +x
set -euo pipefail
exec python3 "$(dirname -- "$0")/lldap_admin.py" bootstrap "$@"

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Retired by NK-WP-0049. Refuse before inspecting arguments or credentials.
# Keep this entry point so stale callers fail visibly instead of mutating IAM.
printf '%s\n' 'This legacy LLDAP lifecycle command is retired.' 'See docs/legacy-lldap-lifecycle-retirement.md for owner routes and recovery limits.' >&2
exit 2

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Private creation or exact-match verification; rotations require --rotate-key.
set +x
set -euo pipefail
umask 077
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec python3 "$SCRIPT_DIR/../../bootstrap/apply-service-secrets.py" lldap "$@"

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Retired by NK-WP-0049. Refuse before inspecting arguments or credentials.
# Keep this entry point so stale callers fail visibly instead of mutating IAM.
printf '%s\n' 'This legacy LLDAP lifecycle command is retired.' 'See docs/legacy-lldap-lifecycle-retirement.md for owner routes and recovery limits.' >&2
exit 2

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Retired by NK-WP-0049. Refuse before inspecting arguments or credentials.
# Keep this entry point so stale callers fail visibly instead of mutating IAM.
printf '%s\n' 'This legacy LLDAP lifecycle command is retired.' 'See docs/legacy-lldap-lifecycle-retirement.md for owner routes and recovery limits.' >&2
exit 2

View file

@ -0,0 +1,144 @@
#!/usr/bin/env python3
"""Attended repair of one consumer field using an operator-held current password.
Never reads a Secret payload, changes a provider password, or copies a bundle.
"""
from __future__ import annotations
import argparse
import base64
import getpass
import json
import re
import subprocess
import sys
CLUSTER_UID = 'a553c742-0115-43d4-99a4-a5ca56fe0786'
KUBE = ['kubectl', '--kubeconfig', '/home/worsch/.kube/config-hosteurope',
'--server', 'https://127.0.0.1:16444', '--request-timeout=20s']
NAMESPACE = 'sso'
SECRET = 'lldap-secrets'
KEY = 'LLDAP_LDAP_USER_PASS'
# The password and returned directory token remain in this child process only.
# Child stdin carries the operator input; no credential is a process argument.
VERIFY = '''import json,os,sys
from urllib.error import HTTPError
from provisioner import LLDAPProvisioner
try:
assert os.environ['LLDAP_URL']=='http://lldap.sso.svc.cluster.local:17170'
p=LLDAPProvisioner(base_url=os.environ['LLDAP_URL'],admin_password=json.load(sys.stdin)['password'])
token=p._login()
p._gql(token,'query { groups { id } }',{})
except Exception:
print('directory-check-failed');sys.exit(1)
print('directory-check-passed')
'''
VERIFY_RELOADED = '''import os,sys
from provisioner import LLDAPProvisioner
try:
p=LLDAPProvisioner(base_url=os.environ['LLDAP_URL'],admin_password=os.environ['LLDAP_ADMIN_PASSWORD'])
token=p._login()
p._gql(token,'query { groups { id } }',{})
except Exception:
print('reloaded-check-failed');sys.exit(1)
print('reloaded-check-passed')
'''
class Refused(Exception):
"""Fixed safe code only; never carry child output or an exception repr."""
def run(arguments, *, data=None, timeout=30):
try:
result = subprocess.run(KUBE + arguments, input=data, text=True,
capture_output=True, timeout=timeout)
except (OSError, subprocess.TimeoutExpired):
raise Refused('command_unavailable') from None
if result.returncode:
raise Refused('command_refused') from None
return result.stdout.strip()
def inspect():
uid = run(['get', 'namespace', 'kube-system', '-o', 'jsonpath={.metadata.uid}'])
if uid != CLUSTER_UID:
raise Refused('wrong_cluster')
# Do not include annotations: last-applied configuration can contain data.
fields = run(['-n', NAMESPACE, 'get', 'secret', SECRET, '-o',
'go-template={{.metadata.uid}} {{.metadata.resourceVersion}} {{if .metadata.ownerReferences}}{{len .metadata.ownerReferences}}{{else}}0{{end}}']).split()
if len(fields) != 3 or not re.fullmatch(r'[a-f0-9-]{36}', fields[0]) or not fields[1].isdigit():
raise Refused('invalid_metadata')
if fields[2] != '0':
raise Refused('controller_owned_secret')
return {'uid': fields[0], 'resource_version': fields[1]}
def patch(password, metadata):
return [
{'op': 'test', 'path': '/metadata/uid', 'value': metadata['uid']},
{'op': 'test', 'path': '/metadata/resourceVersion', 'value': metadata['resource_version']},
{'op': 'replace', 'path': '/data/' + KEY,
'value': base64.b64encode(password.encode()).decode()},
]
def reconcile(password, *, mode, expected_uid=None, expected_resource_version=None):
if not password or len(password) > 4096 or '\x00' in password:
raise Refused('invalid_protected_input')
metadata = inspect()
if mode == 'apply' and metadata != {'uid': expected_uid, 'resource_version': expected_resource_version}:
raise Refused('stale_metadata')
result = run(['-n', NAMESPACE, 'exec', '-i', 'deployment/identity-provisioner',
'--', 'python', '-c', VERIFY], data=json.dumps({'password': password}))
if result != 'directory-check-passed':
raise Refused('current_provider_credential_unverified')
if mode == 'check':
return {'result': 'checked', 'provider_login': True, 'secret_written': False, **metadata}
payload = json.dumps(patch(password, metadata))
command = ['-n', NAMESPACE, 'patch', 'secret', SECRET, '--type=json',
'--patch-file=/dev/stdin', '-o', 'name']
run(command + ['--dry-run=server'], data=payload)
run(command, data=payload)
# Do not restore the known nonworking field on failure. Report partial repair.
try:
run(['-n', NAMESPACE, 'rollout', 'restart', 'deployment/identity-provisioner'])
run(['-n', NAMESPACE, 'rollout', 'status', 'deployment/identity-provisioner',
'--timeout=120s'], timeout=150)
result = run(['-n', NAMESPACE, 'exec', 'deployment/identity-provisioner',
'--', 'python', '-c', VERIFY_RELOADED])
if result != 'reloaded-check-passed':
raise Refused('consumer_verification_failed')
except Refused:
raise Refused('field_updated_consumer_verification_incomplete') from None
return {'result': 'reconciled', 'provider_login': True, 'consumer_login': True,
'provider_password_changed': False, 'secret_field': KEY, **inspect()}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('mode', choices=['inspect', 'check', 'apply'])
parser.add_argument('--expected-uid')
parser.add_argument('--expected-resource-version')
args = parser.parse_args()
try:
if args.mode == 'inspect':
result = {'result': 'inspected', **inspect()}
else:
if args.mode == 'apply' and not (args.expected_uid and args.expected_resource_version):
raise Refused('exact_metadata_required')
if not sys.stdin.isatty():
raise Refused('attended_terminal_required')
password = getpass.getpass('Current working LLDAP admin password (hidden): ')
result = reconcile(password, mode=args.mode, expected_uid=args.expected_uid,
expected_resource_version=args.expected_resource_version)
del password
print(json.dumps(result, sort_keys=True))
return 0
except Refused as error:
print(json.dumps({'result': 'refused', 'code': str(error)}))
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,281 @@
#!/usr/bin/env python3
"""Owner-invoked LDAP group maintenance; no credential discovery or authority grant."""
import argparse
import hashlib
import importlib.util
import io
import json
import os
from pathlib import Path
import re
import ssl
import sys
import urllib.error
import urllib.parse
import urllib.request
ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(ROOT / 'identity-provisioner'))
from provisioner import LLDAPProvisioner, _service_group_request # noqa: E402
BASELINE = ('net-kingdom-users', 'net-kingdom-admins', 'activity-core-operators')
MAX_BODY = 1024 * 1024
class Refused(Exception):
pass
class Parser(argparse.ArgumentParser):
def error(self, message):
raise Refused()
def unique_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise Refused()
result[key] = value
return result
def endpoint(value):
if not isinstance(value, str) or not value.isascii() or any(ord(c) <= 32 for c in value):
raise Refused()
parsed = urllib.parse.urlsplit(value)
if parsed.username is not None or parsed.password is not None or parsed.query or parsed.fragment or parsed.path not in ('', '/'):
raise Refused()
if value.rstrip('/') in ('https://lldap.coulomb.social', 'https://lldap.coulomb.social:443'):
return value.rstrip('/')
if parsed.hostname not in ('127.0.0.1', '::1') or parsed.scheme not in ('http', 'https') or parsed.port is None:
raise Refused()
if not 1 <= parsed.port <= 65535:
raise Refused()
expected = f'{parsed.scheme}://{("[::1]" if parsed.hostname == "::1" else parsed.hostname)}:{parsed.port}'
if value.rstrip('/') != expected:
raise Refused()
return expected
def private_password(root):
root = Path(os.path.abspath(root))
if any((p / '.git').is_file() or (p / '.git/HEAD').is_file() for p in (root, *root.parents)):
raise Refused()
spec = importlib.util.spec_from_file_location('lldap_custody', ROOT / 'sso-mfa/bootstrap/apply-service-secrets.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
with module.custody(root, ['lldap']) as directories:
value = module.read_env(directories['lldap']).get('LLDAP_LDAP_USER_PASS')
if not isinstance(value, str) or not value or len(value.encode()) > 4096 or any(ord(c) < 32 for c in value):
raise Refused()
return value
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
return None
class Transport:
"""Inject bounded, origin-checked transport into the existing provider."""
def __init__(self, base):
self.base = endpoint(base)
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.load_verify_locations(cafile=ssl.get_default_verify_paths().openssl_cafile)
self.opener = urllib.request.build_opener(
urllib.request.ProxyHandler({}), NoRedirect(), urllib.request.HTTPSHandler(context=context),
)
def __call__(self, request, timeout):
if request.full_url not in {self.base + '/auth/simple/login', self.base + '/api/graphql'}:
raise Refused()
if request.method != 'POST' or not isinstance(request.data, bytes) or len(request.data) > MAX_BODY:
raise Refused()
try:
with self.opener.open(request, timeout=min(timeout, 15)) as response:
if response.status != 200:
raise Refused()
raw = response.read(MAX_BODY + 1)
if len(raw) > MAX_BODY:
raise Refused()
body = json.loads(raw, object_pairs_hook=unique_object)
if not isinstance(body, dict):
raise Refused()
if request.full_url.endswith('/auth/simple/login'):
token = body.get('token')
if not isinstance(token, str) or not 1 <= len(token) <= 16384 or any(not 33 <= ord(c) <= 126 for c in token):
raise Refused()
return io.BytesIO(raw)
except urllib.error.HTTPError as exc:
exc.close()
raise Refused() from None
except Exception:
raise Refused() from None
def snapshot(provider, token):
value = provider._gql(token, 'query { users { id } groups { id displayName users { id } } }', {})
if set(value) != {'users', 'groups'} or not isinstance(value['users'], list) or not isinstance(value['groups'], list):
raise Refused()
if len(value['users']) > 10000 or len(value['groups']) > 1000:
raise Refused()
users = []
for user in value['users']:
uid = user.get('id') if isinstance(user, dict) else None
if not isinstance(uid, str) or not re.fullmatch(r'[a-z][a-z0-9._-]{0,127}', uid):
raise Refused()
users.append(uid)
if len(set(users)) != len(users):
raise Refused()
groups = {}
ids = set()
for group in value['groups']:
if not isinstance(group, dict):
raise Refused()
name, gid, members = group.get('displayName'), group.get('id'), group.get('users')
if (not isinstance(name, str) or not name or len(name) > 256 or any(ord(c) < 32 for c in name)
or type(gid) is not int or gid < 1 or not isinstance(members, list)
or name in groups or gid in ids or len(members) > 10000):
raise Refused()
member_ids = [member.get('id') if isinstance(member, dict) else None for member in members]
if any(not isinstance(uid, str) or uid not in users for uid in member_ids) or len(set(member_ids)) != len(member_ids):
raise Refused()
groups[name] = {'id': gid, 'members': sorted(member_ids)}
ids.add(gid)
return {'users': sorted(users), 'groups': groups}
def validate_operation(operation, action=None, group=None, user=None):
if operation not in {'bootstrap', 'membership', 'verify', 'groups'}:
raise Refused()
if operation == 'groups':
# Read-only sign-in diagnosis preview (docs/sign-in-diagnosis.md).
if action is not None or group is not None or not isinstance(user, str) or not re.fullmatch(r'[a-z][a-z0-9._-]{0,127}', user):
raise Refused()
return
if operation != 'membership':
if any(v is not None for v in (action, group, user)):
raise Refused()
return
if action not in {'add', 'remove', 'list'} or not isinstance(group, str):
raise Refused()
if action == 'list':
if user is not None:
raise Refused()
members = {}
else:
members = {group: [user]}
_service_group_request({'groups': [group], 'members': members,
'idempotency_key': 'owner-group-maintenance', 'correlation_id': 'owner-group-maintenance'})
def maintain(provider, base, operation, *, action=None, group=None, user=None, apply=False, expected_digest=None):
validate_operation(operation, action, group, user)
if (apply and (operation in ('verify', 'groups') or action == 'list')) or (expected_digest is not None and not apply):
raise Refused()
if apply and (not isinstance(expected_digest, str) or not re.fullmatch(r'[0-9a-f]{64}', expected_digest)):
raise Refused()
token = provider._login()
before = snapshot(provider, token)
if operation == 'verify':
present = {name: name in before['groups'] for name in BASELINE}
return {'result': 'structure_observed', 'baseline_groups_present': present,
'directory_user_count': len(before['users']),
'break_glass_account_present': 'break-glass' in before['users'],
'break_glass_platform_membership_present': 'break-glass' in before['groups'].get('net-kingdom-admins', {}).get('members', []),
'native_acceptance': 'unverified',
'remaining': ['governed_lifecycle', 'actual_login_and_mfa', 'issued_claims_and_consumer_access',
'session_revocation', 'independent_recovery']}
if operation == 'groups':
if user not in before['users']:
raise Refused()
names = sorted(name for name, item in before['groups'].items() if user in item['members'])
return {'result': 'groups_observed', 'uid': user, 'groups': names, 'native_acceptance': 'unverified'}
if operation == 'membership' and (group not in before['groups'] or (user is not None and user not in before['users'])):
raise Refused()
binding = {'base': endpoint(base), 'operation': operation, 'action': action, 'group': group, 'user': user, 'before': before}
digest = hashlib.sha256(json.dumps(binding, sort_keys=True, separators=(',', ':')).encode()).hexdigest()
if operation == 'bootstrap':
changes = [{'create_group': name} for name in BASELINE if name not in before['groups']]
elif action == 'list':
return {'result': 'membership_observed', 'group': group, 'members': before['groups'][group]['members'], 'native_acceptance': 'unverified'}
else:
member = user in before['groups'][group]['members']
changes = [{'action': action, 'group': group, 'user': user}] if member != (action == 'add') else []
result = {'operation': operation, 'changes': changes, 'snapshot_sha256': digest, 'native_acceptance': 'unverified'}
if not apply:
return dict(result, result='preview')
if digest != expected_digest:
raise Refused()
# LLDAP has no compare-and-swap transaction here. Caller must hold the
# owner-controlled exclusive window; check again before the first mutation.
if snapshot(provider, token) != before:
raise Refused()
if operation == 'bootstrap':
rows = [{'displayName': name, 'id': item['id']} for name, item in before['groups'].items()]
for change in changes:
provider._ensure_group(token, rows, change['create_group'])
elif changes:
method = provider._add_group if action == 'add' else provider._remove_group
method(token, user, before['groups'][group]['id'])
after = snapshot(provider, token)
expected = json.loads(json.dumps(before))
if operation == 'bootstrap':
for change in changes:
name = change['create_group']
if name not in after['groups'] or after['groups'][name]['members']:
raise Refused()
expected['groups'][name] = after['groups'][name]
elif changes:
members = set(expected['groups'][group]['members'])
if action == 'add':
members.add(user)
else:
members.discard(user)
expected['groups'][group]['members'] = sorted(members)
if after != expected:
raise Refused()
return dict(result, result='verified', recovery='no_automatic_rollback')
def main(argv=None):
phase = 'input'
try:
parser = Parser(description=__doc__)
sub = parser.add_subparsers(dest='operation', required=True, parser_class=Parser)
for operation in ('bootstrap', 'membership', 'verify', 'groups'):
command = sub.add_parser(operation)
command.add_argument('--root', required=True, help='Explicit private custody directory outside Git')
command.add_argument('--lldap-url', default='https://lldap.coulomb.social')
if operation == 'groups':
command.add_argument('--user', required=True)
elif operation != 'verify':
command.add_argument('--apply', action='store_true')
command.add_argument('--expected-digest')
if operation == 'membership':
command.add_argument('action', choices=('add', 'remove', 'list'))
command.add_argument('--group', required=True)
command.add_argument('--user')
args = parser.parse_args(argv)
base = endpoint(args.lldap_url)
action, group, user = (getattr(args, name, None) for name in ('action', 'group', 'user'))
validate_operation(args.operation, action, group, user)
apply = getattr(args, 'apply', False)
expected = getattr(args, 'expected_digest', None)
if (expected is not None and not apply) or (apply and (action == 'list' or not isinstance(expected, str) or not re.fullmatch(r'[0-9a-f]{64}', expected))):
raise Refused()
transport = Transport(base)
password = private_password(args.root)
provider = LLDAPProvisioner(base_url=base, admin_password=password, opener=transport)
phase = 'maintenance' if apply else 'observation'
result = maintain(provider, base, args.operation, action=action, group=group, user=user, apply=apply, expected_digest=expected)
print(json.dumps(result, sort_keys=True))
return 2 if args.operation == 'verify' else 0
except Exception:
print(json.dumps({'result': 'unverified', 'phase': phase, 'recovery': 'reconcile_current_state_before_retry'}), file=sys.stderr)
return 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,5 @@
#!/usr/bin/env bash
# Explicit private input and preview/readback adapter; see lldap/GROUP-MAINTENANCE.md.
set +x
set -euo pipefail
exec python3 "$(dirname -- "$0")/lldap_admin.py" membership "$@"

View file

@ -0,0 +1,74 @@
import base64
import importlib.util
import io
import json
from pathlib import Path
import subprocess
import unittest
from unittest.mock import patch
spec=importlib.util.spec_from_file_location('repair',Path(__file__).with_name('identity-provisioner-reconcile.py'))
repair=importlib.util.module_from_spec(spec)
spec.loader.exec_module(repair)
META={'uid':'c6a9e6be-5bb5-47e6-9faa-06b8d72afec3','resource_version':'1234'}
SYNTHETIC='synthetic-only-current-password'
class ReconcileTests(unittest.TestCase):
def test_patch_has_exact_guards_and_changes_only_the_named_field(self):
changes=repair.patch(SYNTHETIC,META)
self.assertEqual(['test','test','replace'],[x['op'] for x in changes])
self.assertEqual('/metadata/uid',changes[0]['path'])
self.assertEqual('/metadata/resourceVersion',changes[1]['path'])
self.assertEqual('/data/LLDAP_LDAP_USER_PASS',changes[2]['path'])
self.assertEqual(SYNTHETIC,base64.b64decode(changes[2]['value']).decode())
def test_stale_metadata_stops_before_credential_use(self):
with patch.object(repair,'inspect',return_value=META), patch.object(repair,'run') as run:
with self.assertRaisesRegex(repair.Refused,'stale_metadata'):
repair.reconcile(SYNTHETIC,mode='apply',expected_uid=META['uid'],expected_resource_version='old')
run.assert_not_called()
def test_provider_refusal_stops_before_any_write(self):
with patch.object(repair,'inspect',return_value=META), patch.object(repair,'run',side_effect=repair.Refused('command_refused')) as run:
with self.assertRaises(repair.Refused):
repair.reconcile(SYNTHETIC,mode='apply',expected_uid=META['uid'],expected_resource_version=META['resource_version'])
self.assertEqual(1,run.call_count)
self.assertNotIn('patch',run.call_args.args[0])
def test_check_does_not_write_and_keeps_password_out_of_arguments(self):
with patch.object(repair,'inspect',return_value=META), patch.object(repair,'run',return_value='directory-check-passed') as run:
result=repair.reconcile(SYNTHETIC,mode='check')
self.assertFalse(result['secret_written'])
self.assertNotIn(SYNTHETIC,str(run.call_args.args))
self.assertNotIn(SYNTHETIC,json.dumps(result))
self.assertEqual(SYNTHETIC,json.loads(run.call_args.kwargs['data'])['password'])
def test_apply_dry_runs_then_writes_reloads_and_verifies(self):
results=['directory-check-passed','secret/lldap-secrets','secret/lldap-secrets','restarted','ready','reloaded-check-passed']
with patch.object(repair,'inspect',return_value=META), patch.object(repair,'run',side_effect=results) as run:
result=repair.reconcile(SYNTHETIC,mode='apply',expected_uid=META['uid'],expected_resource_version=META['resource_version'])
self.assertEqual('reconciled',result['result'])
self.assertFalse(result['provider_password_changed'])
calls=run.call_args_list
self.assertIn('--dry-run=server',calls[1].args[0])
self.assertNotIn('--dry-run=server',calls[2].args[0])
for call in calls:
self.assertNotIn(SYNTHETIC,str(call.args))
self.assertNotIn(base64.b64encode(SYNTHETIC.encode()).decode(),str(call.args))
def test_child_errors_never_reveal_output(self):
failed=subprocess.CompletedProcess([],1,stdout=SYNTHETIC,stderr=SYNTHETIC)
with patch.object(subprocess,'run',return_value=failed):
with self.assertRaises(repair.Refused) as result:
repair.run(['anything'])
self.assertEqual('command_refused',str(result.exception))
def test_wrong_cluster_and_controller_owner_refused(self):
with patch.object(repair,'run',return_value='foreign-cluster'):
with self.assertRaisesRegex(repair.Refused,'wrong_cluster'): repair.inspect()
with patch.object(repair,'run',side_effect=[repair.CLUSTER_UID,META['uid']+' 1234 1']):
with self.assertRaisesRegex(repair.Refused,'controller_owned_secret'): repair.inspect()
if __name__=='__main__': unittest.main()

View file

@ -0,0 +1,7 @@
#!/usr/bin/env bash
# Private creation or exact-match verification; rotations require --rotate-key.
set +x
set -euo pipefail
umask 077
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
exec python3 "$SCRIPT_DIR/../../bootstrap/apply-service-secrets.py" postgresql "$@"

View file

@ -0,0 +1,25 @@
"""Provider-local activation after the guarded deployment; never changes tokens."""
import contextlib,io,json,logging
result={'success':False}
with contextlib.redirect_stdout(io.StringIO()),contextlib.redirect_stderr(io.StringIO()):
try:
logging.disable(logging.CRITICAL)
from privacyidea.app import create_app
from privacyidea.lib.policy import PolicyClass,set_policy
app=create_app(config_name='production',silent=True)
with app.app_context():
if app.config.get('PI_INIT_CHECK_HOOK')!='keycape_onboarding_guard.check':raise ValueError('hook_not_active')
from keycape_onboarding_guard import check
rows=[r for r in PolicyClass().policies if r['scope']=='user' and r['active']]
expected=next(r for r in rows if r['name']=='totp-self-enrollment')
if expected['realm']!=['coulomb'] or expected.get('conditions') or expected['action'] not in [{'enrollTOTP':True,'delete':True,'disable':True},{'enrollTOTP':True}]:raise ValueError('self_service_policy_changed')
if any(r['name'] not in {'totp-self-enrollment','keycape-pending-enrollment-cancel'} and (not r['realm'] or 'coulomb' in r['realm']) for r in rows):raise ValueError('additional_user_policy_requires_review')
set_policy(name='totp-self-enrollment',scope='user',action='enrollTOTP',realm='coulomb')
set_policy(name='keycape-pending-enrollment-cancel',scope='user',action='delete',realm='coulomb',conditions=[('token','rollout_state','equals','verify',True)])
fresh=PolicyClass().policies
active=next(r for r in fresh if r['name']=='totp-self-enrollment')
pending=next(r for r in fresh if r['name']=='keycape-pending-enrollment-cancel')
if active['action']!={'enrollTOTP':True} or not pending['conditions']:raise ValueError('readback_failed')
result={'success':True,'hook':'keycape_onboarding_guard.check','self_service_enrollment':True,'pending_cancel_only':True,'active_factor_changes_require_recovery':True,'existing_tokens_changed':False}
except Exception as error:result={'success':False,'failure_type':type(error).__name__}
print(json.dumps(result));raise SystemExit(0 if result['success'] else 1)

View file

@ -0,0 +1,100 @@
"""Version-scoped in-pod bootstrap. Password input is JSON on stdin only.
Existing credentials/policies are never updated. Insert-only DB writes and
unique constraints protect against concurrent creation. Native MFA/effective
policy acceptance is a separate attended procedure.
"""
import json
import logging
import re
import sys
NAME = 'pi-admin'
POLICY = 'pi-admin-all-rights'
class Refused(Exception):
pass
def admitted_policy(policy):
if (policy.get('name') != POLICY or policy.get('scope') != 'admin'
or policy.get('adminuser') != [NAME] or policy.get('action') != {'*': True}
or policy.get('active') is not True or policy.get('priority') != 1
or policy.get('check_all_resolvers') is not False
or policy.get('user_case_insensitive') is not False):
return False
return all(not policy.get(key) for key in (
'realm', 'adminrealm', 'resolver', 'pinode', 'user', 'client', 'time', 'conditions', 'user_agents'))
def ensure(password, admins, policies, verify, insert):
existing = NAME in admins
named = [policy for policy in policies if policy.get('name') == POLICY]
if len(named) > 1 or (named and not admitted_policy(named[0])): raise Refused()
if not named:
# Introducing the first admin policy changes defaults for every admin.
# Existing deployments need their owner's policy migration, not bootstrap.
if any(admin != NAME for admin in admins) or any(p.get('scope') == 'admin' for p in policies):
raise Refused()
if existing and not verify(NAME, password): raise Refused()
if not existing or not named:
insert(password, account=not existing, policy=not named)
if not verify(NAME, password): raise Refused()
return {'result': 'verified' if existing else 'created', 'mfa_verified': False}
def main():
from importlib.metadata import version
if version('privacyIDEA') != '3.12.2': raise Refused()
raw = sys.stdin.buffer.read(1025)
if len(raw) > 1024: raise Refused()
payload = json.loads(raw)
if (not isinstance(payload, dict) or set(payload) != {'password'}
or not isinstance(payload['password'], str)
or not re.fullmatch(r'[A-Za-z0-9]{32,128}', payload['password'])):
raise Refused()
# Avoid library diagnostics carrying credential/configuration data.
logging.disable(logging.CRITICAL)
from privacyidea.cli import create_silent_app
from privacyidea.lib.auth import list_db_admin, verify_db_admin
from privacyidea.lib.crypto import hash_with_pepper
from privacyidea.lib.policy import get_policies
from privacyidea.models import Admin, Policy, db
from privacyidea.models.config import save_config_timestamp
app = create_silent_app()
with app.app_context():
# Username-only bootstrap policy must not admit namesakes in other realms.
if app.config.get('SUPERUSER_REALM', []): raise Refused()
def insert(password, *, account, policy):
try:
# Do not call Admin.save/create_db_admin or set_policy: they upsert.
if account:
db.session.add(Admin(username=NAME, password=hash_with_pepper(password)))
if policy:
db.session.add(Policy(name=POLICY, scope='admin', action='*', adminuser=NAME))
db.session.flush() # constraint conflicts fail without updates
if policy: save_config_timestamp()
db.session.commit()
except Exception:
db.session.rollback()
raise Refused() from None
result = ensure(payload['password'], [admin.username for admin in list_db_admin()],
get_policies(), verify_db_admin, insert)
if not any(admitted_policy(p) for p in get_policies()): raise Refused()
return result
if __name__ == '__main__':
# Only the fixed receipt may reach the caller; suppress all library output.
import os
receipt_fd = os.dup(1)
with open(os.devnull, 'w') as sink:
os.dup2(sink.fileno(), 1); os.dup2(sink.fileno(), 2)
try:
result = main()
os.write(receipt_fd, (json.dumps(result) + '\n').encode())
except Exception:
sys.exit(1)
finally:
os.close(receipt_fd)