"""RPF-WP-0064-T10 break-glass drill: offline tests with a fake OpenBao (no Docker). The fake answers the bao CLI calls the drill makes, keeps generated tokens, OTPs and nonces in memory only, and records which credential reached which path, so the tests can show the held token is used for ``lookup-self`` only. """ from __future__ import annotations import base64 import copy import json import secrets import subprocess import sys import unittest from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import test_openbao_isolated_restore as U # noqa: E402 A, V, R = U.A, U.V, U.R LABEL = "openbao-raft-20261014t153200z.snap.age" def b64share() -> str: return base64.b64encode(secrets.token_bytes(33)).decode() class FakeBao: """Just enough OpenBao for the drill steps; failure switches per test.""" def __init__(self, held_valid=True): self.held = "s." + secrets.token_urlsafe(18)[:24] self.held_valid = held_valid self.generated: str | None = None self.revoked = False self.attempt: dict | None = None self.calls: list[tuple[str, ...]] = [] self.held_paths: list[str] = [] self.generated_policies = ["root"] self.revoke_body = b"" self.cancel_works = True @staticmethod def ok(body=b""): return subprocess.CompletedProcess([], 0, body if isinstance(body, bytes) else json.dumps(body).encode(), b"") @staticmethod def code(status): return subprocess.CompletedProcess([], 2, b"", f"Error: Error making API request.\n\nCode: {status}. " "Errors:\n\n* permission denied\n".encode()) def __call__(self, args, *, credential=None, body=b"", limit=30, check=True, reason="internal_error"): verb, path = args[0], args[-2] if args[-1] == "-" else args[-1] self.calls.append((verb, path)) if credential is not None and credential == self.held: self.held_paths.append(f"{verb} {path}") done = self.answer(verb, path, credential, body) if check and done.returncode != 0: raise A.Refused(reason) return done def status(self): att = self.attempt or {} return {"started": bool(self.attempt), "progress": att.get("progress", 0), "required": 2, "complete": False, "otp": "", "nonce": "", "encoded_token": "", "otp_length": 26} def answer(self, verb, path, credential, body): if path == "auth/token/lookup-self" and verb == "read": if credential == self.held and self.held_valid: return self.ok({"data": {"policies": ["root"], "ttl": 0, "expire_time": None, "orphan": True, "id": self.held, "accessor": "Ab3" * 8}}) if credential and credential == self.generated and not self.revoked: return self.ok({"data": {"policies": self.generated_policies, "ttl": 0, "expire_time": None, "id": self.generated}}) return self.code(403) if path == "sys/generate-root/attempt" and verb == "read": return self.ok({"data": self.status()}) if path == "sys/generate-root/attempt" and verb == "write": self.attempt = {"otp": secrets.token_urlsafe(30)[:26].replace("-", "a").replace("_", "b"), "nonce": "0b0e" + secrets.token_hex(14), "progress": 0} return self.ok({"data": {**self.status(), "otp": self.attempt["otp"], "nonce": self.attempt["nonce"]}}) if path == "sys/generate-root/attempt" and verb == "delete": if self.cancel_works: self.attempt = None return self.ok(b"Success! Data deleted\n") if path == "sys/generate-root/update": payload = json.loads(body) assert payload["nonce"] == self.attempt["nonce"] self.attempt["progress"] += 1 if self.attempt["progress"] < 2: return self.ok({"data": {**self.status(), "progress": 1}}) self.generated = "s." + secrets.token_urlsafe(18)[:24] otp = self.attempt["otp"].encode() encoded = base64.b64encode(bytes(a ^ b for a, b in zip(self.generated.encode(), otp))).decode() self.attempt = None return self.ok({"data": {"complete": True, "encoded_token": encoded, "progress": 2}}) if path == "sys/auth" and credential == self.generated and not self.revoked: return self.ok({"data": {"token/": {"type": "token"}, "netkingdom/": {"type": "oidc"}}}) if path == "auth/token/revoke-self" and credential == self.generated: self.revoked = True return self.ok(self.revoke_body) return self.code(403) class DrillBase(U.Base): def drill_admission(self, **override): adm = self.admission(attempt_id="RPF-WP-0064-T10-unit-01", drill="break-glass", snapshot_label=LABEL, readback_credential=A.DRILL_READBACK) adm.update(override) return adm def session(self, fake, answers, **adm_override): queue = list(answers) self.prompts: list[str] = [] def prompt(label): self.prompts.append(label) return bytearray(queue.pop(0).encode()) adm = self.drill_admission(**adm_override) session = A.Session(admission=adm, admission_sha256=U.digest("adm"), signer={"principal": "founder@rehearsal", "fingerprint": "SHA256:" + "AbCd" * 10 + "AbC"}, anchor_is_protected=False, ciphertext=self.work / "none", receipt_path=self.work / "r", root=self.root, prompt=prompt, say=lambda _m: None, start_reaper=False, mode=A.DRILL_MODE) session.bao = fake return session def run_drill(self, fake, answers=None): answers = answers if answers is not None else [fake.held, b64share(), b64share()] session = self.session(fake, answers) session.drill_held_token() session.drill_generate_root() session.drill_minimal_action() session.drill_revoke() session.drill_cancel() return session def drill_receipt(self, session=None): """A complete ``drilled`` receipt from the adapter's own drill record plus a valid restore.""" session = session or self.run_drill(FakeBao()) base = self.receipt() rc = copy.deepcopy(session.receipt) for key in ("started_at", "finished_at", "elapsed_seconds", "image_id", "network", "restored", "snapshot"): rc[key] = base[key] rc["claim_limits"]["production_root_token_validity"]["snapshot_plaintext_sha256"] = \ rc["snapshot"]["plaintext_sha256"] rc["image"] = base["image"] rc["teardown"].update(container_removed=True, container_absent_verified=True) rc.update(result="drilled", reason="break_glass_drill_complete") return rc class DrillAdmissionTests(DrillBase): def test_valid_drill_admission(self): A.validate_admission(self.drill_admission(), A.DRILL_MODE) def test_phase_a_and_drill_admissions_are_disjoint(self): with self.assertRaises(A.Refused): A.validate_admission(self.drill_admission(), A.PHASE_A) with self.assertRaises(A.Refused): A.validate_admission(self.admission(), A.DRILL_MODE) with self.assertRaises(A.Refused): A.Session(admission=self.admission(), admission_sha256=U.digest("a"), signer={}, anchor_is_protected=False, ciphertext=self.work, receipt_path=self.work / "x", mode=A.DRILL_MODE, start_reaper=False) def test_drill_refusals(self): cases = [{"drill": "phase-a"}, {"attempt_id": "RPF-WP-0064-T07-01"}, {"snapshot_label": "latest"}, {"snapshot_label": "Openbao-20261014"}, {"snapshot_label": "openbao-raft-snapshot"}, {"readback_credential": "generate-root-from-entered-shares"}, {"phase_b_admitted": True}, {"material": "original", "max_duration_seconds": 600}] for override in cases: with self.subTest(override=override), self.assertRaises(A.Refused): A.validate_admission(self.drill_admission(**override), A.DRILL_MODE) def test_allowed_surfaces(self): self.assertIn("sys/generate-root/attempt", A.ALLOWED_READS) self.assertIn("sys/generate-root/attempt", A.ALLOWED_WRITES) self.assertIn("sys/generate-root/update", A.ALLOWED_WRITES) self.assertEqual(A.ALLOWED_DELETES, frozenset({"sys/generate-root/attempt"})) self.assertIn("auth/token/lookup-self", A.ALLOWED_READS) self.assertIn("sys/auth", A.ALLOWED_READS) self.assertFalse(any("/data/" in p for p in A.ALLOWED_READS)) self.assertEqual(A.MAX_DURATION_SECONDS, 2700) listener = json.loads(A.SERVER_CONFIG)["listener"]["tcp"] self.assertEqual(listener["address"], "127.0.0.1:8200") self.assertIs(listener["disable_unauthed_generate_root_endpoints"], False) with self.assertRaises(A.Refused): self.session(FakeBao(), []).delete("sys/unseal") class DrillStepTests(DrillBase): def test_full_drill_records_only_facts(self): fake = FakeBao() shares = [b64share(), b64share()] session = self.run_drill(fake, [fake.held, *shares]) rc = session.receipt self.assertEqual(self.prompts, ["Held initial root token", "Re-enter unseal share 1 of 2 for generate-root", "Re-enter unseal share 2 of 2 for generate-root"]) self.assertEqual(rc["held_root_token"], {"valid": True, "policies": ["root"], "ttl_zero": True, "expire_time_null": True, "orphan": True}) self.assertEqual(rc["generate_root"], {"started": True, "shares_entered": 2, "complete": True, "otp_decode_ok": True, "policies": ["root"], "minimal_action_path": "sys/auth", "minimal_action_ok": True}) self.assertEqual(rc["revocation"], {"revoke_status": 204, "post_revoke_lookup": 403}) self.assertEqual(rc["cancel_drill"], {"started_then_cancelled": True}) self.assertIsNone(session.credential) self.assertTrue(rc["teardown"]["readback_credential_revoked"]) # The held token reached lookup-self only: never revoked, never used for anything else. self.assertEqual(fake.held_paths, ["read auth/token/lookup-self"]) self.assertIn(("delete", "sys/generate-root/attempt"), fake.calls) text = json.dumps(rc) for value in (fake.held, fake.generated, *shares): self.assertNotIn(value, text) self.assertEqual(rc["claim_limits"]["production_root_token_validity"], {"as_of_snapshot": LABEL, "snapshot_plaintext_sha256": rc["snapshot"]["plaintext_sha256"]}) self.assertTrue(set(A.DRILL_CLAIMS_NOT_MADE) <= set(rc["claims_not_made"])) def test_invalid_held_token_is_a_finding_not_an_abort(self): fake = FakeBao(held_valid=False) rc = self.run_drill(fake).receipt self.assertEqual(rc["held_root_token"], {"valid": False, "policies": [], "ttl_zero": None, "expire_time_null": None, "orphan": None}) self.assertEqual(self.verify(self.drill_receipt(self.run_drill(FakeBao(held_valid=False)))), "drilled") def failing(self, fake, step, reason, answers=None): session = self.session(fake, answers if answers is not None else [fake.held, b64share(), b64share()]) steps = [session.drill_held_token, session.drill_generate_root, session.drill_minimal_action, session.drill_revoke, session.drill_cancel] with self.assertRaises(A.Refused) as ctx: for fn in steps[:step + 1]: fn() self.assertEqual(ctx.exception.reason, reason) return session def test_failures(self): self.failing(FakeBao(), 0, "held_token_refused", ["not a token!", b64share(), b64share()]) fake = FakeBao() fake.generated_policies = ["default"] self.failing(fake, 1, "generate_root_failed") self.failing(FakeBao(), 1, "generate_root_failed", [FakeBao().held, "short", b64share()]) fake = FakeBao() fake.revoke_body = b'{"data": {}}' session = self.failing(fake, 3, "revocation_failed") self.assertFalse(session.receipt["teardown"]["readback_credential_revoked"]) fake = FakeBao() fake.cancel_works = False self.failing(fake, 4, "cancel_drill_failed") def test_stale_attempt_refused(self): fake = FakeBao() fake.attempt = {"otp": "x", "nonce": "y", "progress": 1} self.failing(fake, 1, "generate_root_failed") class DrillVerifierTests(DrillBase): def test_drilled_receipt_passes(self): self.assertEqual(self.verify(self.drill_receipt()), "drilled") def test_aborted_drill_receipt_passes(self): session = self.session(FakeBao(), []) rc = self.drill_receipt(session) rc.update(result="aborted", reason="generate_root_failed") self.assertEqual(self.verify(rc), "aborted") def test_drill_and_phase_a_shapes_do_not_mix(self): rc = self.drill_receipt() rc.update(result="restored", reason="all_required_objects_match") with self.assertRaises(V.Reject): self.verify(rc) phase_a = self.receipt() phase_a.update(result="drilled", reason="break_glass_drill_complete") with self.assertRaises(V.Reject): self.verify(phase_a) def test_adversarial_fixtures(self): token = "s." + "Kq3x9ZbT7mW2pL8vR4nY6cJd" hvs = "hvs.CAESI" + "Kq3x9ZbT7mW2pL8vR4nY6cJd" * 3 accessor = "Kq3x9ZbT7mW2pL8vR4nY6cJd" otp = "Zq8T1mVb4Xw9Kc2Lp7Rn3Hs6Yd" nonce = "0b0e6c2d-9f3a-4e1b-8c7d-5a6b7c8d9e0f" encoded = base64.b64encode(bytes(range(40, 66))).decode() share = base64.b64encode(bytes(range(1, 34))).decode() hex_share = "ab01" * 17 gen = lambda **kw: (lambda r: r["generate_root"].update(**kw)) # noqa: E731 held = lambda **kw: (lambda r: r["held_root_token"].update(**kw)) # noqa: E731 cases = { "token_value_policies": held(policies=[token]), "token_in_path": lambda r: r.update(attempt_id="RPF-WP-0064-T10-" + token.replace(".", "-")), "hvs_in_label": lambda r: r["claim_limits"]["production_root_token_validity"].update(as_of_snapshot=hvs), "token_key": lambda r: r["held_root_token"].update(token=token), "root_token_key": lambda r: r.update(root_token=token), "accessor_key": held(accessor=accessor), "accessor_value": gen(minimal_action_path=accessor), "accessor_in_network": lambda r: r["network"].update(network_mode=accessor), "otp_key": gen(otp=otp), "otp_value": gen(policies=[otp]), "otp_in_last_error": lambda r: r["restored"]["leader_wait"].update(last_error=otp) if r["restored"]["leader_wait"] else r["network"].update(mounts=[otp]), "nonce_key": gen(nonce=nonce), "nonce_value": gen(minimal_action_path=nonce), "encoded_token_key": gen(encoded_token=encoded), "encoded_root_token_key": gen(encoded_root_token=encoded), "encoded_value": lambda r: r["network"].update(mounts=[encoded]), "share_key": gen(shares=[share]), "share_value": gen(policies=[share]), "share_in_cancel": lambda r: r.update(cancel_drill={"started_then_cancelled": share}), "hex_share": lambda r: r["network"].update(mounts=[hex_share]), "unseal_keys": lambda r: r.update(unseal_keys_b64=[share]), "client_token": lambda r: r["revocation"].update(client_token=token), } for name, mutate in cases.items(): with self.subTest(name): rc = self.drill_receipt() mutate(rc) with self.assertRaises(V.Reject): self.verify(rc) def test_inconsistent_drill_receipts(self): cases = { "revoke_200": (lambda r: r["revocation"].update(revoke_status=200), "drill_inconsistent"), "lookup_after_revoke_ok": (lambda r: r["revocation"].update(post_revoke_lookup=200), "drill_inconsistent"), "one_share": (lambda r: r["generate_root"].update(shares_entered=1), "drill_inconsistent"), "not_root": (lambda r: r["generate_root"].update(policies=["default"]), "drill_fields"), "no_cancel": (lambda r: r["cancel_drill"].update(started_then_cancelled=False), "drill_inconsistent"), "minimal_path": (lambda r: r["generate_root"].update(minimal_action_path="sys/mounts"), "drill_fields"), "held_invalid_with_facts": (lambda r: r["held_root_token"].update(valid=False), "drill_inconsistent"), "held_unchecked": (lambda r: r["held_root_token"].update(valid=None, policies=[], ttl_zero=None, expire_time_null=None, orphan=None), "drill_inconsistent"), "claims_missing": (lambda r: r["claims_not_made"].remove("sso_down_access_path"), "claims_not_made"), "snapshot_unnamed": (lambda r: r["claim_limits"]["production_root_token_validity"].update( as_of_snapshot="latest"), "claim_limits"), "snapshot_other_sha": (lambda r: r["claim_limits"]["production_root_token_validity"].update( snapshot_plaintext_sha256=U.digest("other")), "claim_limits"), "objects_read": (lambda r: r.update(objects=self.receipt()["objects"]), "objects"), "phase_a_attempt": (lambda r: r.update(attempt_id="RPF-WP-0064-T07-01x"), "attempt_id"), "unrevoked_teardown": (lambda r: r["teardown"].update(readback_credential_revoked=None), "drill_inconsistent"), "kv_data_read": (lambda r: r.update(kv_data_read=True), "secret_policy"), "drill_reason_on_phase_a": (lambda r: r.update(result="aborted", reason="nope"), "result_inconsistent"), } for name, (mutate, code) in cases.items(): with self.subTest(name): rc = self.drill_receipt() mutate(rc) with self.assertRaises(V.Reject) as ctx: self.verify(rc) self.assertEqual(str(ctx.exception), code) def test_cli_accepts_drilled(self): path = self.work / "drill.json" path.write_text(json.dumps(self.drill_receipt())) done = subprocess.run([sys.executable, str(R.VERIFIER), "--receipt", str(path), "--source-root", str(self.root), "--expect-result", "drilled"], capture_output=True) self.assertEqual(done.returncode, 0, done.stdout) class DrillLauncherTests(DrillBase): def test_launcher_mode_flag_refuses_before_prompt(self): launcher = R.ROOT / "scripts/openbao-isolated-restore/run-isolated-restore.sh" f = self.work / "f" f.write_text("x") out = subprocess.run(["bash", str(launcher), "--break-glass-drill", str(f), str(f), U.digest("x"), str(f), str(self.work)], capture_output=True, stdin=subprocess.DEVNULL).stdout self.assertIn(b"interactive terminal", out) self.assertIn(b"--break-glass-drill", subprocess.run(["bash", str(launcher)], capture_output=True).stdout) if __name__ == "__main__": unittest.main()