"""Offline tests for the operators/nextcloud retirement helper against a fake ``bao``.""" import json import os from pathlib import Path import subprocess import sys import tempfile import textwrap import unittest ROOT = Path(__file__).resolve().parents[1] SCRIPT = ROOT / 'scripts/operators_entry_retire.py' CONFIRM = 'RETIRE operators/nextcloud' ONE_SHOT = 'hvs.oneshot-' + 'K3l4' * 6 # KV calls are refused unless they carry the one-shot token and its policy exists. FAKE_BAO = textwrap.dedent(f'''\ #!/usr/bin/env python3 import json, os, sys sf = os.environ['FAKE_BAO_STATE'] st = json.load(open(sf)) a = sys.argv[1:] tok = os.environ.get('BAO_TOKEN', 'caller') st['calls'].append({{'args': a, 'token': tok}}) def save(): json.dump(st, open(sf, 'w')) def out(obj): save(); print(json.dumps(obj)); sys.exit(0) def deny(): save(); sys.stderr.write('permission denied\\n'); sys.exit(2) if a[:2] == ['token', 'lookup']: out({{'data': {{'policies': st['caller_policies']}}}}) if a[:2] == ['policy', 'write']: st['policy'] = open(a[3]).read(); save(); sys.exit(0) if a[:2] == ['policy', 'delete']: st.pop('policy', None); save(); sys.exit(0) if a[:2] == ['token', 'create']: if st.get('fail_create'): save(); sys.exit(2) out({{'auth': {{'client_token': '{ONE_SHOT}'}}}}) if a[:2] == ['token', 'revoke']: st['revoked'] = tok; save(); sys.exit(0) if tok != '{ONE_SHOT}' or 'policy' not in st: deny() if a[0] == 'read': rest = a[-1].split('/', 2)[2] if rest not in st['kv']: save(); sys.stderr.write('No value found\\n'); sys.exit(2) n = st['kv'][rest] out({{'data': {{'current_version': n, 'versions': {{str(i + 1): {{}} for i in range(n)}}}}}}) if a[0] == 'delete': if st.get('fail_delete'): deny() st['kv'].pop(a[1].split('/', 2)[2], None); save(); sys.exit(0) save(); sys.exit(9) ''') class Retire(unittest.TestCase): def setUp(self): self.tmp = Path(tempfile.mkdtemp()) (self.tmp / 'bin').mkdir() (self.tmp / 'bin/bao').write_text(FAKE_BAO) (self.tmp / 'bin/bao').chmod(0o755) self.home = self.tmp / '.warden-attended-login' / 'session' self.home.mkdir(parents=True) self.state = {'calls': [], 'caller_policies': ['default', 'platform-admin'], 'kv': {'nextcloud': 2, 'nextcloud/backup': 3}} def run_helper(self, *extra, confirm=CONFIRM): (self.tmp / 'state.json').write_text(json.dumps(self.state)) receipt = self.tmp / f'r{len(list(self.tmp.glob("r*.json")))}.json' env = {k: v for k, v in os.environ.items() if k not in ('BAO_TOKEN', 'VAULT_TOKEN')} env.update(PATH=f'{self.tmp / "bin"}:{env["PATH"]}', HOME=str(self.home), FAKE_BAO_STATE=str(self.tmp / 'state.json')) proc = subprocess.run([sys.executable, str(SCRIPT), '--confirm', confirm, '--receipt', str(receipt), *extra], env=env, capture_output=True, text=True) self.assertEqual((proc.stdout, proc.stderr), ('', '')) self.state = json.loads((self.tmp / 'state.json').read_text()) text = receipt.read_text() self.assertNotIn(ONE_SHOT, text) return proc.returncode, json.loads(text) def assert_cleaned_up(self, receipt): self.assertNotIn('policy', self.state) self.assertTrue(receipt['policy_deleted']) self.assertEqual(self.state.get('revoked'), ONE_SHOT) def test_retires_entry_and_leaves_backup_untouched(self): code, receipt = self.run_helper() self.assertEqual((code, receipt['outcome']), (0, 'retired')) self.assertNotIn('nextcloud', self.state['kv']) self.assertEqual(self.state['kv']['nextcloud/backup'], 3) self.assertEqual(receipt['entry_before'], {'current_version': 2, 'versions': 2}) self.assert_cleaned_up(receipt) self.assertFalse(any(c['args'][0] == 'read' and '/data/' in c['args'][-1] for c in self.state['calls'])) def test_one_shot_policy_is_exact(self): self.run_helper('--dry-run') calls = [c for c in self.state['calls'] if c['args'][:2] == ['token', 'create']] self.assertEqual(len(calls), 1) args = calls[0]['args'] for flag in ('-orphan', '-policy=operators-retire-nextcloud', '-no-default-policy', '-ttl=60s'): self.assertIn(flag, args) def test_dry_run_deletes_nothing_and_cleans_up(self): code, receipt = self.run_helper('--dry-run') self.assertEqual((code, receipt['outcome']), (0, 'dry_run_would_retire')) self.assertIn('nextcloud', self.state['kv']) self.assert_cleaned_up(receipt) def test_rerun_reports_already_absent(self): self.state['kv'].pop('nextcloud') code, receipt = self.run_helper() self.assertEqual((code, receipt['outcome']), (0, 'already_absent')) self.assert_cleaned_up(receipt) def test_missing_sibling_refuses(self): self.state['kv'].pop('nextcloud/backup') code, receipt = self.run_helper() self.assertEqual((code, receipt['outcome']), (2, 'sibling_absent_refusing_to_guess_layout')) self.assertIn('nextcloud', self.state['kv']) self.assert_cleaned_up(receipt) def test_delete_failure_still_cleans_up(self): self.state['fail_delete'] = True code, receipt = self.run_helper() self.assertEqual((code, receipt['outcome']), (4, 'delete_failed')) self.assert_cleaned_up(receipt) def test_refuses_without_confirmation_or_platform_admin(self): code, receipt = self.run_helper(confirm='RETIRE') self.assertEqual((code, receipt['outcome']), (2, 'exact_confirmation_required')) self.state['caller_policies'] = ['operator-custody'] code, receipt = self.run_helper() self.assertEqual((code, receipt['outcome']), (2, 'attended_platform_admin_required')) self.assertFalse(any(c['args'][:2] == ['policy', 'write'] for c in self.state['calls'])) if __name__ == '__main__': unittest.main()