"""CCR-2026-0033 custody child: synthetic values and fakes only; no OpenBao, cluster or real file.""" import contextlib import copy import io import json import os from pathlib import Path import sys import tempfile from types import SimpleNamespace import unittest from unittest.mock import patch import yaml ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / 'scripts')) import keycape_secrets_engine_openbao_custody as child # noqa: E402 DECISION = '00000000-0000-4000-8000-000000000033' SYNTHETIC = 'synthetic-test-value-' + 'x' * 43 def result(code=0, stdout=b'', stderr=b''): return SimpleNamespace(returncode=code, stdout=stdout, stderr=stderr) class FakeBao: """KV-v2 metadata/data behaviour for one path; refuses data reads outright.""" def __init__(self, current=None, fail_write=False, metadata=None): self.current, self.fail_write, self.calls, self.payloads = current, fail_write, [], [] self.metadata = metadata or {'cas_required': False, 'max_versions': 0, 'delete_version_after': '0s', 'custom_metadata': None} self.metadata_writes = [] def __call__(self, args, *, payload=None, token=None, allow_failure=False): self.calls.append(list(args)) if args[:2] == ['token', 'lookup']: return result(stdout=json.dumps({'data': {'policies': ['default', 'platform-admin']}}).encode()) if args[0] == 'read' and args[-1] == child.KV_DATA: raise AssertionError('data path read attempted') if args[0] == 'read' and args[-1] == child.KV_METADATA: if self.current is None: return result(2, stderr=b'No value found at ' + child.KV_METADATA.encode()) body = dict(self.metadata, current_version=self.current, versions={str(self.current): {'destroyed': False, 'deletion_time': ''}}) return result(stdout=json.dumps({'data': body}).encode()) if args[0] == 'write' and child.KV_METADATA in args: self.metadata_writes.append(payload) self.metadata.update(copy.deepcopy(payload)) return result() if args[0] == 'write' and child.KV_DATA in args: self.payloads.append(payload) if self.fail_write or payload['options']['cas'] != (self.current or 0): raise child.LaneError('command_failed') self.current = (self.current or 0) + 1 return result(stdout=json.dumps({'request_id': 'req-1', 'data': {'version': self.current}}).encode()) raise AssertionError('unexpected bao call: ' + ' '.join(args[:2])) def approve(path, base, name, decision=DECISION): ccr = yaml.safe_load(path.read_text()) ccr['status'] = 'approved' ccr['openbao']['auth']['bound_claims_confirmed'] = True ccr['review']['comments'] = [ {'at': '2026-10-06', 'reviewer': r, 'decision': 'approved', 'comment': 'custody child approved by decision ' + decision} for r in child.APPROVERS] out = base / name out.write_text(yaml.safe_dump(ccr, sort_keys=False)) return out, ccr class CustodyChild(unittest.TestCase): def setUp(self): self.tmp = tempfile.TemporaryDirectory() base = Path(self.tmp.name) self.ccr, data = approve(child.CCR_PATH, base, 'CCR-2026-0033.yaml') self.desired = data['openbao']['kv_metadata'] self.sibling, _ = approve(child.SIBLING_PATH, base, 'CCR-2026-0034.yaml') self.bao = FakeBao() self.patches = [patch.object(child, 'CCR_PATH', self.ccr), patch.object(child, 'SIBLING_PATH', self.sibling), patch.object(child, 'bao', self.bao), patch.object(child, 'attended_envelope', lambda: True), patch.object(child.secrets, 'token_urlsafe', lambda n: SYNTHETIC)] for p in self.patches: p.start() self.umask = os.umask(0o022) def tearDown(self): os.umask(self.umask) for p in reversed(self.patches): p.stop() self.tmp.cleanup() def run_child(self, *extra, decision=DECISION): receipt = Path(self.tmp.name) / ('receipt-%d.json' % len(list(Path(self.tmp.name).glob('receipt-*')))) out, err = io.StringIO(), io.StringIO() argv = ['provision', '--receipt', str(receipt), *extra] if decision is not None: argv += ['--approval-decision', decision] with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): code = child.main(argv) text = receipt.read_text() for blob in (text, out.getvalue(), err.getvalue(), json.dumps(self.bao.calls), ' '.join(argv)): self.assertNotIn(SYNTHETIC, blob) self.assertEqual(out.getvalue(), '') self.assertEqual(oct(receipt.stat().st_mode & 0o777), oct(0o600)) return code, json.loads(text) def test_refuses_without_or_with_wrong_decision(self): with self.assertRaises(SystemExit), contextlib.redirect_stderr(io.StringIO()): child.main(['provision', '--receipt', str(Path(self.tmp.name) / 'r.json')]) for decision, reason in (('not-a-uuid', 'approval_decision_id_required'), (child.BOUNDARY_DECISION, 'boundary_decision_does_not_approve_custody_child'), ('11111111-1111-4111-8111-111111111111', 'custody_child_decision_not_recorded_in_ccr')): code, receipt = self.run_child(decision=decision) self.assertEqual((code, receipt['error']), (1, reason)) self.assertEqual(self.bao.calls, []) def proposed_copy(self, name): text = (ROOT / 'credential-change-requests' / name).read_text() path = Path(self.tmp.name) / name path.write_text(text.replace('\nstatus: approved\n', '\nstatus: proposed\n', 1)) return path def test_proposed_ccrs_are_refused(self): with patch.object(child, 'CCR_PATH', self.proposed_copy('CCR-2026-0033.yaml')): code, receipt = self.run_child() self.assertEqual((code, receipt['error']), (1, 'approved_ccr_required')) with patch.object(child, 'SIBLING_PATH', self.proposed_copy('CCR-2026-0034.yaml')): code, receipt = self.run_child() self.assertEqual((code, receipt['error']), (1, 'approved_ccr_required')) self.assertEqual(self.bao.calls, []) def test_refuses_ambient_token_or_outside_envelope(self): with patch.object(child, 'attended_envelope', lambda: False): code, receipt = self.run_child() self.assertEqual(receipt['error'], 'attended_warden_envelope_required') self.assertEqual(self.bao.calls, []) self.patches[3].stop() try: with patch.object(child.Path, 'home', lambda: Path('/x/.warden-attended-login/u')): with patch.dict(os.environ, {}, clear=False): os.environ.pop('BAO_TOKEN', None) os.environ.pop('VAULT_TOKEN', None) self.assertTrue(child.attended_envelope()) for var in ('BAO_TOKEN', 'VAULT_TOKEN'): with patch.dict(os.environ, {var: 'synthetic-ambient'}): self.assertFalse(child.attended_envelope()) code, receipt = self.run_child() self.assertEqual(receipt['error'], 'attended_warden_envelope_required') finally: self.patches[3].start() self.assertEqual(self.bao.calls, []) def test_create_writes_kv_cas0_and_metadata(self): code, receipt = self.run_child() self.assertEqual(code, 0) self.assertEqual(receipt['status'], 'custody_written_pending_issuer_delivery') self.assertEqual((receipt['kv_version'], receipt['kv_cas'], receipt['kv_metadata']), (1, 0, 'applied')) self.assertEqual(self.bao.payloads, [{'options': {'cas': 0}, 'data': {'CLIENT_SECRET': SYNTHETIC}}]) self.assertEqual(self.bao.metadata_writes, [self.desired]) self.assertTrue(self.bao.metadata['cas_required']) self.assertEqual(self.bao.metadata['max_versions'], 2) self.assertEqual(self.bao.metadata['custom_metadata']['ccr'], 'CCR-2026-0033') self.assertEqual(sorted(p.name for p in Path(self.tmp.name).iterdir()), ['CCR-2026-0033.yaml', 'CCR-2026-0034.yaml', 'receipt-0.json']) def test_existing_entry_requires_rotate_and_rotation_uses_current_cas(self): self.bao.current = 1 self.bao.metadata.update(copy.deepcopy(self.desired)) code, receipt = self.run_child() self.assertEqual((code, receipt['error']), (1, 'existing_custody_requires_rotate')) self.assertEqual(self.bao.payloads, []) code, receipt = self.run_child('--rotate') self.assertEqual((code, receipt['kv_cas'], receipt['kv_version'], receipt['kv_metadata']), (0, 1, 2, 'unchanged')) self.assertEqual(self.bao.metadata_writes, []) def test_rotate_without_entry_is_refused(self): code, receipt = self.run_child('--rotate') self.assertEqual((code, receipt['error']), (1, 'rotation_requires_existing_custody')) self.assertEqual(self.bao.payloads, []) def test_failed_kv_write_records_no_version_and_no_metadata(self): self.bao.fail_write = True code, receipt = self.run_child() self.assertEqual((code, receipt['kv_written'], receipt['error']), (1, False, 'command_failed')) self.assertEqual(self.bao.metadata_writes, []) def test_metadata_readback_mismatch_fails_but_records_written_version(self): real = self.bao.__call__ bao = self.bao def wrapper(args, **kw): if args[0] == 'write' and child.KV_METADATA in args: bao.calls.append(list(args)) return result() return real(args, **kw) with patch.object(child, 'bao', wrapper): code, receipt = self.run_child() self.assertEqual((code, receipt['kv_written'], receipt['kv_version'], receipt['error']), (1, True, 1, 'kv_metadata_readback_mismatch')) def test_receipt_contains_only_metadata_keys(self): _, receipt = self.run_child() self.assertTrue(set(receipt) <= { 'schema', 'ccr', 'status', 'phase', 'started_at', 'finished_at', 'credential_values_emitted', 'client_side_read_admitted', 'kv_written', 'approval_decision_id', 'ccr_sha256', 'kv_version', 'kv_request_id', 'kv_cas', 'kv_metadata', 'error'}) self.assertFalse(any('consumer' in key for key in receipt)) if __name__ == '__main__': unittest.main()