railiance-platform/tests/test_openbao_revoke_initial_root_token.py

131 lines
5.2 KiB
Python
Raw Normal View History

"""Initial root-token check/revoke on a fake OpenBao (NK-WP-0055-T16). No network, synthetic token."""
import importlib.util
import io
import json
from pathlib import Path
import tempfile
import unittest
import urllib.error
ROOT = Path(__file__).resolve().parents[1]
_spec = importlib.util.spec_from_file_location('revoke_root', ROOT / 'scripts/openbao_revoke_initial_root_token.py')
m = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(m)
TOKEN = 's.syntheticRootToken0123'
ADDR, BG = 'http://127.0.0.1:18201', 'http://127.0.0.1:18210'
DECISION = '11111111-2222-3333-4444-555555555555'
class Response(io.BytesIO):
def __init__(self, status, body):
super().__init__(json.dumps(body).encode() if body is not None else b'')
self.status = status
def __enter__(self):
return self
def __exit__(self, *a):
self.close()
class FakeBao:
def __init__(self, policies=('root',), ttl=0, orphan=True, breakglass=True, revoke_code=204):
self.valid = True
self.policies, self.ttl, self.orphan = list(policies), ttl, orphan
self.breakglass, self.revoke_code = breakglass, revoke_code
self.calls = []
def open(self, request, timeout=None):
url, method = request.full_url, request.get_method()
self.calls.append((method, url))
if url == BG + '/v1/sys/generate-root/attempt':
if not self.breakglass:
raise urllib.error.HTTPError(url, 405, 'disabled', {}, None)
return Response(200, {'started': False})
assert url.startswith(ADDR)
if request.headers.get('X-vault-token') != TOKEN or not self.valid:
raise urllib.error.HTTPError(url, 403, 'denied', {}, None)
if url.endswith('/lookup-self'):
return Response(200, {'data': {'policies': self.policies, 'ttl': self.ttl, 'orphan': self.orphan,
'expire_time': None, 'id': TOKEN}})
if url.endswith('/revoke-self'):
if self.revoke_code == 204:
self.valid = False
return Response(self.revoke_code, None)
raise AssertionError(url)
class InitialRootTokenTest(unittest.TestCase):
def setUp(self):
self.dir = tempfile.TemporaryDirectory()
self.receipt = Path(self.dir.name) / 'r.json'
def tearDown(self):
self.dir.cleanup()
def run_mode(self, mode, bao, **kw):
return m.run(mode, ADDR, BG, TOKEN, self.receipt, kw.get('decision', DECISION if mode == 'revoke' else None), bao)
def test_check_records_facts_without_revoking(self):
bao = FakeBao()
record = self.run_mode('check', bao)
self.assertEqual(record['status'], 'checked')
self.assertTrue(bao.valid)
self.assertNotIn(('POST', ADDR + '/v1/auth/token/revoke-self'), bao.calls)
text = self.receipt.read_text()
self.assertNotIn(TOKEN, text)
self.assertEqual(oct(self.receipt.stat().st_mode & 0o777), '0o600')
def test_revoke_confirms_204_then_403(self):
bao = FakeBao()
record = self.run_mode('revoke', bao)
self.assertEqual(record['status'], 'revoked')
self.assertEqual(record['revoke_status'], 204)
self.assertEqual(record['after'], {'valid': False, 'lookup_status': 403})
self.assertEqual(record['decision_id'], DECISION)
self.assertNotIn(TOKEN, self.receipt.read_text())
def test_revoke_refused_without_breakglass_path(self):
bao = FakeBao(breakglass=False)
record = self.run_mode('revoke', bao)
self.assertEqual(record['status'], 'refused_no_breakglass_path')
self.assertTrue(bao.valid)
def test_revoke_refused_for_a_non_initial_root_token(self):
for kw in ({'policies': ('default', 'platform-admin')}, {'ttl': 3600}, {'orphan': False}):
bao = FakeBao(**kw)
self.receipt.unlink(missing_ok=True)
record = self.run_mode('revoke', bao)
self.assertEqual(record['status'], 'refused_not_initial_root_token')
self.assertTrue(bao.valid)
def test_already_invalid_token_is_reported(self):
bao = FakeBao()
bao.valid = False
self.assertEqual(self.run_mode('check', bao)['status'], 'token_already_invalid')
def test_unconfirmed_revoke_is_not_reported_as_revoked(self):
self.assertEqual(self.run_mode('revoke', FakeBao(revoke_code=500))['status'], 'revoke_unconfirmed')
def test_refusals_before_any_call(self):
bao = FakeBao()
cases = [
lambda: m.run('revoke', ADDR, BG, TOKEN, self.receipt, None, bao),
lambda: m.run('revoke', ADDR, BG, TOKEN, self.receipt, 'not-a-decision', bao),
lambda: m.run('check', 'http://10.0.0.1:8200', BG, TOKEN, self.receipt, None, bao),
lambda: m.run('check', 'https://127.0.0.1:8200', BG, TOKEN, self.receipt, None, bao),
lambda: m.run('check', ADDR, BG, 'bad token\n', self.receipt, None, bao),
]
for case in cases:
with self.assertRaises(m.Refused):
case()
self.receipt.write_text('{}')
with self.assertRaises(m.Refused):
m.run('check', ADDR, BG, TOKEN, self.receipt, None, bao)
self.assertEqual(bao.calls, [])
if __name__ == '__main__':
unittest.main()