diff --git a/host_agent/pyproject.toml b/host_agent/pyproject.toml index e51ef3c..4d1de49 100644 --- a/host_agent/pyproject.toml +++ b/host_agent/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "mrblake-netbox-store-agent" -version = "0.1.0" +version = "0.1.1" description = "Fail-closed host agent for curated NetBox plugin lifecycle operations" readme = "README.md" requires-python = ">=3.11" diff --git a/host_agent/src/netbox_store_agent/managed_files.py b/host_agent/src/netbox_store_agent/managed_files.py index d657612..08dafed 100644 --- a/host_agent/src/netbox_store_agent/managed_files.py +++ b/host_agent/src/netbox_store_agent/managed_files.py @@ -56,7 +56,10 @@ class ManagedFiles: def _read_previous(self, path: Path) -> PreviousFile: self._validate_target(path) if not path.exists(): - return PreviousFile(path, None, 0o640) + # These generated files contain no secrets and must remain readable + # by the unprivileged NetBox service after the root agent replaces + # them atomically. + return PreviousFile(path, None, 0o644) metadata = path.stat() if metadata.st_size > self.MAX_MANAGED_FILE_BYTES: raise PolicyError("managed file exceeds the safe backup limit") diff --git a/host_agent/tests/test_managed_files.py b/host_agent/tests/test_managed_files.py index 109a864..89628f8 100644 --- a/host_agent/tests/test_managed_files.py +++ b/host_agent/tests/test_managed_files.py @@ -47,6 +47,11 @@ class ManagedFilesTests(unittest.TestCase): self.assertEqual(self.config.paths.include_path.read_text(), "old include") self.assertEqual(self.config.paths.requirements_path.read_text(), "old requirements") + def test_new_managed_files_are_readable_by_netbox(self) -> None: + previous = self.files._read_previous(self.config.paths.include_path) + + self.assertEqual(previous.mode, 0o644) + def test_conflicting_locked_distribution_rejected(self) -> None: other_requirement = {**self.plugin.requirements[0], "version": "2.0.0"} other = ManagedPlugin( diff --git a/store/templates/installation.php b/store/templates/installation.php index 04609c2..973663f 100644 --- a/store/templates/installation.php +++ b/store/templates/installation.php @@ -62,7 +62,58 @@ sudo systemctl restart netbox curl -sS -o /dev/null -w 'HTTP %{http_code} in %{time_total}s\n' --max-time 10 http://127.0.0.1:8001/login/
Die Control-Schnittstelle wird von NetBox nicht für den normalen WSGI-Betrieb benötigt. Der Test muss innerhalb von zehn Sekunden einen HTTP-Status ausgeben.
dry_run verändert das System nicht. Für Installieren, Aktualisieren, Aktivieren und Entfernen wird der mitgelieferte Linux Host-Agent benötigt. Installiere ihn aus host_agent/, prüfe /etc/netbox-store-agent/agent.toml und stelle anschließend execution_mode auf agent.
dry_run verändert das System nicht. Für Installieren, Aktualisieren, Aktivieren und Entfernen wird der mitgelieferte Linux Host-Agent benötigt. Installiere ihn in eine eigene virtuelle Umgebung:
sudo apt update
+sudo apt install -y python3-venv util-linux curl
+sudo python3 -m venv /opt/netbox-store-agent/venv
+sudo /opt/netbox-store-agent/venv/bin/pip install --upgrade pip
+sudo /opt/netbox-store-agent/venv/bin/pip install \
+ 'mrblake-netbox-store-agent @ https://git.mrblake.cc/MrBlake/Netbox-Store/archive/main.tar.gz#subdirectory=host_agent'
+
+sudo install -d -o root -g root -m 0750 /etc/netbox-store-agent
+curl -fsSL https://git.mrblake.cc/MrBlake/Netbox-Store/raw/branch/main/host_agent/examples/agent.toml \
+ | sudo tee /etc/netbox-store-agent/agent.toml >/dev/null
+sudo chmod 0600 /etc/netbox-store-agent/agent.toml
+
+curl -fsSL https://git.mrblake.cc/MrBlake/Netbox-Store/raw/branch/main/host_agent/systemd/netbox-store-agent.service \
+ | sudo tee /etc/systemd/system/netbox-store-agent.service >/dev/null
+curl -fsSL https://git.mrblake.cc/MrBlake/Netbox-Store/raw/branch/main/host_agent/systemd/netbox-store-agent.socket \
+ | sudo tee /etc/systemd/system/netbox-store-agent.socket >/dev/null
+sudo sed -i 's#/usr/local/bin/netbox-store-agent#/opt/netbox-store-agent/venv/bin/netbox-store-agent#' \
+ /etc/systemd/system/netbox-store-agent.service
+ Ermittle anschließend UID und GID des NetBox-Dienstkontos:
+id netbox
+id -u netbox
+id -g netbox
+ Öffne /etc/netbox-store-agent/agent.toml und trage die ausgegebenen Zahlen bei allowed_peer_uids, allowed_peer_gids und socket_gid ein. Setze außerdem unter [policy] die tatsächlich installierte NetBox-Version. Pfade und erlaubte Hosts des Beispiels sind bereits auf die Standardinstallation unter /opt/netbox und diesen Store ausgerichtet.
Erzeuge die beiden verwalteten Startdateien und binde sie einmalig ein:
+echo 'STORE_PLUGINS = []' | sudo tee /opt/netbox/netbox/netbox/store_plugins.py >/dev/null
+echo '# Generated by netbox-store-agent. Do not edit.' | sudo tee /opt/netbox/local_requirements_store.txt >/dev/null
+sudo chmod 0644 /opt/netbox/netbox/netbox/store_plugins.py /opt/netbox/local_requirements_store.txt
+
+grep -qxF -- '-r /opt/netbox/local_requirements_store.txt' /opt/netbox/local_requirements.txt \
+ || echo '-r /opt/netbox/local_requirements_store.txt' | sudo tee -a /opt/netbox/local_requirements.txt
+ Füge in configuration.py direkt nach der vorhandenen PLUGINS-Liste einmalig Folgendes ein:
from store_plugins import STORE_PLUGINS
+
+PLUGINS += STORE_PLUGINS
+ Prüfe zunächst die Agent-Konfiguration und den Socket im sicheren Dry-Run-Modus:
+sudo /opt/netbox-store-agent/venv/bin/netbox-store-agent \
+ --config /etc/netbox-store-agent/agent.toml validate-config
+sudo systemctl daemon-reload
+sudo systemctl enable --now netbox-store-agent.socket
+sudo -u netbox /opt/netbox-store-agent/venv/bin/netbox-store-agent capabilities
+ Wenn dieser Test erfolgreich ist, ändere in agent.toml den Eintrag unter [agent] bewusst auf dry_run = false. Stelle anschließend die Plugin-Konfiguration um:
"execution_mode": "agent",
+"agent_socket_path": "/run/netbox-store-agent/agent.sock",
+"agent_timeout": 30,
+"default_dry_run": False,
+ Aktiviere die reale Ausführung erst nach Prüfung aller Werte:
+sudo systemctl restart netbox-store-agent.service
+sudo /opt/netbox/venv/bin/python -m py_compile /opt/netbox/netbox/netbox/configuration.py
+sudo systemctl restart netbox netbox-rq
+sudo -u netbox /opt/netbox-store-agent/venv/bin/netbox-store-agent capabilities
+
Source-Kandidaten werden nur nach Admin-Freigabe verarbeitet. Der Agent prüft Commitbindung, Größe und SHA-256, baut daraus lokal ein Wheel und installiert nicht direkt aus einem beweglichen Branch.
diff --git a/store/tests/run.php b/store/tests/run.php index 48f0568..37ab6f2 100644 --- a/store/tests/run.php +++ b/store/tests/run.php @@ -500,6 +500,11 @@ test('public and admin templates render safely with complete artifact evidence', assertTrue(str_contains($installation, 'python -m py_compile')); assertTrue(str_contains($installation, 'control_socket_disable = True')); assertTrue(str_contains($installation, 'http://127.0.0.1:8001/login/')); + assertTrue(str_contains($installation, 'mrblake-netbox-store-agent @ https://git.mrblake.cc')); + assertTrue(str_contains($installation, 'netbox-store-agent.socket')); + assertTrue(str_contains($installation, 'dry_run = false')); + assertTrue(str_contains($installation, '"execution_mode": "agent"')); + assertTrue(str_contains($installation, '"default_dry_run": False')); $admin = $view->render('admin/dashboard', [ 'title' => 'Admin', 'currentPath' => '/admin', 'adminEnabled' => true, 'adminUser' => 'admin', 'csrf' => 'safe-token', 'ok' => '', 'error' => '', 'sources' => $state['sources'],