Document real host agent installations
CI / php-store (push) Waiting to run
CI / python-components (push) Waiting to run

This commit is contained in:
2026-08-24 22:50:48 +02:00
parent 0440b6a48f
commit a4cecec7b9
5 changed files with 67 additions and 3 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "mrblake-netbox-store-agent" name = "mrblake-netbox-store-agent"
version = "0.1.0" version = "0.1.1"
description = "Fail-closed host agent for curated NetBox plugin lifecycle operations" description = "Fail-closed host agent for curated NetBox plugin lifecycle operations"
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
@@ -56,7 +56,10 @@ class ManagedFiles:
def _read_previous(self, path: Path) -> PreviousFile: def _read_previous(self, path: Path) -> PreviousFile:
self._validate_target(path) self._validate_target(path)
if not path.exists(): 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() metadata = path.stat()
if metadata.st_size > self.MAX_MANAGED_FILE_BYTES: if metadata.st_size > self.MAX_MANAGED_FILE_BYTES:
raise PolicyError("managed file exceeds the safe backup limit") raise PolicyError("managed file exceeds the safe backup limit")
+5
View File
@@ -47,6 +47,11 @@ class ManagedFilesTests(unittest.TestCase):
self.assertEqual(self.config.paths.include_path.read_text(), "old include") self.assertEqual(self.config.paths.include_path.read_text(), "old include")
self.assertEqual(self.config.paths.requirements_path.read_text(), "old requirements") 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: def test_conflicting_locked_distribution_rejected(self) -> None:
other_requirement = {**self.plugin.requirements[0], "version": "2.0.0"} other_requirement = {**self.plugin.requirements[0], "version": "2.0.0"}
other = ManagedPlugin( other = ManagedPlugin(
+52 -1
View File
@@ -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/</code></pre> curl -sS -o /dev/null -w 'HTTP %{http_code} in %{time_total}s\n' --max-time 10 http://127.0.0.1:8001/login/</code></pre>
<p>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.</p> <p>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.</p>
<h2>4. Sicheren Host-Agent einrichten</h2> <h2>4. Sicheren Host-Agent einrichten</h2>
<p><code>dry_run</code> verändert das System nicht. Für Installieren, Aktualisieren, Aktivieren und Entfernen wird der mitgelieferte Linux Host-Agent benötigt. Installiere ihn aus <code>host_agent/</code>, prüfe <code>/etc/netbox-store-agent/agent.toml</code> und stelle anschließend <code>execution_mode</code> auf <code>agent</code>.</p> <p><code>dry_run</code> 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:</p>
<pre><code>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</code></pre>
<p>Ermittle anschließend UID und GID des NetBox-Dienstkontos:</p>
<pre><code>id netbox
id -u netbox
id -g netbox</code></pre>
<p>Öffne <code>/etc/netbox-store-agent/agent.toml</code> und trage die ausgegebenen Zahlen bei <code>allowed_peer_uids</code>, <code>allowed_peer_gids</code> und <code>socket_gid</code> ein. Setze außerdem unter <code>[policy]</code> die tatsächlich installierte NetBox-Version. Pfade und erlaubte Hosts des Beispiels sind bereits auf die Standardinstallation unter <code>/opt/netbox</code> und diesen Store ausgerichtet.</p>
<p>Erzeuge die beiden verwalteten Startdateien und binde sie einmalig ein:</p>
<pre><code>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</code></pre>
<p>Füge in <code>configuration.py</code> direkt nach der vorhandenen <code>PLUGINS</code>-Liste einmalig Folgendes ein:</p>
<pre><code>from store_plugins import STORE_PLUGINS
PLUGINS += STORE_PLUGINS</code></pre>
<p>Prüfe zunächst die Agent-Konfiguration und den Socket im sicheren Dry-Run-Modus:</p>
<pre><code>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</code></pre>
<p>Wenn dieser Test erfolgreich ist, ändere in <code>agent.toml</code> den Eintrag unter <code>[agent]</code> bewusst auf <code>dry_run = false</code>. Stelle anschließend die Plugin-Konfiguration um:</p>
<pre><code>"execution_mode": "agent",
"agent_socket_path": "/run/netbox-store-agent/agent.sock",
"agent_timeout": 30,
"default_dry_run": False,</code></pre>
<p>Aktiviere die reale Ausführung erst nach Prüfung aller Werte:</p>
<pre><code>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</code></pre>
<aside class="notice"><strong>Zwei bewusste Freigaben:</strong> Für eine reale Aktion müssen sowohl <code>execution_mode = "agent"</code> im NetBox-Plugin als auch <code>dry_run = false</code> im Host-Agent gesetzt sein. Kontrolliere im Bestätigungsdialog außerdem, dass „Nur prüfen (Dry-Run)“ nicht markiert ist. Eine Installation lässt das neue Plugin zunächst deaktiviert; führe danach separat „Aktivieren“ aus.</aside>
<p>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.</p> <p>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.</p>
<aside class="notice warning"><strong>Wichtig:</strong> Source-Builds führen den Build-Code des freigegebenen Repository-Commits aus. Gib nur vertrauenswürdige Kandidaten frei.</aside> <aside class="notice warning"><strong>Wichtig:</strong> Source-Builds führen den Build-Code des freigegebenen Repository-Commits aus. Gib nur vertrauenswürdige Kandidaten frei.</aside>
</section> </section>
+5
View File
@@ -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, 'python -m py_compile'));
assertTrue(str_contains($installation, 'control_socket_disable = True')); assertTrue(str_contains($installation, 'control_socket_disable = True'));
assertTrue(str_contains($installation, 'http://127.0.0.1:8001/login/')); 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', [ $admin = $view->render('admin/dashboard', [
'title' => 'Admin', 'currentPath' => '/admin', 'adminEnabled' => true, 'adminUser' => 'admin', 'title' => 'Admin', 'currentPath' => '/admin', 'adminEnabled' => true, 'adminUser' => 'admin',
'csrf' => 'safe-token', 'ok' => '', 'error' => '', 'sources' => $state['sources'], 'csrf' => 'safe-token', 'ok' => '', 'error' => '', 'sources' => $state['sources'],