"""Unit: trust domains + the shared control-plane provisioning contract (#476).""" from __future__ import annotations import unittest from unittest.mock import patch from bot_bottle import orchestrator_auth from bot_bottle.orchestrator_auth import ROLE_CLI, ROLE_GATEWAY from bot_bottle.trust_domain import ( CONTROL_PLANE, ControlPlaneProvisioning, ProvisioningError, TrustDomain, ) # A second, unrelated domain — the shape #468's host controller would take: its # own key file, its own role, its own env vars. Distinct from CONTROL_PLANE. _HOST_CTRL = TrustDomain( name="host-controller", key_filename="host-controller-token", roles=frozenset({"host"}), key_env="BOT_BOTTLE_HOST_CONTROLLER_TOKEN", token_env="BOT_BOTTLE_HOST_CONTROLLER_JWT", ) class TestTrustDomainMintVerify(unittest.TestCase): def test_mint_verify_round_trips_within_a_domain(self) -> None: with patch("bot_bottle.trust_domain.host_signing_key", return_value="k"): tok = CONTROL_PLANE.mint(ROLE_GATEWAY) self.assertEqual(ROLE_GATEWAY, CONTROL_PLANE.verify(tok, "k")) def test_mint_rejects_a_role_outside_the_domain(self) -> None: # `host` is a valid role in _HOST_CTRL but not in the control plane — # the control-plane key must refuse to mint it. with patch("bot_bottle.trust_domain.host_signing_key", return_value="k"): with self.assertRaises(ValueError): CONTROL_PLANE.mint("host") def test_a_custom_role_set_verifies_its_own_role(self) -> None: with patch("bot_bottle.trust_domain.host_signing_key", return_value="k"): tok = _HOST_CTRL.mint("host") self.assertEqual("host", _HOST_CTRL.verify(tok, "k")) def test_key_from_env_reads_the_domains_env_var(self) -> None: self.assertEqual( "secret", CONTROL_PLANE.key_from_env({CONTROL_PLANE.key_env: " secret "})) self.assertEqual("", CONTROL_PLANE.key_from_env({})) class TestDomainBoundary(unittest.TestCase): """The #476/#468 invariant: two domains, two keys, two role sets — one domain's key can neither mint nor verify the other's tokens.""" def test_a_control_plane_token_does_not_verify_under_another_domains_key( self, ) -> None: # Even with an IDENTICAL underlying key, a `cli` token minted under the # control-plane domain must not verify as a role in the host-controller # domain — the role isn't in that domain's set. cli_tok = orchestrator_auth.mint(ROLE_CLI, "shared-bytes") self.assertIsNone(_HOST_CTRL.verify(cli_tok, "shared-bytes")) def test_distinct_keys_do_not_cross_verify(self) -> None: # The realistic case: distinct host-canonical keys per domain. A token # signed by one key never verifies under the other. host_tok = orchestrator_auth.mint( "host", "host-ctrl-key", roles=_HOST_CTRL.roles) self.assertIsNone(_HOST_CTRL.verify(host_tok, "control-plane-key")) self.assertEqual("host", _HOST_CTRL.verify(host_tok, "host-ctrl-key")) class TestControlPlaneProvisioning(unittest.TestCase): def test_orchestrator_key_returns_the_canonical_key(self) -> None: prov = ControlPlaneProvisioning() with patch("bot_bottle.trust_domain.host_signing_key", return_value="key"): self.assertEqual("key", prov.orchestrator_key()) def test_orchestrator_key_fail_closes_when_empty(self) -> None: # Invariant 4: the orchestrator must never start without a key — it would # run OPEN and grant every caller that reaches it full `cli`. There is no # topology opt-out: a separate host does not stop the gateway (or any # other caller) from reaching the control-plane listener. prov = ControlPlaneProvisioning() with patch("bot_bottle.trust_domain.host_signing_key", return_value=""): with self.assertRaises(ProvisioningError): prov.orchestrator_key() def test_gateway_token_is_a_verifiable_gateway_role_token(self) -> None: prov = ControlPlaneProvisioning() with patch("bot_bottle.trust_domain.host_signing_key", return_value="k"): tok = prov.gateway_token() self.assertEqual(ROLE_GATEWAY, CONTROL_PLANE.verify(tok, "k")) def test_gateway_token_cannot_be_reused_as_cli(self) -> None: # The data plane's token is `gateway`-scoped: it never carries `cli`. prov = ControlPlaneProvisioning() with patch("bot_bottle.trust_domain.host_signing_key", return_value="k"): tok = prov.gateway_token() self.assertNotEqual(ROLE_CLI, CONTROL_PLANE.verify(tok, "k")) if __name__ == "__main__": unittest.main()