net-kingdom/sso-mfa/k8s/keycape/test_secrets_engine_openbao_client_rollout.py
tegwick 1fbb8dbdf6
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 2s
Register secrets-engine-openbao live and fold its env binding in (NK-WP-0046-T03)
Founder live go caafe4be. Config resourceVersion 71471711 -> 71655453 with only
this client added; KeyCape restarted on the kept image 6f79a2af.

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-07 02:20:08 +02:00

214 lines
9.9 KiB
Python

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()