refactor(platform): freeze laboratory and telemetry boundaries
This commit is contained in:
@@ -1,7 +1,10 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
import json
|
||||
import stat
|
||||
import sys
|
||||
import zipfile
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
|
||||
@@ -22,6 +25,7 @@ def _prepare_module() -> ModuleType:
|
||||
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
|
||||
|
||||
@@ -29,74 +33,122 @@ def _prepare_module() -> ModuleType:
|
||||
prepare = _prepare_module()
|
||||
|
||||
|
||||
def test_initialize_environment_generates_private_unique_secrets(
|
||||
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:
|
||||
env_path = tmp_path / ".env"
|
||||
monkeypatch.setattr(prepare, "ENV_PATH", env_path)
|
||||
_private_paths(tmp_path, monkeypatch)
|
||||
|
||||
prepare._initialize_environment("192.0.2.15")
|
||||
|
||||
values = dict(
|
||||
line.split("=", 1)
|
||||
for line in env_path.read_text(encoding="utf-8").splitlines()
|
||||
for line in prepare.ENV_PATH.read_text(encoding="utf-8").splitlines()
|
||||
)
|
||||
assert values["MISSIONCORE_MQTT_BIND_ADDRESS"] == "192.0.2.15"
|
||||
secrets = {
|
||||
generated = {
|
||||
values["MISSIONCORE_DB_PASSWORD"],
|
||||
values["MISSIONCORE_DB_INGEST_PASSWORD"],
|
||||
values["MISSIONCORE_MQTT_INGEST_PASSWORD"],
|
||||
values["MISSIONCORE_MQTT_WORKER_006_PASSWORD"],
|
||||
}
|
||||
assert len(secrets) == 4
|
||||
assert all(len(secret) >= 40 for secret in secrets)
|
||||
assert stat.S_IMODE(env_path.stat().st_mode) == 0o600
|
||||
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:
|
||||
env_path = tmp_path / ".env"
|
||||
env_path.write_text("existing=true\n", encoding="utf-8")
|
||||
monkeypatch.setattr(prepare, "ENV_PATH", env_path)
|
||||
_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 env_path.read_text(encoding="utf-8") == "existing=true\n"
|
||||
assert prepare.ENV_PATH.read_text(encoding="utf-8") == "existing=true\n"
|
||||
|
||||
|
||||
def test_environment_migration_adds_only_new_private_values(
|
||||
def test_migration_moves_legacy_worker_to_private_registry(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
env_path = tmp_path / ".env"
|
||||
env_path.write_text(
|
||||
_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_USER=worker-006\n"
|
||||
"MISSIONCORE_MQTT_WORKER_006_CONTOUR=worker-006\n"
|
||||
f"MISSIONCORE_MQTT_WORKER_006_PASSWORD={'l' * 40}\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
monkeypatch.setattr(prepare, "ENV_PATH", env_path)
|
||||
|
||||
prepare._migrate_environment()
|
||||
first = env_path.read_text(encoding="utf-8")
|
||||
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
|
||||
assert "MISSIONCORE_DB_INGEST_PASSWORD=" in first
|
||||
assert "MISSIONCORE_MQTT_WORKER_006_CONTOUR=worker-006" in first
|
||||
assert env_path.read_text(encoding="utf-8") == first
|
||||
assert stat.S_IMODE(env_path.stat().st_mode) == 0o600
|
||||
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_existing_password_file_is_updated_without_recreation(
|
||||
def test_enrollment_is_generic_private_and_rejects_duplicate_agent_id(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
password_path = tmp_path / "passwords"
|
||||
password_path.write_text("existing", encoding="utf-8")
|
||||
_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(
|
||||
@@ -106,9 +158,9 @@ def test_existing_password_file_is_updated_without_recreation(
|
||||
*,
|
||||
create: bool,
|
||||
) -> None:
|
||||
assert path == password_path
|
||||
assert password
|
||||
calls.append((username, create))
|
||||
path.write_text("new-password-file", encoding="utf-8")
|
||||
|
||||
monkeypatch.setattr(prepare, "_password_entry", capture)
|
||||
|
||||
@@ -116,11 +168,70 @@ def test_existing_password_file_is_updated_without_recreation(
|
||||
password_path,
|
||||
"missioncore-ingest",
|
||||
"ingest-secret",
|
||||
"worker-006",
|
||||
"worker-secret",
|
||||
agents,
|
||||
)
|
||||
|
||||
assert calls == [
|
||||
("missioncore-ingest", False),
|
||||
("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
|
||||
|
||||
Reference in New Issue
Block a user