Finalize proposed CCR-2026-0033 and its attended custody child (source only).
Correct the KV path into platform/workloads/, record secrets-engine consumer coordinates (D1-D4 pending), rotation ownership, live order and rollback, and add a decision-gated custody child with fake-only tests. Nothing executed. 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
This commit is contained in:
parent
01e0576132
commit
df24a77482
9 changed files with 538 additions and 10 deletions
194
tests/test_keycape_secrets_engine_openbao_custody.py
Normal file
194
tests/test_keycape_secrets_engine_openbao_custody.py
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
"""CCR-2026-0033 custody child: synthetic values and fakes only; no OpenBao, cluster or real file."""
|
||||
import contextlib
|
||||
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:
|
||||
def __init__(self, current=None, fail_write=False):
|
||||
self.current, self.fail_write, self.calls, self.payloads = current, fail_write, [], []
|
||||
|
||||
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_METADATA:
|
||||
if self.current is None:
|
||||
return result(2, stderr=b'No value found at ' + child.KV_METADATA.encode())
|
||||
return result(stdout=json.dumps({'data': {'current_version': self.current,
|
||||
'versions': {str(self.current): {'destroyed': False, 'deletion_time': ''}}}}).encode())
|
||||
if args[0] == 'write' and args[2] == child.KV_DATA:
|
||||
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]))
|
||||
|
||||
|
||||
class CustodyChild(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
base = Path(self.tmp.name)
|
||||
os.chmod(base, 0o755)
|
||||
self.consumer = base / 'home/.secrets-engine/keycape/secrets-engine-openbao.client-secret'
|
||||
self.consumer.parent.parent.mkdir(parents=True)
|
||||
ccr = yaml.safe_load((ROOT / 'credential-change-requests/CCR-2026-0033.yaml').read_text())
|
||||
ccr['status'] = 'approved'
|
||||
ccr['openbao']['auth']['bound_claims_confirmed'] = True
|
||||
ccr['consumer_copy']['file'] = str(self.consumer)
|
||||
ccr['review']['comments'] = [
|
||||
{'reviewer': r, 'decision': 'approved', 'date': '2026-10-06',
|
||||
'comment': 'custody child approved by decision ' + DECISION} for r in child.APPROVERS]
|
||||
self.ccr = base / 'CCR-2026-0033.yaml'
|
||||
self.ccr.write_text(yaml.safe_dump(ccr, sort_keys=False))
|
||||
checker = base / 'check.py'
|
||||
checker.write_text('#!' + sys.executable + '\nimport json,os,stat,sys\np=sys.argv[sys.argv.index("--path")+1]\n'
|
||||
'ok=os.path.isfile(p) and stat.S_IMODE(os.stat(p).st_mode)==0o600\n'
|
||||
'print(json.dumps({"ok":ok}))\nsys.exit(0 if ok else 1)\n')
|
||||
checker.chmod(0o700)
|
||||
self.checker = str(checker)
|
||||
self.bao = FakeBao()
|
||||
self.patches = [patch.object(child, 'CCR_PATH', self.ccr), patch.object(child, 'CONSUMER_FILE', self.consumer),
|
||||
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), '--secrets-engine', self.checker, *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)):
|
||||
self.assertNotIn(SYNTHETIC, blob)
|
||||
self.assertEqual(oct(receipt.stat().st_mode & 0o777), oct(0o600))
|
||||
return code, json.loads(text), receipt
|
||||
|
||||
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_ccr_is_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'))
|
||||
self.assertEqual(self.bao.calls, [])
|
||||
|
||||
def test_refuses_outside_attended_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, [])
|
||||
|
||||
def test_provision_writes_kv_and_consumer_in_one_process(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']), (1, 0))
|
||||
self.assertTrue(receipt['consumer_written'] and receipt['consumer_check_ok'])
|
||||
self.assertEqual(self.bao.payloads, [{'options': {'cas': 0}, 'data': {'CLIENT_SECRET': SYNTHETIC}}])
|
||||
self.assertNotIn(['read', '-format=json', child.KV_DATA], self.bao.calls)
|
||||
self.assertEqual(self.consumer.read_text(), SYNTHETIC + '\n')
|
||||
self.assertEqual(self.consumer.stat().st_mode & 0o777, 0o600)
|
||||
self.assertEqual(self.consumer.parent.stat().st_mode & 0o777, 0o700)
|
||||
self.assertEqual(sorted(p.name for p in self.consumer.parent.iterdir()), [self.consumer.name])
|
||||
|
||||
def test_existing_custody_or_consumer_file_is_refused(self):
|
||||
self.bao.current = 1
|
||||
code, receipt, _ = self.run_child()
|
||||
self.assertEqual(receipt['error'], 'existing_custody_requires_retry_receipt_or_rotation_approval')
|
||||
self.bao.current = None
|
||||
self.consumer.parent.mkdir(mode=0o700)
|
||||
self.consumer.write_text('old\n')
|
||||
code, receipt, _ = self.run_child()
|
||||
self.assertEqual(receipt['error'], 'consumer_file_exists_rotation_needs_separate_approval')
|
||||
self.assertEqual(self.bao.payloads, [])
|
||||
|
||||
def test_shared_parent_is_refused(self):
|
||||
self.consumer.parent.mkdir(mode=0o755)
|
||||
os.chmod(self.consumer.parent, 0o755)
|
||||
code, receipt, _ = self.run_child()
|
||||
self.assertEqual(receipt['error'], 'consumer_parent_must_be_owned_0700_directory')
|
||||
self.assertEqual(self.bao.payloads, [])
|
||||
|
||||
def test_failed_kv_write_writes_no_consumer_file(self):
|
||||
self.bao.fail_write = True
|
||||
code, receipt, _ = self.run_child()
|
||||
self.assertEqual((code, receipt['kv_written'], receipt['consumer_written']), (1, False, False))
|
||||
self.assertFalse(self.consumer.exists())
|
||||
self.assertEqual(list(self.consumer.parent.iterdir()), [])
|
||||
|
||||
def test_consumer_failure_cleans_temp_and_retry_preserves_version(self):
|
||||
with patch.object(child.os, 'rename', side_effect=OSError('synthetic')):
|
||||
code, first, path = self.run_child()
|
||||
self.assertEqual((code, first['kv_written'], first['kv_version'], first['consumer_written']), (1, True, 1, False))
|
||||
self.assertTrue(first['consumer_temp_removed'])
|
||||
self.assertEqual(list(self.consumer.parent.iterdir()), [])
|
||||
code, receipt, _ = self.run_child()
|
||||
self.assertEqual(receipt['error'], 'existing_custody_requires_retry_receipt_or_rotation_approval')
|
||||
code, second, _ = self.run_child('--prior-receipt', str(path))
|
||||
self.assertEqual((code, second['kv_version'], second['kv_cas'], second['retry_of_kv_version']), (0, 2, 1, 1))
|
||||
self.assertEqual([p['options']['cas'] for p in self.bao.payloads], [0, 1])
|
||||
self.bao.current = 3
|
||||
Path(self.consumer).unlink()
|
||||
code, third, _ = self.run_child('--prior-receipt', str(path))
|
||||
self.assertEqual(third['error'], 'kv_version_changed_since_prior_receipt')
|
||||
|
||||
def test_failed_consumer_check_fails_receipt(self):
|
||||
bad = Path(self.tmp.name) / 'bad.py'
|
||||
bad.write_text('#!' + sys.executable + '\nimport sys\nprint(\'{"ok": false}\')\nsys.exit(1)\n')
|
||||
bad.chmod(0o700)
|
||||
self.checker = str(bad)
|
||||
code, receipt, _ = self.run_child()
|
||||
self.assertEqual((code, receipt['consumer_written'], receipt['consumer_check_ok'], receipt['error']),
|
||||
(1, True, False, 'consumer_check_failed'))
|
||||
|
||||
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', 'consumer_written', 'approval_decision_id', 'ccr_sha256',
|
||||
'kv_version', 'kv_request_id', 'kv_cas', 'consumer_check_ok', 'retry_of_kv_version',
|
||||
'consumer_temp_removed', 'error'})
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue