Files
NODEDC_MISSION_CORE/tests/test_telemetry_plane_prepare.py

238 lines
8.0 KiB
Python

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