from __future__ import annotations import importlib.util import json import stat import sys import zipfile from pathlib import Path from types import ModuleType import pytest def _prepare_module() -> ModuleType: path = ( Path(__file__).resolve().parents[1] / "deploy" / "telemetry-plane" / "prepare.py" ) specification = importlib.util.spec_from_file_location( "mission_core_telemetry_prepare", path, ) assert specification is not None assert specification.loader is not None module = importlib.util.module_from_spec(specification) sys.modules[specification.name] = module specification.loader.exec_module(module) return module prepare = _prepare_module() def _private_paths(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setattr(prepare, "ENV_PATH", tmp_path / ".env") monkeypatch.setattr(prepare, "RUNTIME_ROOT", tmp_path / "runtime") monkeypatch.setattr(prepare, "RUNTIME", tmp_path / "runtime" / "mosquitto") monkeypatch.setattr( prepare, "AGENT_REGISTRY_PATH", tmp_path / "runtime" / "agents.json", ) def _write_environment(path: Path) -> None: path.write_text( "MISSIONCORE_MQTT_BIND_ADDRESS=192.168.68.52\n" "MISSIONCORE_MQTT_PORT=1883\n" "MISSIONCORE_DB_PASSWORD=db-secret\n" "MISSIONCORE_DB_INGEST_PASSWORD=db-ingest-secret\n" "MISSIONCORE_MQTT_INGEST_USER=missioncore-ingest\n" "MISSIONCORE_MQTT_INGEST_PASSWORD=mqtt-ingest-secret\n", encoding="utf-8", ) def test_initialize_environment_generates_only_plane_secrets( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _private_paths(tmp_path, monkeypatch) prepare._initialize_environment("192.0.2.15") values = dict( line.split("=", 1) for line in prepare.ENV_PATH.read_text(encoding="utf-8").splitlines() ) assert values["MISSIONCORE_MQTT_BIND_ADDRESS"] == "192.0.2.15" generated = { values["MISSIONCORE_DB_PASSWORD"], values["MISSIONCORE_DB_INGEST_PASSWORD"], values["MISSIONCORE_MQTT_INGEST_PASSWORD"], } assert len(generated) == 3 assert all(len(secret) >= 40 for secret in generated) assert not any("WORKER_006" in name for name in values) assert stat.S_IMODE(prepare.ENV_PATH.stat().st_mode) == 0o600 def test_initialize_environment_refuses_to_replace_credentials( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _private_paths(tmp_path, monkeypatch) prepare.ENV_PATH.write_text("existing=true\n", encoding="utf-8") with pytest.raises(RuntimeError, match="refusing to overwrite"): prepare._initialize_environment("127.0.0.1") assert prepare.ENV_PATH.read_text(encoding="utf-8") == "existing=true\n" def test_migration_moves_legacy_worker_to_private_registry( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _private_paths(tmp_path, monkeypatch) prepare.ENV_PATH.write_text( "MISSIONCORE_DB_PASSWORD=keep-me\n" "MISSIONCORE_MQTT_WORKER_006_USER=worker-006\n" "MISSIONCORE_MQTT_WORKER_006_CONTOUR=worker-006\n" f"MISSIONCORE_MQTT_WORKER_006_PASSWORD={'l' * 40}\n", encoding="utf-8", ) prepare._migrate_environment() first_env = prepare.ENV_PATH.read_text(encoding="utf-8") first_registry = prepare.AGENT_REGISTRY_PATH.read_bytes() prepare._migrate_environment() assert "MISSIONCORE_DB_PASSWORD=keep-me" in first_env assert "MISSIONCORE_DB_INGEST_PASSWORD=" in first_env assert prepare.ENV_PATH.read_text(encoding="utf-8") == first_env assert prepare.AGENT_REGISTRY_PATH.read_bytes() == first_registry assert prepare._read_agent_registry() == ( prepare.AgentCredential("worker-006", "worker-006", "l" * 40), ) assert stat.S_IMODE(prepare.AGENT_REGISTRY_PATH.stat().st_mode) == 0o600 def test_enrollment_is_generic_private_and_rejects_duplicate_agent_id( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _private_paths(tmp_path, monkeypatch) first = prepare._enroll_agent("compute-east", "worker-006") second = prepare._enroll_agent("compute-west", "worker-007") assert first.password != second.password assert prepare._read_agent_registry() == (first, second) assert stat.S_IMODE(prepare.AGENT_REGISTRY_PATH.stat().st_mode) == 0o600 with pytest.raises(RuntimeError, match="already enrolled"): prepare._enroll_agent("another-contour", "worker-006") def test_password_file_is_rebuilt_and_acl_is_exactly_scoped( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _private_paths(tmp_path, monkeypatch) prepare.RUNTIME.mkdir(parents=True) password_path = prepare.RUNTIME / "passwords" password_path.write_text("stale-user", encoding="utf-8") agents = ( prepare.AgentCredential("compute-west", "worker-007", "w" * 40), prepare.AgentCredential("compute-east", "worker-006", "e" * 40), ) calls: list[tuple[str, bool]] = [] def capture( path: Path, username: str, password: str, *, create: bool, ) -> None: assert password calls.append((username, create)) path.write_text("new-password-file", encoding="utf-8") monkeypatch.setattr(prepare, "_password_entry", capture) prepare._prepare_password_entries( password_path, "missioncore-ingest", "ingest-secret", agents, ) assert calls == [ ("missioncore-ingest", True), ("worker-006", False), ("worker-007", False), ] assert password_path.read_text() == "new-password-file" acl = prepare._acl_document("missioncore-ingest", agents) assert "topic write mission-core/v1/contours/compute-east/agents/worker-006/+" in acl assert "topic write mission-core/v1/contours/compute-west/agents/worker-007/+" in acl assert "topic write mission-core/v1/contours/+/agents/+/+" not in acl def test_payload_export_is_private_separate_and_non_overwriting( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: _private_paths(tmp_path, monkeypatch) _write_environment(prepare.ENV_PATH) credential = prepare._enroll_agent("compute-east", "worker-007") output = tmp_path / "worker-007.private.json" prepare._export_agent_payload( agent_id="worker-007", node_id="WORKSTATION-007", output=output, ) payload = json.loads(output.read_text()) assert payload["schema_version"] == prepare.AGENT_PAYLOAD_SCHEMA assert payload["MISSIONCORE_CONTOUR_ID"] == "compute-east" assert payload["MISSIONCORE_AGENT_ID"] == "worker-007" assert payload["MISSIONCORE_MQTT_PASSWORD"] == credential.password assert stat.S_IMODE(output.stat().st_mode) == 0o600 with pytest.raises(RuntimeError, match="refusing to replace"): prepare._export_agent_payload( agent_id="worker-007", node_id="WORKSTATION-007", output=output, ) def test_windows_agent_bundle_is_deterministic_and_contains_no_credentials( tmp_path: Path, ) -> None: first = tmp_path / "agent-a.zip" second = tmp_path / "agent-b.zip" first_id = prepare._build_agent_bundle(platform_name="windows", output=first) second_id = prepare._build_agent_bundle(platform_name="windows", output=second) assert first_id == second_id assert first.read_bytes() == second.read_bytes() with zipfile.ZipFile(first) as archive: assert set(archive.namelist()) == { "manifest.json", *prepare.WINDOWS_BUNDLE_FILES, } manifest = json.loads(archive.read("manifest.json")) assert manifest["bundle_id"] == first_id assert manifest["credential_embedded"] is False archive_bytes = b"".join(archive.read(name) for name in archive.namelist()) assert b"replace-with-a-random-local-secret" not in archive_bytes assert b"legacy-worker-secret" not in archive_bytes assert stat.S_IMODE(first.stat().st_mode) == 0o600