import base64 import contextlib import copy import importlib.util import io import json from pathlib import Path import unittest import yaml spec = importlib.util.spec_from_file_location( 'se_openbao_rollout', Path(__file__).with_name('secrets-engine-openbao-client-rollout.py')) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) UID = '00000000-0000-4000-8000-000000000001' KEYCAPE_EXAMPLE = Path('/home/worsch/key-cape/config/service-clients.example.yaml') EXISTING = [ {'clientId': 'openbao-admin', 'clientType': 'public', 'redirectUris': ['http://localhost:8250/oidc/callback']}, {'clientId': 'secrets-engine-approval', 'audience': 'approval-engine', 'grantTypes': ['client_credentials'], 'clientType': 'confidential', 'secretRef': 'env:KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET', 'serviceSubject': 'service:secrets-engine', 'tenant': 'tenant:platform', 'roles': ['secrets-engine'], 'tokenLifetime': '15m'}, ] def fixture(clients=None, tail='tokenLifetime: 15m\npolicyPath: /var/lib/keycape-policy/policy.yaml\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-marker\n' 'clients:\n' + yaml.safe_dump(json.loads(json.dumps(EXISTING if clients is None else clients)), sort_keys=False) + tail) secret = {'metadata': {'uid': UID, 'resourceVersion': '7'}, 'data': {'config.yaml': base64.b64encode(raw.encode()).decode(), 'signing-key.pem': 'c3ludGhldGljLXNpZ25pbmcta2V5'}} return secret, raw def decode(encoded): return base64.b64decode(encoded).decode() class RegistrationTests(unittest.TestCase): def test_add_appends_only_this_client_and_preserves_bytes(self): secret, raw = fixture() before = copy.deepcopy(secret) encoded, changed = module.replacement(secret) updated = decode(encoded) self.assertTrue(changed) head = raw[:raw.index('tokenLifetime:')] self.assertTrue(updated.startswith(head)) self.assertTrue(updated.endswith(raw[len(head):])) expected = yaml.safe_load(raw) expected['clients'].append(module.CLIENT) self.assertEqual(yaml.safe_load(updated), expected) self.assertEqual(secret, before) # input, including the signing key, untouched def test_add_at_end_of_document(self): secret, raw = fixture(tail='') updated = decode(module.replacement(secret)[0]) self.assertTrue(updated.startswith(raw)) self.assertEqual(yaml.safe_load(updated)['clients'][-1], module.CLIENT) def test_identical_registration_is_noop(self): secret, _ = fixture() secret['data']['config.yaml'] = module.replacement(secret)[0] self.assertEqual(module.replacement(secret), (secret['data']['config.yaml'], False)) def test_drift_fails_closed(self): for change in [dict(allowedScopes=['openbao:login', 'openbao:admin']), dict(audience='openbao'), dict(tokenLifetime='1h'), dict(roles=['secrets-engine', 'platform-admin']), dict(tenant='tenant:other'), dict(clientType='public'), dict(serviceSubject='service:other')]: with self.subTest(change=change), self.assertRaises(module.Refused): module.replacement(fixture(EXISTING + [dict(module.CLIENT, **change)])[0]) def test_duplicates_fail_closed(self): for clients in [EXISTING + [module.CLIENT, module.CLIENT], EXISTING + [EXISTING[0]]]: with self.assertRaises(module.Refused): module.replacement(fixture(clients)[0]) with self.assertRaises(module.Refused): module.removal(fixture(clients)[0]) def test_secret_ref_reused_by_another_client_fails_closed(self): other = dict(EXISTING[1], clientId='other', secretRef=module.CLIENT['secretRef']) with self.assertRaises(module.Refused): module.replacement(fixture(EXISTING + [other])[0]) def test_unverified_upstream_issuer_refused(self): secret, raw = fixture() raw = raw.replace('https://auth.coulomb.social', 'https://auth.example.invalid') secret['data']['config.yaml'] = base64.b64encode(raw.encode()).decode() with self.assertRaises(module.Refused): module.replacement(secret) def test_rollback_removes_only_this_client_and_restores_exact_bytes(self): for tail in ['tokenLifetime: 15m\n', '']: secret, raw = fixture(tail=tail) secret['data']['config.yaml'] = module.replacement(secret)[0] encoded, changed = module.removal(secret) self.assertTrue(changed) self.assertEqual(decode(encoded), raw) def test_rollback_of_middle_entry_preserves_neighbours(self): secret, raw = fixture(EXISTING[:1] + [module.CLIENT] + EXISTING[1:]) updated = decode(module.removal(secret)[0]) self.assertEqual(updated, fixture()[1]) def test_rollback_absent_is_noop_and_drift_refused(self): secret, _ = fixture() self.assertEqual(module.removal(secret), (secret['data']['config.yaml'], False)) with self.assertRaises(module.Refused): module.removal(fixture(EXISTING + [dict(module.CLIENT, tokenLifetime='1h')])[0]) def test_registration_matches_keycape_source(self): if not KEYCAPE_EXAMPLE.is_file(): self.skipTest('key-cape checkout absent') source = yaml.safe_load(KEYCAPE_EXAMPLE.read_text())['clients'] self.assertEqual([c for c in source if c['clientId'] == module.CLIENT_ID], [module.CLIENT]) class DeploymentBindingTests(unittest.TestCase): def deployment(self, env): return {'spec': {'template': {'spec': {'containers': [{'name': 'keycape', 'env': env}]}}}} def test_proposed_patch_carries_exactly_the_one_binding(self): patch = yaml.safe_load(Path(__file__).with_name( 'secrets-engine-openbao-client.proposed.patch.yaml').read_text()) container = patch['spec']['template']['spec']['containers'][0] self.assertEqual(container, {'name': 'keycape', 'env': [module.ENV_BINDING]}) def test_deployment_yaml_binds_exactly_once_after_live_go(self): # Folded in under live go caafe4be (2026-10-07). docs = yaml.safe_load_all(Path(__file__).with_name('deployment.yaml').read_text()) dep = next(d for d in docs if d and d.get('kind') == 'Deployment') self.assertTrue(module.deployment_binding(dep)) env = dep['spec']['template']['spec']['containers'][0]['env'] self.assertEqual([e['name'] for e in env].count(module.ENV_NAME), 1) def test_binding_detection_and_drift(self): self.assertTrue(module.deployment_binding(self.deployment([module.ENV_BINDING]))) wrong = {'name': module.ENV_NAME, 'value': 'synthetic'} with self.assertRaises(module.Refused): module.deployment_binding(self.deployment([wrong])) class RunModeTests(unittest.TestCase): """Drive run() against a fake kubectl; nothing reaches a cluster.""" def setUp(self): self.secret, _ = fixture() self.calls = [] self.original = module.lane.kubectl self.env = [] def fake(arguments, payload=None): self.calls.append((arguments, payload)) if arguments[:2] == ['get', 'namespace']: return {'metadata': {'uid': module.lane.CLUSTER_UID}} if 'deployment' in arguments: return {'spec': {'template': {'spec': {'containers': [{'name': 'keycape', 'env': self.env}]}}}} if 'patch' in arguments: return {'data': dict(self.secret['data'], **{'config.yaml': payload[-1]['value']})} return copy.deepcopy(self.secret) module.lane.kubectl = fake def tearDown(self): module.lane.kubectl = self.original def main(self, *argv): out = io.StringIO() with contextlib.redirect_stdout(out): code = module.main(list(argv)) return code, json.loads(out.getvalue()) def test_default_is_inspect_and_never_patches(self): code, receipt = self.main() self.assertEqual(code, 0) self.assertEqual(receipt['mode'], 'inspect') self.assertTrue(receipt['change_needed']) self.assertFalse(any('patch' in a for a, _ in self.calls)) self.assertNotIn('synthetic', json.dumps(receipt)) def test_dry_run_is_server_side(self): code, _ = self.main('dry-run', '--expected-uid', UID, '--expected-resource-version', '7') self.assertEqual(code, 0) patches = [a for a, _ in self.calls if 'patch' in a] self.assertEqual(len(patches), 1) self.assertIn('--dry-run=server', patches[0]) def test_apply_requires_live_go_and_env_binding(self): base = ['apply', '--expected-uid', UID, '--expected-resource-version', '7'] self.assertEqual(self.main(*base), (1, {'status': 'refused', 'reason': 'founder_live_go_required'})) self.assertEqual(self.main(*base, '--live-go-decision', module.LIVE_GO_DECISION), (1, {'status': 'refused', 'reason': 'deployment_env_binding_required_first'})) self.assertFalse(any('patch' in a for a, _ in self.calls)) def test_stale_revision_refused_before_patch(self): code, receipt = self.main('dry-run', '--expected-uid', UID, '--expected-resource-version', '6') self.assertEqual(receipt, {'status': 'refused', 'reason': 'observed_revision_changed'}) self.assertFalse(any('patch' in a for a, _ in self.calls)) def test_unexpected_errors_are_contained(self): self.secret['data']['config.yaml'] = 'not base64 !!' self.assertEqual(self.main(), (1, {'status': 'failed', 'reason': 'contained_operation_failed'})) if __name__ == '__main__': unittest.main()