"""`src/dashboards/certs.py` — the dashboard's CA, its client cert, and every agent's, tested. The lifecycle here is the part of phase 15 that cannot be checked in a browser: a root CA that regenerates on restart silently untrusts every installed agent bundle; a bundle carrying the root *key* turns one stolen agent install into a forged fleet; a revocation that lands on one host's bundle but not the others is a revocation that did not happen. So this file is mostly claims about **stored bytes**, and the store underneath them is a dict. That is the `tests/test_auth_bootstrap.py` idiom, and it is why `certs.py` talks to a three-method `MetaStore` protocol: the security-relevant branch has no SQLite in it, so the decisions are asserted directly. Three tests at the end put something real underneath instead — the real `SampleStore` for durability, a real `ssl` handshake for usability, and the real `create_app()` lifespan for the fail-closed startup — because "it parses" is not the same claim as "an agent accepts it". Nothing sleeps and nothing reads the wall clock: `now` is an argument everywhere, so a 10-year root and a 1-year leaf are asserted as numbers. """ from __future__ import annotations import ast import json import socket import ssl import tempfile import threading from pathlib import Path from typing import Any import pytest from cryptography import x509 from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey from cryptography.hazmat.primitives.asymmetric.types import PublicKeyTypes from cryptography.hazmat.primitives.serialization import ( Encoding, NoEncryption, PrivateFormat, PublicFormat, load_pem_private_key, ) from cryptography.x509.oid import NameOID from fastapi.testclient import TestClient from auth_contract import ADMIN_PASSWORD, FAST_PBKDF2 from dashboards import auth, certs from dashboards.app import create_app from dashboards.config import Settings from dashboards.store import SampleStore CERTS_SOURCE = Path(__file__).resolve().parents[1] / "src" / "dashboards" / "certs.py" #: One fixed instant for every lifetime assertion. `certs.py` never reads a clock, so an expiry is #: this number plus the documented lifetime — no sleeps, no flaky window. NOW = 1_700_000_000.0 CLIENT_CERT_FILE = "client-cert.pem" CLIENT_KEY_FILE = "client-key.pem" CA_FILE = "root-ca.pem" class FakeMeta: """The three-method shape `certs.py` asks for, with no database in it. Exactly how the auth bootstrap is tested: the lifecycle is a function over a key/value store, so the store is a dict and the assertions are about the rows. """ def __init__(self, rows: dict[str, str] | None = None) -> None: self.rows: dict[str, str] = dict(rows or {}) def get_meta(self, key: str) -> str | None: return self.rows.get(key) def set_meta(self, key: str, value: str) -> None: self.rows[key] = value def delete_meta(self, key: str) -> int: return 1 if self.rows.pop(key, None) is not None else 0 def boot(*, now: float = NOW, rows: dict[str, str] | None = None ) -> tuple[FakeMeta, certs.CertsState]: """A store and the state one process start produced against it.""" store = FakeMeta(rows) return store, certs.ensure_certs(store, now=now) def issue(store: FakeMeta, state: certs.CertsState, name: str = "deskwork", san: list[str] | None = None) -> certs.CertsState: return certs.issue_agent_cert(store, state, name, san or [], now=NOW) def rotate(store: FakeMeta, state: certs.CertsState, name: str = "deskwork", san: list[str] | None = None) -> certs.CertsState: return certs.reissue_agent_cert(store, state, name, san or [], now=NOW) def revoke(store: FakeMeta, state: certs.CertsState, name: str) -> certs.CertsState: return certs.revoke(store, state, name, now=NOW) def root_of(state: certs.CertsState) -> x509.Certificate: return cert_of(state.root_cert) # --- reading the artifacts back, independently of the module -------------------------------- # def blocks(text: str) -> list[str]: """Every PEM block in `text`, in order. Reimplemented with a split rather than by calling the module's parser: a test that reads a bundle with the code that wrote it cannot notice the format changing, and task 02's agent has to parse it with its own stdlib code anyway. """ out: list[str] = [] for part in text.split("-----BEGIN ")[1:]: label, _, rest = part.partition("-----\n") body, _, _tail = rest.partition("-----END ") out.append(f"-----BEGIN {label}-----\n{body}-----END {label}-----\n") return out def cert_of(text: str) -> x509.Certificate: return x509.load_pem_x509_certificate(text.encode("ascii")) def key_of(text: str) -> EllipticCurvePrivateKey: key = load_pem_private_key(text.encode("ascii"), password=None) assert isinstance(key, EllipticCurvePrivateKey), "this module issues P-256 keys" return key def public_id(public_key: PublicKeyTypes) -> bytes: """A public key's identity as its SPKI encoding. Compared instead of `public_numbers()` because that attribute exists per algorithm and `certificate.public_key()` is typed as the union of all of them; the encoding is the same claim ("same key") and needs no cast. """ return public_key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo) def entry_of(state: certs.CertsState, name: str) -> certs.AgentCert: """The entry for one host, asserted present — a missing row is the failure, not a `None`.""" entry = state.agent(name) assert entry is not None, f"no entry stored for {name}" return entry def bundle_pems(bundle: str) -> dict[str, str]: """A bundle as `{cert, key, root}` — the agent's view of the same three blocks.""" found = blocks(bundle) certs_found = [b for b in found if b.startswith("-----BEGIN CERTIFICATE")] keys = [b for b in found if not b.startswith("-----BEGIN CERTIFICATE")] assert len(certs_found) == 2 and len(keys) == 1, f"unexpected bundle shape: {found}" return {"cert": certs_found[0], "key": keys[0], "root": certs_found[1]} def revoked_lines(bundle: str) -> list[int]: """The `# REVOKED` denylist, as serials, in the order the bundle carries them.""" return [int(line.removeprefix(certs.REVOKED_LINE)) for line in bundle.splitlines() if line.startswith(certs.REVOKED_LINE)] def rev_of(bundle: str) -> int: lines = [line for line in bundle.splitlines() if line.startswith(certs.BUNDLE_REV_LINE)] assert len(lines) == 1, "exactly one revision line" return int(lines[0].removeprefix(certs.BUNDLE_REV_LINE)) def san_names(cert: x509.Certificate) -> list[str]: return [str(item.value) for item in cert.extensions.get_extension_for_class(x509.SubjectAlternativeName).value] def eku_of(cert: x509.Certificate) -> list[str]: extension = cert.extensions.get_extension_for_class(x509.ExtendedKeyUsage).value return [str(item.dotted_string) for item in extension] def cn(cert: x509.Certificate) -> str: return str(cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME)[0].value) # --- the module's own hygiene ---------------------------------------------------------------- # def test_the_module_owns_five_meta_keys_and_names_them_all_in_the_reset() -> None: """The rows an operator has to be able to type, and the message that types them for them.""" assert certs.META_KEYS == ("certs.root.key", "certs.root.cert", "certs.client.key", "certs.client.cert", "certs.agents") for key in certs.META_KEYS: assert key in certs.CORRUPT_STORE_MESSAGE assert "DELETE FROM meta" in certs.CORRUPT_STORE_MESSAGE assert "sqlite3" in certs.CORRUPT_STORE_MESSAGE def _tree() -> ast.Module: return ast.parse(CERTS_SOURCE.read_text()) def test_the_module_imports_nothing_from_the_package_and_nothing_logs() -> None: """The `auth.py` rule: standalone, testable with a dict, with no path from a key to a log line. A private key is a credential the same way a password is, and this app turns log lines and exception messages into things a browser can read (`AGENTS.md` invariant 6). Keeping `logging` out of the file entirely is the version of that rule a test can actually hold. """ names: set[str] = set() for node in ast.walk(_tree()): if isinstance(node, ast.Import): names.update(alias.name.split(".")[0] for alias in node.names) elif isinstance(node, ast.ImportFrom): assert node.level == 0, "no relative import: this module imports nothing in the package" if node.module: names.add(node.module.split(".")[0]) assert "logging" not in names assert "cryptography" in names, "the one third-party dependency this phase adds" def test_the_only_raise_is_the_fixed_text_refusal() -> None: """Every raise site is `RuntimeError(CORRUPT_STORE_MESSAGE)`: no f-string, no interpolated value. The row that failed to parse may be a private key, and a message quoting even a prefix of it would go into the startup output and wherever the app prints failures. Enumerating the raise sites is what makes the *next* one somebody adds a test failure instead of a leak. """ raised = [node for node in ast.walk(_tree()) if isinstance(node, ast.Raise)] assert raised, "the fail-closed refusal exists" for node in raised: assert isinstance(node.exc, ast.Call) and isinstance(node.exc.func, ast.Name) assert node.exc.func.id == "RuntimeError" assert len(node.exc.args) == 1 message = node.exc.args[0] assert isinstance(message, ast.Name) and message.id == "CORRUPT_STORE_MESSAGE", ( "the refusal is the module-level literal, never a built string") assert node.cause is None or isinstance(node.cause, ast.Name), "no interpolated context" assert not any(isinstance(child, ast.JoinedStr) for child in ast.walk(node)), ( "a raise may not interpolate anything") # ...and the literal behind that name is a literal, not something assembled at import time. assigned = [stmt for stmt in _tree().body if isinstance(stmt, ast.Assign) and any(isinstance(t, ast.Name) and t.id == "CORRUPT_STORE_MESSAGE" for t in stmt.targets)] assert len(assigned) == 1 and isinstance(assigned[0].value, ast.Constant) assert isinstance(assigned[0].value.value, str) # --- first boot: generate once, then never again ---------------------------------------------- # def test_the_first_boot_persists_exactly_the_five_cert_rows() -> None: store, state = boot() assert sorted(store.rows) == sorted(certs.META_KEYS) assert state.root_cert == store.rows[certs.META_ROOT_CERT] assert state.client_key == store.rows[certs.META_CLIENT_KEY] assert state.agents == () assert json.loads(store.rows[certs.META_AGENTS]) == {}, "the empty fleet is a row too" # PEM text, not DER and not a path: `sqlite3 … SELECT value` shows a certificate. assert store.rows[certs.META_ROOT_CERT].startswith("-----BEGIN CERTIFICATE-----") assert store.rows[certs.META_CLIENT_KEY].startswith("-----BEGIN PRIVATE KEY-----") def test_a_second_boot_changes_no_byte_of_any_stored_certificate() -> None: """The completion criterion for the task, and the one that makes a redeploy survivable. A root CA regenerated per start would untrust every agent bundle in the fleet, so this is a read that only fills gaps. The later `now` is part of the test: regenerating would also move the expiry, and a state that merely *looks* current is worse than one that refuses. """ store, first = boot() before = dict(store.rows) second = certs.ensure_certs(store, now=NOW + 3_600) assert store.rows == before, "no row moved, including the root key" assert (second.root_cert, second.root_key) == (first.root_cert, first.root_key) assert (second.client_cert, second.client_key) == (first.client_cert, first.client_key) assert (second.root_serial, second.client_serial) == (first.root_serial, first.client_serial) assert second.root_not_after == first.root_not_after def test_a_missing_client_pair_is_generated_under_the_stored_root() -> None: """A crash between two `set_meta` calls leaves half a credential; the fix is the other half.""" store, first = boot() del store.rows[certs.META_CLIENT_KEY] del store.rows[certs.META_CLIENT_CERT] second = certs.ensure_certs(store, now=NOW) assert second.root_cert == first.root_cert, "the root is never regenerated to match a leaf" assert second.client_cert != first.client_cert cert_of(second.client_cert).verify_directly_issued_by(root_of(second)) def test_a_new_root_takes_the_client_certificate_with_it() -> None: """Keeping a leaf signed by a dead root would hand the process a credential nothing trusts.""" store, first = boot() del store.rows[certs.META_ROOT_KEY] del store.rows[certs.META_ROOT_CERT] second = certs.ensure_certs(store, now=NOW) assert second.root_serial != first.root_serial assert second.client_cert != first.client_cert cert_of(second.client_cert).verify_directly_issued_by(root_of(second)) with pytest.raises(InvalidSignature): cert_of(first.client_cert).verify_directly_issued_by(root_of(second)) def test_a_row_of_whitespace_is_an_absent_row() -> None: store, state = boot(rows={certs.META_ROOT_CERT: " \n "}) assert state.root_cert.startswith("-----BEGIN CERTIFICATE-----") assert cert_of(state.root_cert).serial_number == state.root_serial assert store.rows[certs.META_ROOT_CERT] == state.root_cert # --- the shapes ------------------------------------------------------------------------------- # def test_the_root_is_self_signed_and_can_only_sign() -> None: _store, state = boot() root = root_of(state) assert root.subject == root.issuer assert cn(root) == certs.ROOT_CN assert root.extensions.get_extension_for_class(x509.BasicConstraints).value.ca is True usage = root.extensions.get_extension_for_class(x509.KeyUsage).value assert usage.key_cert_sign is True and usage.crl_sign is True assert usage.digital_signature is False, "a CA that signs data is a CA being used as a leaf" assert root.not_valid_after_utc.timestamp() == NOW + certs.ROOT_LIFETIME.total_seconds() assert certs.ROOT_LIFETIME.days == 3650 and certs.LEAF_LIFETIME.days == 365 assert state.root_not_after == root.not_valid_after_utc.timestamp() def test_every_certificate_carries_the_identifiers_a_strict_verifier_requires() -> None: """AKI and SKI are not decoration: OpenSSL refuses a chain without them in strict mode. Python's `ssl.create_default_context()` (which is what the dashboard's own https dial builds, and what `curl --cacert`-style clients reach for) turns `X509_STRICT` on, and a strict verification of a leaf with no AuthorityKeyIdentifier fails with `CERTIFICATE_VERIFY_FAILED: Missing Authority Key Identifier` — every TLS agent would read red, with an error that names neither the extension nor the module that left it out. The agent's own server-side check does not use strict mode, which is why the TLS tests pass without this and why it is pinned here instead. """ store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) entry = state.agent("deskwork") assert entry is not None root = root_of(state) root_ski = root.extensions.get_extension_for_class(x509.SubjectKeyIdentifier).value assert root_ski.digest, "a CA whose leaves cannot name it cannot be chained to strictly" for label, cert in (("client", cert_of(state.client_cert)), ("agent", cert_of(bundle_pems(entry.bundle)["cert"]))): identifier = cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) authority = cert.extensions.get_extension_for_class(x509.AuthorityKeyIdentifier) assert authority.value.key_identifier == root_ski.digest, f"{label}: AKI must name our root" assert not identifier.critical and not authority.critical, ( "RFC 5280: the identifiers are non-critical, so a verifier that ignores them " "still validates the signature") # and a strict client context accepts what that produced, which is the claim the dial makes context = ssl.create_default_context(cadata=state.root_cert) assert context.verify_flags & ssl.VERIFY_X509_STRICT, "this is the mode being satisfied" def test_the_client_certificate_chains_to_the_root_and_is_a_client_identity() -> None: _store, state = boot() client = cert_of(state.client_cert) assert client.verify_directly_issued_by(root_of(state)) is None assert cn(client) == certs.CLIENT_CN assert eku_of(client) == [x509.oid.ExtendedKeyUsageOID.CLIENT_AUTH.dotted_string], ( "an agent verifies a client certificate against the clientAuth purpose") assert client.extensions.get_extension_for_class(x509.BasicConstraints).value.ca is False assert client.not_valid_after_utc.timestamp() == NOW + certs.LEAF_LIFETIME.total_seconds() assert san_names(client) == [certs.CLIENT_CN] assert state.client_serial == client.serial_number assert state.client_not_after == client.not_valid_after_utc.timestamp() def test_an_agent_certificate_chains_to_the_root_names_the_host_and_holds_the_san() -> None: store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51", "deskwork.reeselink.com"]) entry = state.agent("deskwork") assert entry is not None agent = cert_of(bundle_pems(entry.bundle)["cert"]) agent.verify_directly_issued_by(root_of(state)) assert cn(agent) == "deskwork" assert san_names(agent) == ["10.1.0.51", "deskwork.reeselink.com", "deskwork"] assert eku_of(agent) == [x509.oid.ExtendedKeyUsageOID.SERVER_AUTH.dotted_string] assert agent.serial_number == entry.serial assert agent.not_valid_after_utc.timestamp() == entry.not_after == ( NOW + certs.LEAF_LIFETIME.total_seconds()) # The key in the bundle is the key the certificate was issued for. An agent that loads a # mismatched pair fails the handshake with an error about nothing it can see. assert public_id(key_of(bundle_pems(entry.bundle)["key"]).public_key()) == public_id(agent.public_key()) def test_the_san_holds_ip_addresses_as_ips_and_names_as_dns() -> None: """`10.1.0.126` has to be an iPAddress SAN or hostname verification of an IP dial fails.""" store, state = boot() state = issue(store, state, "gateway", ["10.1.1.27"]) entry = state.agent("gateway") assert entry is not None general = cert_of(bundle_pems(entry.bundle)["cert"]).extensions.get_extension_for_class( x509.SubjectAlternativeName).value assert {type(item).__name__ for item in general} == {"IPAddress", "DNSName"} def test_the_host_name_is_always_in_the_san_and_nothing_is_listed_twice() -> None: """A caller that passes no address still gets a verifiable cert, and a duplicate is one name.""" store, state = boot() state = issue(store, state, "driveripper", ["driveripper", " driveripper ", ""]) entry = state.agent("driveripper") assert entry is not None assert entry.san == ("driveripper",) assert san_names(cert_of(bundle_pems(entry.bundle)["cert"])) == ["driveripper"] # --- issue, rotate, revoke ---------------------------------------------------------------------- # def test_a_first_issue_is_revision_one_active_and_one_row() -> None: store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) entry = state.agent("deskwork") assert entry is not None assert entry.status == certs.ACTIVE assert entry.bundle_rev == 1 assert entry.serial > 0 assert entry.not_after == NOW + certs.LEAF_LIFETIME.total_seconds() assert rev_of(entry.bundle) == 1 stored = json.loads(store.rows[certs.META_AGENTS]) assert list(stored) == ["deskwork"] assert sorted(stored["deskwork"]) == ["bundle", "bundle_rev", "not_after", "san", "serial", "status"] assert stored["deskwork"]["bundle"] == entry.bundle, "the agent holds exactly what the row holds" def test_a_reissue_bumps_the_revision_and_replaces_the_serial_and_the_bundle() -> None: store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) first = state.agent("deskwork") assert first is not None state = rotate(store, state, "deskwork", ["10.1.0.51"]) second = state.agent("deskwork") assert second is not None assert second.bundle_rev == first.bundle_rev + 1 assert second.serial != first.serial assert second.bundle != first.bundle assert rev_of(second.bundle) == second.bundle_rev assert key_of(bundle_pems(second.bundle)["key"]) != key_of(bundle_pems(first.bundle)["key"]), ( "rotation replaces the key too, or the stolen one still works") assert len(state.agents) == 1, "an issue is not an append" def test_issue_and_reissue_are_one_operation_under_two_names() -> None: """Rotating a host with nothing issued yet is legal and yields revision 1 (the task's wording).""" store, state = boot() rotated = rotate(store, state, "deskwork", ["10.1.0.51"]) issued = issue(store, state, "deskwork", ["10.1.0.51"]) assert entry_of(rotated, "deskwork").bundle_rev == 1 assert entry_of(issued, "deskwork").bundle_rev == 1 assert entry_of(rotated, "deskwork").status == certs.ACTIVE assert entry_of(issued, "deskwork").status == certs.ACTIVE def test_revoking_puts_the_serial_on_every_bundles_denylist() -> None: """The list lives inside each bundle, so revoking one host rewrites them all — that is the point.""" store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) state = issue(store, state, "gateway", ["10.1.1.27"]) gateway = state.agent("gateway") deskwork = state.agent("deskwork") assert gateway is not None and deskwork is not None state = revoke(store, state, "gateway") assert entry_of(state, "gateway").status == certs.REVOKED assert entry_of(state, "gateway").serial == gateway.serial, "the serial stays the denylisted one" assert revoked_lines(entry_of(state, "gateway").bundle) == [gateway.serial] assert revoked_lines(entry_of(state, "deskwork").bundle) == [gateway.serial], ( "an agent that cannot see the revoked serial keeps accepting that client certificate") assert entry_of(state, "deskwork").bundle_rev == deskwork.bundle_rev, ( "a peer being revoked is not a new identity for this host") assert entry_of(state, "deskwork").status == certs.ACTIVE assert [entry.name for entry in certs.read_agents(store)] == ["deskwork", "gateway"] def test_revoking_a_host_with_no_certificate_still_records_the_revocation() -> None: """What a revoke *does* is stop the dashboard dialing; never-issued must not be a no-op.""" store, state = boot() state = revoke(store, state, "deskwork") entry = state.agent("deskwork") assert entry is not None assert entry.status == certs.REVOKED assert entry.bundle_rev == 1 assert revoked_lines(entry.bundle) == [entry.serial] assert certs.agent_status(store, "deskwork") == certs.REVOKED def test_a_reissue_after_a_revoke_gives_a_serial_that_is_not_listed() -> None: store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) state = issue(store, state, "gateway", ["10.1.1.27"]) revoked_serial = entry_of(state, "gateway").serial state = revoke(store, state, "gateway") state = rotate(store, state, "gateway", ["10.1.1.27"]) fresh = state.agent("gateway") assert fresh is not None and fresh.status == certs.ACTIVE assert fresh.serial != revoked_serial assert revoked_lines(fresh.bundle) == [], "the fix for a revoked identity is a new one" assert revoked_lines(entry_of(state, "deskwork").bundle) == [] assert certs.agent_status(store, "gateway") == certs.ACTIVE def test_two_revocations_are_both_listed_and_in_one_order() -> None: store, state = boot() for name in ("deskwork", "gateway", "driveripper"): state = issue(store, state, name, [f"10.1.0.{len(name)}"]) state = revoke(store, state, "deskwork") state = revoke(store, state, "driveripper") expected = sorted([entry_of(state, "deskwork").serial, entry_of(state, "driveripper").serial]) for name in ("deskwork", "gateway", "driveripper"): assert revoked_lines(entry_of(state, name).bundle) == expected, "one order, in every bundle" assert entry_of(state, "gateway").status == certs.ACTIVE def test_agent_status_names_only_what_the_operator_revoked() -> None: store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) state = revoke(store, state, "deskwork") assert certs.agent_status(store, "deskwork") == certs.REVOKED assert certs.agent_status(store, "gateway") == certs.ACTIVE assert certs.agent_status(FakeMeta(), "anything") == certs.ACTIVE, ( "no row is not a revocation: the safe answer is to keep dialing") def test_read_agents_is_a_fresh_view_of_the_table() -> None: """Task 03 re-reads this each poll; a cached view would delay a revocation by a restart.""" store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) assert [entry.name for entry in certs.read_agents(store)] == ["deskwork"] snapshot = state state = revoke(store, state, "deskwork") assert entry_of(snapshot, "deskwork").status == certs.ACTIVE, "a returned state is a snapshot" assert certs.read_agents(store)[0].status == certs.REVOKED def test_read_agents_sorts_by_name_so_one_order_reaches_the_page() -> None: store, state = boot() for name in ("gateway", "deskwork", "driveripper"): state = issue(store, state, name) assert [entry.name for entry in certs.read_agents(store)] == [ "deskwork", "driveripper", "gateway"] assert [entry.name for entry in state.agents] == ["deskwork", "driveripper", "gateway"] def test_nothing_is_issued_on_a_read() -> None: """A `GET` that minted a certificate would make "did anyone rotate this?" unanswerable.""" store, state = boot() before = dict(store.rows) for _ in range(5): certs.ensure_certs(store, now=NOW) certs.read_state(store) certs.read_agents(store) certs.agent_status(store, "deskwork") assert store.rows == before assert state.agents == () def test_read_state_is_the_stored_authority_rather_than_the_bootstrapped_one() -> None: """`read_state` answers "what does the table hold now?", which is what `GET /api/certs` prints. After a root rotation the process still holds the state it started with, so a page reading that object would show a CA the operator has just replaced. The read goes to the rows, which is the same reason task 03 reads `agent_status` per poll instead of caching it at startup. """ store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) stale_root = state.root_serial rotated = certs.rotate_root(store, state, now=NOW) fresh = certs.read_state(store) assert (fresh.root_serial, fresh.client_serial) == (rotated.root_serial, rotated.client_serial) assert fresh.root_serial != stale_root, "the old authority is not what the read answers" assert fresh.root_cert == store.rows[certs.META_ROOT_CERT], "byte for byte, as stored" assert [entry.name for entry in fresh.agents] == [entry.name for entry in rotated.agents] def test_read_state_refuses_a_missing_row_instead_of_minting_one() -> None: """The one behaviour that separates this read from `ensure_certs`, pinned from both sides. A GET that quietly regenerated a deleted row would both write on a read and replace the CA with one nothing in the fleet trusts — the failure `ensure_certs` exists to bootstrap at startup and the failure a request must never cause. `ensure_certs` on the same table generates; `read_state` refuses, and the message is the fixed reset text that names the rows. """ store, _state = boot() store.rows.pop(certs.META_CLIENT_CERT) with pytest.raises(RuntimeError) as caught: certs.read_state(store) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE assert store.rows.get(certs.META_CLIENT_CERT) is None, "the refusal wrote nothing back" regenerated = certs.ensure_certs(store, now=NOW) # the startup read does mint it assert regenerated.client_cert @pytest.mark.parametrize("key", [certs.META_ROOT_KEY, certs.META_ROOT_CERT, certs.META_CLIENT_KEY, certs.META_CLIENT_CERT]) def test_read_state_validates_the_rows_it_reports(key: str) -> None: """A page listing a corrupt authority would be a calmer way to hide the outage.""" store, _state = boot() store.rows[key] = "not a PEM document" with pytest.raises(RuntimeError) as caught: certs.read_state(store) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE def test_read_state_refuses_a_key_that_is_not_the_one_its_certificate_certifies() -> None: """A row copied over from another row is corruption with a plausible look to it. The two halves of a pair are separate `meta` rows, so this is exactly what a hand-edit produces, and a process that started with a mismatched pair would sign leaves with a key nothing verifies. """ store, _state = boot() store.rows[certs.META_CLIENT_KEY] = store.rows[certs.META_ROOT_KEY] with pytest.raises(RuntimeError) as caught: certs.read_state(store) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE def test_read_state_refuses_a_leaf_the_stored_root_did_not_sign() -> None: """A crash between the root write and the client write is the corrupt state, not a half-state.""" store, _state = boot() other, _other_state = boot() store.rows[certs.META_CLIENT_CERT] = other.rows[certs.META_CLIENT_CERT] store.rows[certs.META_CLIENT_KEY] = other.rows[certs.META_CLIENT_KEY] with pytest.raises(RuntimeError) as caught: certs.read_state(store) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE # --- root rotation ------------------------------------------------------------------------------ # def test_rotating_the_root_reissues_every_agent_and_resets_the_denylist() -> None: store, state = boot() for name in ("deskwork", "gateway"): state = issue(store, state, name, [f"10.1.0.{len(name)}"]) state = revoke(store, state, "gateway") old_root = root_of(state) old_client_serial = state.client_serial before = {entry.name: entry for entry in state.agents} state = certs.rotate_root(store, state, now=NOW) new_root = root_of(state) assert new_root.serial_number != old_root.serial_number assert state.client_serial != old_client_serial cert_of(state.client_cert).verify_directly_issued_by(new_root) for name, previous in before.items(): entry = state.agent(name) assert entry is not None assert entry.status == certs.ACTIVE, "every identity is replaced, so nothing stays revoked" assert entry.bundle_rev == previous.bundle_rev + 1 assert entry.serial != previous.serial assert entry.san == previous.san, "rotation must not change the names the dashboard dials" cert_of(bundle_pems(entry.bundle)["cert"]).verify_directly_issued_by(new_root) assert bundle_pems(entry.bundle)["root"] == state.root_cert assert revoked_lines(entry.bundle) == [], "the denylist belongs to one generation" assert json.loads(store.rows[certs.META_AGENTS])["gateway"]["bundle_rev"] == ( before["gateway"].bundle_rev + 1) def test_no_stored_value_still_carries_the_old_generation() -> None: """A rotated root has to be *gone*: a stale key or cert left in a row is the leak and the bug.""" store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) old_root_key, old_root_cert, old_client_key = state.root_key, state.root_cert, state.client_key state = certs.rotate_root(store, state, now=NOW) for key, value in store.rows.items(): assert old_root_key not in value, key assert old_root_cert not in value, key assert old_client_key not in value, key assert state.root_key != old_root_key assert root_of(state).serial_number != cert_of(old_root_cert).serial_number entry = state.agent("deskwork") assert entry is not None and old_root_cert not in entry.bundle def test_rotating_the_root_with_no_agents_yet_still_leaves_a_working_credential() -> None: store, state = boot() state = certs.rotate_root(store, state, now=NOW) assert state.agents == () cert_of(state.client_cert).verify_directly_issued_by(root_of(state)) assert sorted(store.rows) == sorted(certs.META_KEYS) # --- the bundle --------------------------------------------------------------------------------- # def test_the_bundle_is_metadata_then_agent_cert_then_agent_key_then_root_cert() -> None: store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) entry = state.agent("deskwork") assert entry is not None bundle = entry.bundle assert bundle.startswith(f"{certs.BUNDLE_REV_LINE}1\n") assert "# REVOKED" not in bundle, "nothing is revoked on a fresh root with nothing revoked" assert [block.split("-----")[1] for block in blocks(bundle)] == [ "BEGIN CERTIFICATE", "BEGIN PRIVATE KEY", "BEGIN CERTIFICATE"] assert bundle_pems(bundle)["root"] == state.root_cert, "the trust anchor is the stored root" assert bundle_pems(bundle)["cert"] == cert_of( bundle_pems(bundle)["cert"]).public_bytes(Encoding.PEM).decode("ascii") def test_the_bundle_carries_the_agent_key_and_never_the_root_key() -> None: """The TODO's "root certificate installed with each exporter" is the root *cert*. Asserted twice on purpose: by header count (one key block, so a second private key is visible) and by parsing that key against both keypairs, so a swap that kept the count honest still fails here. """ store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) entry = state.agent("deskwork") assert entry is not None bundle = entry.bundle assert bundle.count("-----BEGIN PRIVATE KEY-----") == 1 assert state.root_key not in bundle in_bundle = key_of(bundle_pems(bundle)["key"]) agent_cert = cert_of(bundle_pems(bundle)["cert"]) assert public_id(in_bundle.public_key()) == public_id(agent_cert.public_key()) assert public_id(in_bundle.public_key()) != public_id(root_of(state).public_key()) def test_a_bundle_that_is_not_the_stored_shape_is_the_fixed_text_refusal() -> None: """A hand-edited or truncated row cannot be re-sealed — and re-sealing is what revoking does.""" store, state = boot() state = issue(store, state, "deskwork", ["10.1.0.51"]) payload = json.loads(store.rows[certs.META_AGENTS]) payload["deskwork"]["bundle"] = "-----BEGIN CERTIFICATE-----\nbroken\n" store.set_meta(certs.META_AGENTS, json.dumps(payload)) tampered = certs.ensure_certs(store, now=NOW) with pytest.raises(RuntimeError) as caught: revoke(store, tampered, "deskwork") assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE # --- fail closed on a corrupt row ---------------------------------------------------------------- # @pytest.mark.parametrize("key", [certs.META_ROOT_KEY, certs.META_ROOT_CERT, certs.META_CLIENT_KEY, certs.META_CLIENT_CERT]) def test_a_stored_pem_that_will_not_parse_refuses_to_start(key: str) -> None: store, _state = boot() store.rows[key] = "not a PEM document" with pytest.raises(RuntimeError) as caught: certs.ensure_certs(store, now=NOW) message = str(caught.value) assert message == certs.CORRUPT_STORE_MESSAGE for name in certs.META_KEYS: assert name in message, "the operator is told which rows to delete" def test_a_key_type_this_build_cannot_use_refuses_to_start() -> None: """Somebody hand-inserting an RSA key gets the reset message, not a stack trace at first dial.""" store, _state = boot() rsa_pem = rsa.generate_private_key(public_exponent=65537, key_size=2048).private_bytes( Encoding.PEM, PrivateFormat.PKCS8, NoEncryption()).decode("ascii") store.rows[certs.META_CLIENT_KEY] = rsa_pem with pytest.raises(RuntimeError) as caught: certs.ensure_certs(store, now=NOW) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE def test_a_key_that_does_not_match_its_certificate_refuses_to_start() -> None: store, _state = boot() other = FakeMeta() other_state = certs.ensure_certs(other, now=NOW) store.rows[certs.META_CLIENT_KEY] = other_state.client_key with pytest.raises(RuntimeError) as caught: certs.ensure_certs(store, now=NOW) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE def test_a_client_certificate_the_stored_root_did_not_sign_refuses_to_start() -> None: """The half-finished root rotation: a new root beside an old leaf is a dashboard that reaches nothing.""" store, _state = boot() other = FakeMeta() other_state = certs.ensure_certs(other, now=NOW) store.rows[certs.META_CLIENT_CERT] = other_state.client_cert store.rows[certs.META_CLIENT_KEY] = other_state.client_key with pytest.raises(RuntimeError) as caught: certs.ensure_certs(store, now=NOW) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE def test_a_root_key_and_cert_from_two_generations_refuse_to_start() -> None: store, _state = boot() other = FakeMeta() other_state = certs.ensure_certs(other, now=NOW) store.rows[certs.META_ROOT_KEY] = other_state.root_key with pytest.raises(RuntimeError) as caught: certs.ensure_certs(store, now=NOW) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE @pytest.mark.parametrize("raw", [ "not json at all", "[]", '{"deskwork": "nope"}', '{"deskwork": {"serial": 1, "not_after": 2, "status": "active", "bundle_rev": 1, ' '"bundle": "x"}}', '{"deskwork": {"serial": 1, "not_after": 2, "status": "active", "bundle_rev": 1, ' '"bundle": "x", "san": "deskwork"}}', '{"deskwork": {"serial": "abc", "not_after": 2, "status": "active", "bundle_rev": 1, ' '"bundle": "x", "san": []}}', '{"deskwork": {"serial": 1, "not_after": 2, "status": "active", "bundle_rev": 1, ' '"bundle": "", "san": []}}', ]) def test_an_agent_table_that_is_not_the_stored_shape_refuses_to_start(raw: str) -> None: """Every one of these is what a partial write or a hand edit could leave behind.""" store, _state = boot() store.rows[certs.META_AGENTS] = raw with pytest.raises(RuntimeError) as caught: certs.ensure_certs(store, now=NOW) assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE def test_the_refusal_quotes_nothing_from_the_database() -> None: """invariant 6, against the value that matters most in this module: the root key.""" store, _state = boot() secret = store.rows[certs.META_ROOT_KEY] store.rows[certs.META_ROOT_CERT] = "truncated-----" with pytest.raises(RuntimeError) as caught: certs.ensure_certs(store, now=NOW) message = str(caught.value) body = "\n".join(secret.splitlines()[1:-1]) assert body not in message assert body[:32] not in message assert store.rows[certs.META_ROOT_KEY] == secret, "refusing must not rewrite the rows" # --- durability through the real store -------------------------------------------------------------- # def test_the_certificates_survive_a_reopen_of_the_real_database(tmp_path: Path) -> None: """The whole reason this lives in SQLite: a restart re-reads it and an agent install stays valid.""" path = tmp_path / "dashboards.sqlite3" first_store = SampleStore(path) try: first = certs.ensure_certs(first_store, now=NOW) issued = certs.issue_agent_cert(first_store, first, "deskwork", ["10.1.0.51"], now=NOW) finally: first_store.close() reopened = SampleStore(path) try: second = certs.ensure_certs(reopened, now=NOW + 10) finally: reopened.close() assert second.root_cert == first.root_cert assert second.root_key == first.root_key assert second.client_cert == first.client_cert entry = second.agent("deskwork") assert entry is not None assert entry.bundle == entry_of(issued, "deskwork").bundle assert entry.bundle_rev == 1 # --- on-disk material ------------------------------------------------------------------------------ # def test_the_material_is_three_private_files_and_never_the_root_key(tmp_path: Path) -> None: _store, state = boot() material = certs.write_material(state, tmp_path) try: assert sorted(p.name for p in material.dir.iterdir()) == [ CLIENT_CERT_FILE, CLIENT_KEY_FILE, CA_FILE] assert material.ca.read_text() == state.root_cert assert material.client_cert.read_text() == state.client_cert assert material.client_key.read_text() == state.client_key assert (material.dir.stat().st_mode & 0o777) == 0o700 for path in (material.ca, material.client_cert, material.client_key): assert (path.stat().st_mode & 0o777) == 0o600, str(path) assert state.root_key not in "\n".join(p.read_text() for p in material.dir.iterdir()) assert material.state is state finally: certs.remove_material(material) def test_the_material_defaults_to_the_environment_tmp_dir() -> None: _store, state = boot() material = certs.write_material(state) try: assert material.dir.name.startswith("dashboards-certs-") assert material.dir.parent.resolve() == Path(tempfile.gettempdir()).resolve() assert material.ca.exists() finally: certs.remove_material(material) def test_removing_the_material_takes_the_directory_and_tolerates_none(tmp_path: Path) -> None: _store, state = boot() material = certs.write_material(state, tmp_path) certs.remove_material(material) assert not material.dir.exists() certs.remove_material(None) # --- the material works in a real TLS handshake ----------------------------------------------------- # def test_the_material_and_a_bundle_complete_a_real_client_certificate_handshake( tmp_path: Path) -> None: """The claim tasks 02 and 03 rest on, checked with `ssl` instead of with this module's parser. A certificate `cryptography` wrote that `ssl` will not load is the worst bug available here: every other test in this file would stay green while no agent in the fleet could handshake. The server side runs with `CERT_OPTIONAL`, which is task 02's assumption that `/health` stays probeable without a client certificate, so both answers are asserted: a peer certificate for the dashboard, and none for a bare probe. """ # Real clock for this one test: `ssl` checks the validity window itself, so the fixed `NOW` # this file mints everything else at would be "certificate has expired" by 2027. store = FakeMeta() state = certs.ensure_certs(store) state = certs.issue_agent_cert(store, state, "localhost", ["127.0.0.1", "localhost"]) entry = state.agent("localhost") assert entry is not None pems = bundle_pems(entry.bundle) (tmp_path / "agent-cert.pem").write_text(pems["cert"]) (tmp_path / "agent-key.pem").write_text(pems["key"]) (tmp_path / CA_FILE).write_text(state.root_cert) base = tmp_path / "material" base.mkdir() material = certs.write_material(state, base) try: (tmp_path / CLIENT_CERT_FILE).write_text(material.client_cert.read_text()) (tmp_path / CLIENT_KEY_FILE).write_text(material.client_key.read_text()) server = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) server.load_cert_chain(str(tmp_path / "agent-cert.pem"), str(tmp_path / "agent-key.pem")) server.load_verify_locations(str(tmp_path / CA_FILE)) server.verify_mode = ssl.CERT_OPTIONAL with_cert = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) with_cert.load_verify_locations(str(tmp_path / CA_FILE)) with_cert.load_cert_chain(str(tmp_path / CLIENT_CERT_FILE), str(tmp_path / CLIENT_KEY_FILE)) without_cert = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) without_cert.load_verify_locations(str(tmp_path / CA_FILE)) seen = _handshake(server, [with_cert, without_cert], tmp_path) finally: certs.remove_material(material) assert seen[0], "the dashboard's client certificate reached the agent" assert any(name == "commonName" and value == certs.CLIENT_CN for name, value in seen[0]["subject"][0]) # `getpeercert()` says "no certificate" as `{}` on some builds and `None` on others; the # handler's check (task 02) is `if not cert`, which is what this pins rather than the empty # value's exact shape. assert not seen[1], "a probe with no client certificate still handshakes: the /health path" def _handshake(server: ssl.SSLContext, clients: list[ssl.SSLContext], tmp_path: Path) -> list[Any]: """Serve `server` on a loopback port and run one handshake per client context.""" assert len(clients) == 2 listener = socket.socket() listener.bind(("127.0.0.1", 0)) listener.listen(2) listener.settimeout(15) port = listener.getsockname()[1] seen: list[Any] = [] def serve() -> None: for _ in clients: try: raw, _peer = listener.accept() except OSError: return try: with server.wrap_socket(raw, server_side=True) as tls: seen.append(tls.getpeercert()) tls.sendall(b"ok") except OSError as exc: # a refused handshake is itself an answer under test seen.append(exc) thread = threading.Thread(target=serve, daemon=True) thread.start() try: for context in clients: with socket.create_connection(("127.0.0.1", port), timeout=15) as raw, \ context.wrap_socket(raw, server_hostname="localhost") as tls: assert tls.recv(2) == b"ok" finally: listener.close() thread.join(timeout=15) return seen # --- the app wiring (work item 3) ---------------------------------------------------------------- # def settings_for(db: str) -> Settings: return Settings(db_path=db, admin_password=ADMIN_PASSWORD, pbkdf2_iterations=FAST_PBKDF2) def test_the_lifespan_builds_the_material_before_the_loops_start(tmp_path: Path) -> None: """`ctx.certs` is there for task 03 to dial with, in the injected-store branch too.""" db = str(tmp_path / "app.sqlite3") store = SampleStore(db) app = create_app(settings_for(db), store) try: with TestClient(app): context = app.state.ctx assert context.certs is not None assert context.certs.ca.exists() and context.certs.client_cert.exists() assert context.certs.client_key.exists() directory = context.certs.dir for key in certs.META_KEYS: assert store.get_meta(key), f"{key} is durable, not only in memory" assert context.auth is not None assert auth.verify_password(ADMIN_PASSWORD, context.auth.verifier) is True finally: store.close() assert not directory.exists(), "the private directory goes with the process" def test_a_corrupt_stored_certificate_makes_the_server_refuse_to_start(tmp_path: Path) -> None: """Fail closed, before the loops: a dashboard with no credential cannot report anything truthfully.""" db = str(tmp_path / "corrupt.sqlite3") store = SampleStore(db) certs.ensure_certs(store, now=NOW) store.set_meta(certs.META_ROOT_CERT, "the certificate that is not one") app = create_app(settings_for(db), store) try: with pytest.raises(RuntimeError) as caught, TestClient(app): pytest.fail("the lifespan yielded with unreadable certificates") assert str(caught.value) == certs.CORRUPT_STORE_MESSAGE assert not hasattr(app.state, "ctx"), "nothing is published, so nothing can serve" finally: store.close()