railiance-platform/tests/test_keycape_secrets_engine_openbao_custody.py
codex 4a284f0a3b
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Revise KeyCape secrets-engine-openbao custody: OpenBao-only, operator reader CCR-2026-0034 (source only)
Founder direction 2026-10-06 and ops-mason plan keycape-secrets-engine-openbao-client-custody
(658cdbc, MASON-WP-0009): the client secret lives only in OpenBao.

- CCR-2026-0033: consumer file and secrets-engine D1-D4 removed; KV metadata declared
  (cas_required, max_versions=2, delete_version_after=0s, custom_metadata); target.domain
  infotech; live order and roll-forward rollback revised; status stays proposed.
- New sibling CCR-2026-0034 + policy: attended OIDC operator reader on auth/netkingdom,
  read data path + revoke-self only, 5m/4 uses, no default policy, bound
  groups=net-kingdom-admins and /assurance/level=aal2 (KeyCape 7d96ee4). Proposed,
  bound claims unconfirmed (founder item for both readers).
- credential-change.py: OIDC roles carry token_max_ttl/explicit_max_ttl/no_default_policy/
  num_uses/type and bound_claims_type (string only); token_revoke_self policy option;
  validated openbao.kv_metadata. Tests added.
- Custody child: consumer write and check-secret-file removed; cas=0 create / --rotate
  cas=<current>; compare-before-write metadata put; refusals unchanged.
- Founder runbook (keycape/founder-runbook.md), window docs, RPF-WP-0035-T09 (waits on A1/A2/A3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
2026-10-06 14:26:42 +02:00

208 lines
10 KiB
Python

"""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 test_committed_proposed_ccrs_are_refused(self):
with patch.object(child, 'CCR_PATH', ROOT / 'credential-change-requests/CCR-2026-0033.yaml'):
code, receipt = self.run_child()
self.assertEqual((code, receipt['error']), (1, 'approved_ccr_required'))
with patch.object(child, 'SIBLING_PATH', ROOT / 'credential-change-requests/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()