feat(perception): stabilize pre-capture methodology
This commit is contained in:
@@ -3,6 +3,7 @@ from __future__ import annotations
|
||||
import argparse
|
||||
import ipaddress
|
||||
import os
|
||||
import re
|
||||
import secrets
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
@@ -13,6 +14,9 @@ ENV_PATH: Final = ROOT / ".env"
|
||||
RUNTIME: Final = ROOT / "runtime" / "mosquitto"
|
||||
IMAGE: Final = "eclipse-mosquitto:2.1.2-alpine"
|
||||
PLACEHOLDER: Final = "replace-with-"
|
||||
SAFE_IDENTIFIER: Final = re.compile(
|
||||
r"^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$"
|
||||
)
|
||||
|
||||
|
||||
def _initialize_environment(bind_address: str, *, overwrite: bool = False) -> None:
|
||||
@@ -26,9 +30,11 @@ def _initialize_environment(bind_address: str, *, overwrite: bool = False) -> No
|
||||
"MISSIONCORE_DB_NAME": "missioncore_telemetry",
|
||||
"MISSIONCORE_DB_USER": "missioncore_ingest",
|
||||
"MISSIONCORE_DB_PASSWORD": secrets.token_urlsafe(36),
|
||||
"MISSIONCORE_DB_INGEST_PASSWORD": secrets.token_urlsafe(36),
|
||||
"MISSIONCORE_MQTT_INGEST_USER": "missioncore-ingest",
|
||||
"MISSIONCORE_MQTT_INGEST_PASSWORD": secrets.token_urlsafe(36),
|
||||
"MISSIONCORE_MQTT_WORKER_006_USER": "worker-006",
|
||||
"MISSIONCORE_MQTT_WORKER_006_CONTOUR": "worker-006",
|
||||
"MISSIONCORE_MQTT_WORKER_006_PASSWORD": secrets.token_urlsafe(36),
|
||||
}
|
||||
ENV_PATH.write_text(
|
||||
@@ -51,6 +57,23 @@ def _environment() -> dict[str, str]:
|
||||
return values
|
||||
|
||||
|
||||
def _migrate_environment() -> None:
|
||||
values = _environment()
|
||||
additions: dict[str, str] = {}
|
||||
if "MISSIONCORE_DB_INGEST_PASSWORD" not in values:
|
||||
additions["MISSIONCORE_DB_INGEST_PASSWORD"] = secrets.token_urlsafe(36)
|
||||
if "MISSIONCORE_MQTT_WORKER_006_CONTOUR" not in values:
|
||||
additions["MISSIONCORE_MQTT_WORKER_006_CONTOUR"] = "worker-006"
|
||||
if not additions:
|
||||
return
|
||||
with ENV_PATH.open("a", encoding="utf-8", newline="\n") as stream:
|
||||
for name, value in additions.items():
|
||||
stream.write(f"{name}={value}\n")
|
||||
stream.flush()
|
||||
os.fsync(stream.fileno())
|
||||
os.chmod(ENV_PATH, 0o600)
|
||||
|
||||
|
||||
def _required(values: dict[str, str], name: str) -> str:
|
||||
value = values.get(name, "")
|
||||
if not value or value.startswith(PLACEHOLDER):
|
||||
@@ -58,6 +81,13 @@ def _required(values: dict[str, str], name: str) -> str:
|
||||
return value
|
||||
|
||||
|
||||
def _identifier(values: dict[str, str], name: str) -> str:
|
||||
value = _required(values, name)
|
||||
if SAFE_IDENTIFIER.fullmatch(value) is None:
|
||||
raise RuntimeError(f"{name} must contain a DNS-safe lowercase identifier")
|
||||
return value
|
||||
|
||||
|
||||
def _password_entry(path: Path, username: str, password: str, *, create: bool) -> None:
|
||||
command = [
|
||||
"docker",
|
||||
@@ -80,6 +110,22 @@ def _password_entry(path: Path, username: str, password: str, *, create: bool) -
|
||||
)
|
||||
|
||||
|
||||
def _prepare_password_entries(
|
||||
path: Path,
|
||||
ingest_user: str,
|
||||
ingest_password: str,
|
||||
worker_user: str,
|
||||
worker_password: str,
|
||||
) -> None:
|
||||
_password_entry(
|
||||
path,
|
||||
ingest_user,
|
||||
ingest_password,
|
||||
create=not path.exists(),
|
||||
)
|
||||
_password_entry(path, worker_user, worker_password, create=False)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
@@ -87,6 +133,11 @@ def main() -> None:
|
||||
action="store_true",
|
||||
help="create a private .env with generated local credentials",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--migrate",
|
||||
action="store_true",
|
||||
help="add newly required generated secrets without replacing existing values",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--mqtt-bind-address",
|
||||
default="127.0.0.1",
|
||||
@@ -95,17 +146,28 @@ def main() -> None:
|
||||
arguments = parser.parse_args()
|
||||
if arguments.initialize:
|
||||
_initialize_environment(arguments.mqtt_bind_address)
|
||||
if arguments.migrate:
|
||||
_migrate_environment()
|
||||
values = _environment()
|
||||
ingest_user = _required(values, "MISSIONCORE_MQTT_INGEST_USER")
|
||||
ingest_password = _required(values, "MISSIONCORE_MQTT_INGEST_PASSWORD")
|
||||
worker_user = _required(values, "MISSIONCORE_MQTT_WORKER_006_USER")
|
||||
worker_user = _identifier(values, "MISSIONCORE_MQTT_WORKER_006_USER")
|
||||
worker_contour = _identifier(
|
||||
values,
|
||||
"MISSIONCORE_MQTT_WORKER_006_CONTOUR",
|
||||
)
|
||||
worker_password = _required(values, "MISSIONCORE_MQTT_WORKER_006_PASSWORD")
|
||||
_required(values, "MISSIONCORE_DB_PASSWORD")
|
||||
|
||||
RUNTIME.mkdir(parents=True, exist_ok=True)
|
||||
password_path = RUNTIME / "passwords"
|
||||
_password_entry(password_path, ingest_user, ingest_password, create=True)
|
||||
_password_entry(password_path, worker_user, worker_password, create=False)
|
||||
_prepare_password_entries(
|
||||
password_path,
|
||||
ingest_user,
|
||||
ingest_password,
|
||||
worker_user,
|
||||
worker_password,
|
||||
)
|
||||
acl_path = RUNTIME / "acl"
|
||||
acl_path.write_text(
|
||||
"\n".join(
|
||||
@@ -114,8 +176,12 @@ def main() -> None:
|
||||
"topic read mission-core/v1/contours/+/agents/+/+",
|
||||
"topic read $SYS/broker/uptime",
|
||||
"",
|
||||
"# Agent username must equal its stable agent id.",
|
||||
"pattern write mission-core/v1/contours/+/agents/%u/+",
|
||||
"# Every credential is scoped to one contour and one stable agent.",
|
||||
f"user {worker_user}",
|
||||
(
|
||||
"topic write mission-core/v1/contours/"
|
||||
f"{worker_contour}/agents/{worker_user}/+"
|
||||
),
|
||||
"",
|
||||
]
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user