rmm2/README.md

40 KiB

OPNsense RMM System

Remote Monitoring & Management fuer OPNsense Firewalls. Go-basierter Agent (v1.0.3) + Backend mit WebSocket-Kommunikation und Session-basierten TCP-Tunneln.

Architektur

┌────────────────┐   HTTPS/WSS    ┌────────────────┐   TCP Proxy     ┌────────────┐
│  OPNsense FW   │◄──────────────►│  RMM Backend   │◄───────────────│  Browser/  │
│  (FreeBSD)     │  :8443         │  (Linux)       │  :10000-20000  │  SSH/etc.  │
│                │                │                │                └────────────┘
│  rmm-agent     │  Heartbeat     │  REST API      │                ┌────────────┐
│  rmm-updater   │  WebSocket     │  WebSocket Hub │◄───────────────│  Frontend  │
│  - Collectors  │  Binary Msgs   │  Tunnel Mgr    │  Nginx Proxy   │  (React)   │
│  - WS Client   │                │  PostgreSQL +  │  :80/443       │  .20       │
│  - Tunnel Mgr  │                │  TimescaleDB   │                └────────────┘
└────────────────┘                └────────────────┘

Tunnel-Flow

Client ──► Backend:ProxyPort ──► WebSocket (Binary) ──► Agent ──► Zieldienst
           (TCP-Listener)        (Session-basiert)       (TCP)    (SSH/HTTPS/etc)
  • Jeder Tunnel oeffnet einen dynamischen Proxy-Port (10000-20000) auf dem Backend
  • Jede TCP-Verbindung am Proxy bekommt eine eigene Session
  • Der Agent oeffnet pro Session eine separate Ziel-Verbindung (Lazy Connect)
  • Mehrere gleichzeitige Verbindungen pro Tunnel moeglich
  • Tunnel bleiben offen wenn einzelne Sessions beendet werden

Agent-Faehigkeiten (Status: Implementiert)

Datensammlung (automatisch bei jedem Heartbeat, alle 60s)

Collector Daten Quelle
Hardware Hersteller, Modell, Seriennummer, BIOS kenv, sysctl
CPU Modell, Cores, Threads, Frequenz, Auslastung sysctl
Memory Total, Used, Free sysctl
Disks ZFS Datasets, Belegung, Mountpoints zfs list
Network Interfaces, IPs, Status, MAC, Statistiken ifconfig
Services Laufende OPNsense-Dienste (gefiltert) service -e, configctl, /usr/local/sbin/pluginctl
WireGuard Tunnel, Peers, Transfer, Handshake wg show
DHCP Aktive Leases (KEA/ISC/dnsmasq) Lease-Dateien, KEA JSON
Routing Routing-Tabelle netstat -rn
Gateways Gateway-Status, RTT, Loss configctl interface gateways status
Zertifikate Alle Certs + CAs, Ablaufdatum, Aussteller config.xml + PEM-Parsing
Plugins Installierte OPNsense-Plugins pkg info
Updates Pending Core + Package Updates opnsense-update -c, pkg upgrade -n
Cron System-Crontab + OPNsense config.xml Jobs crontab -l, config.xml

Remote-Commands (via WebSocket, on-demand)

Command API-Endpoint Beschreibung
exec POST /agents/{id}/exec Beliebigen Shell-Befehl ausfuehren
backup POST /agents/{id}/backup Config-Backup (/conf/config.xml) erstellen
update_check POST /agents/{id}/update-check Verfuegbare Updates pruefen
update POST /agents/{id}/update Normales Update (Core + Packages, opt. Reboot)
major_update POST /agents/{id}/major-update Major-Upgrade (2-Phasen: Base/Kernel → Reboot → Packages)
reboot POST /agents/{id}/reboot Reboot mit konfigurierbarer Verzoegerung
wg_add_peer POST /agents/{id}/wireguard/peers WireGuard-Peer anlegen (Keypair + Config)
wg_list_peers GET /agents/{id}/wireguard/peers Alle Peers einer WG-Instanz auflisten
wg_delete_peer DELETE /agents/{id}/wireguard/peers/{uuid} WireGuard-Peer loeschen
tunnel_connect POST /agents/{id}/tunnel TCP-Tunnel zu lokalem Dienst oeffnen
tunnel_disconnect DELETE /agents/{id}/tunnel/{tid} Tunnel schliessen
agent_update POST /agents/{id}/agent-update Agent-Binary remote aktualisieren (via WebSocket, Legacy)

Registrierte Agents

Agent-ID Name IP Rolle
6beb8dfd... OPN-PROD.intra.weidenhaupt.net 192.168.85.1 Produktion (READ-ONLY)
e92e87a6... OPN-REP.intra.weidenhaupt.net 192.168.85.33 Test/Replikation
1697d28e... OPN-TEST-UPDATE 192.168.85.19 Update-Tests

Komponenten

Agent (FreeBSD/OPNsense)

  • Collectors: Hardware, CPU, Memory, Disk, Network, Services, WireGuard, DHCP (KEA/ISC/dnsmasq), Routing, Gateways, Zertifikate, Plugins, Updates
  • WebSocket Client: Persistente Verbindung zum Backend, automatisches Reconnect mit Backoff
  • Command Handler: Remote-Befehlsausfuehrung, Tunnel-Connect/Disconnect
  • Tunnel Manager: Session-basierte TCP-Verbindungen zu lokalen Diensten

Backend (Linux)

  • REST API: Agent-Registrierung, Heartbeat, Systemdaten, Tunnel-Management
  • WebSocket Hub: Verwaltet Agent-Verbindungen, routet Commands und Binary-Messages
  • Tunnel Manager: Proxy-Listener, Session-Tracking, Ready-Handshake
  • PostgreSQL + TimescaleDB: Relationale Daten + Time-Series Metriken

Voraussetzungen

  • Build-System: Go 1.24+ (Cross-Compilation, kein CGO)
  • Backend-Server: Linux (Debian 12+), PostgreSQL 17+, TimescaleDB 2.x
  • Agent-Ziel: OPNsense / FreeBSD (amd64)

Installation (Backend)

1. PostgreSQL + TimescaleDB installieren

# Debian 12+ (Bookworm/Trixie)
apt install -y postgresql postgresql-client

# TimescaleDB Repository hinzufuegen
echo "deb https://packagecloud.io/timescale/timescaledb/debian/ bookworm main" \
  > /etc/apt/sources.list.d/timescaledb.list
curl -L https://packagecloud.io/timescale/timescaledb/gpgkey | gpg --dearmor \
  > /etc/apt/trusted.gpg.d/timescaledb.gpg
apt update

# TimescaleDB passend zur PG-Version installieren (z.B. PG 17)
apt install -y timescaledb-2-postgresql-17

# TimescaleDB in postgresql.conf aktivieren
echo "shared_preload_libraries = 'timescaledb'" >> /etc/postgresql/17/main/conf.d/timescaledb.conf
systemctl restart postgresql

2. Datenbank und User anlegen

sudo -u postgres psql <<EOF
CREATE USER rmm WITH PASSWORD 'SICHERES_PASSWORT';
CREATE DATABASE rmm OWNER rmm;
\c rmm
CREATE EXTENSION IF NOT EXISTS timescaledb;
GRANT ALL ON SCHEMA public TO rmm;
EOF

Verbindung testen:

psql -h 127.0.0.1 -U rmm -d rmm -c "SELECT default_version FROM pg_available_extensions WHERE name='timescaledb';"

3. Build

make all       # Backend (linux/amd64) + Agent (freebsd/amd64)
make backend   # Nur Backend
make agent     # Nur Agent

Binaries landen in build/. Kein CGO — reine Go-Compilation.

4. TLS-Zertifikate generieren

make certs

Oder manuell:

mkdir -p certs
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
  -keyout certs/server.key -out certs/server.crt \
  -days 3650 -nodes \
  -subj "/CN=rmm-backend/O=RMM" \
  -addext "subjectAltName=IP:BACKEND_IP,IP:127.0.0.1,DNS:localhost"

5. Backend konfigurieren (config.yaml)

listen_addr: ":8443"
tls_cert: "certs/server.crt"
tls_key: "certs/server.key"
api_keys:
  - "SICHERER_API_KEY"

database:
  host: "127.0.0.1"
  port: 5432
  user: "rmm"
  password: "SICHERES_PASSWORT"
  dbname: "rmm"
  sslmode: "disable"        # "require" wenn PG ueber Netz

Alternativ per Environment-Variablen: RMM_LISTEN_ADDR, RMM_TLS_CERT, RMM_TLS_KEY, RMM_DB_HOST, RMM_DB_PASSWORD.

6. Backend deployen und starten

# Verzeichnis anlegen
ssh root@BACKEND_IP "mkdir -p /opt/rmm/certs"

# Binary + Config + Certs kopieren
scp build/rmm-backend root@BACKEND_IP:/opt/rmm/rmm-backend
scp config.yaml root@BACKEND_IP:/opt/rmm/config.yaml
scp certs/server.* root@BACKEND_IP:/opt/rmm/certs/

# Systemd-Service einrichten
ssh root@BACKEND_IP 'cat > /etc/systemd/system/rmm-backend.service << EOF
[Unit]
Description=RMM Backend
After=network.target postgresql.service

[Service]
Type=simple
WorkingDirectory=/opt/rmm
ExecStart=/opt/rmm/rmm-backend /opt/rmm/config.yaml
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF'

ssh root@BACKEND_IP "systemctl daemon-reload && systemctl enable rmm-backend && systemctl start rmm-backend"

Oder per Makefile: make deploy-backend

7. Starten und verifizieren

# Status pruefen
systemctl status rmm-backend

# Log pruefen
journalctl -u rmm-backend -f

# API testen
curl -sk -H "X-API-Key: DEIN_API_KEY" https://BACKEND_IP:8443/api/v1/agents

Beim ersten Start:

  • Tabellen agents, system_data, config_backups, agent_events werden automatisch angelegt
  • Hypertable metrics wird erstellt (TimescaleDB)
  • Retention Policy (90 Tage) und Compression Policy (nach 7 Tagen) werden automatisch gesetzt

8. Datenbank-Schema (automatisch)

Das Backend erstellt folgendes Schema bei erstem Start:

Tabelle Typ Beschreibung
agents Relational Agent-Registry (ID, Name, Hostname, IP, Version, Heartbeat, customer_id, update_requested)
system_data Relational Letzte Systemdaten pro Agent (JSONB)
config_backups Relational OPNsense Config-Backups (dedupliziert per SHA256)
agent_events Relational Online/Offline/Connected Events
customers Relational Kundenstammdaten (Nummer, Name)
users Relational Benutzer (Username, bcrypt-Hash, Display-Name)
agent_firmware Relational Agent-Binaries pro Plattform+Version (BYTEA, SHA256)
metrics TimescaleDB Hypertable Time-Series Metriken (CPU, RAM, Disk, Network, Gateway)

TimescaleDB Policies:

  • Retention: Rohdaten werden nach 90 Tagen automatisch geloescht
  • Compression: Daten aelter als 7 Tage werden komprimiert (segmentby: agent_id+metric)

Installation (Agent / OPNsense)

Der Agent wird als OPNsense-Plugin installiert — mit WebGUI-Konfiguration unter Services > RMM Agent.

1. Agent bauen

make agent    # Erzeugt build/rmm-agent (freebsd/amd64)

2. Plugin installieren

# Binary + Installer auf die Firewall kopieren
scp build/rmm-agent root@OPNSENSE:/tmp/
scp opnsense-plugin/install.sh root@OPNSENSE:/tmp/

# Installer ausfuehren
ssh root@OPNSENSE '/bin/sh /tmp/install.sh'

Der Installer richtet alles ein:

  • Binary nach /usr/local/rmm/rmm-agent
  • rc.d Service (/usr/local/etc/rc.d/rmm_agent)
  • OPNsense MVC Plugin (Model, Controller, View)
  • configd Actions (start/stop/restart/status/reconfigure)
  • Plugin-Hook fuer Service-Integration
  • Setup-Script das Config aus dem Model generiert

3. WebGUI konfigurieren

  1. OPNsense WebGUI oeffnen
  2. Services > RMM Agent
  3. Felder ausfuellen:
    • Aktiviert: Ankreuzen
    • Backend URL: https://BACKEND_IP:8443
    • API Key: Backend API-Key eintragen
    • Agent Name: Optional (Standard: Hostname)
    • Heartbeat Intervall: 60 (Sekunden)
    • Self-Signed Zertifikate: Ankreuzen bei self-signed Backend-Certs
  4. Speichern & Anwenden klicken

Der Agent startet automatisch und meldet sich am Backend an.

4. Verifizieren

# Status in der WebGUI: Badge zeigt "Running"

# Oder per SSH:
ssh root@OPNSENSE 'service rmm_agent status'

# Auf dem Backend: Agent sollte erscheinen
curl -sk -H "X-API-Key: DEIN_API_KEY" https://BACKEND_IP:8443/api/v1/agents

Agent-Update (via Updater — empfohlen)

Der rmm-updater ist ein separater Daemon, der auf jeder Firewall neben dem Agent laeuft. Er prueft alle 60 Sekunden ob ein Update ansteht und fuehrt es automatisch durch.

Flow:

  1. Neue Agent-Binary im Frontend hochladen (Firmware-Seite)
  2. Update-Button bei einzelnem Agent oder "Alle updaten" klicken
  3. Updater erkennt den Flag, laedt die Binary vom Backend, stoppt Agent, ersetzt Binary, startet Agent
  4. Flag wird automatisch zurueckgesetzt

Technische Details:

  • Separates Go-Binary (rmm-updater), laeuft als rmm_updater rc.d Service
  • Liest die gleiche config.yaml wie der Agent (Backend-URL, API-Key)
  • Download ueber GET /api/v1/firmware/download?platform=freebsd
  • SHA256-Verifizierung vor Ersetzung
  • Automatisches Rollback bei fehlgeschlagenem Agent-Start
  • Backup der alten Binary als .bak waehrend des Updates
  • Logs: /var/log/rmm-updater.log

Manuelles Update (Fallback):

make agent
scp build/rmm-agent root@OPNSENSE:/tmp/
ssh root@OPNSENSE '/bin/sh -c "service rmm_agent stop; cp /tmp/rmm-agent /usr/local/rmm/rmm-agent; chmod +x /usr/local/rmm/rmm-agent; service rmm_agent start"'

Oder Plugin komplett neu installieren (install.sh ist idempotent — installiert Agent + Updater).

Hinweise

  • Agent-ID wird persistent in /usr/local/rmm/agent_id gespeichert — ueberlebt Neuinstallationen
  • Config wird bei "Speichern & Anwenden" aus dem OPNsense-Model nach /usr/local/rmm/config.yaml generiert
  • Logs: /var/log/rmm-agent.log
  • PID-File: /var/run/rmm_agent.pid

API

Agent-Management

Methode Endpoint Beschreibung
POST /api/v1/agent/register Agent registrieren (mit optionaler agent_id)
POST /api/v1/agent/heartbeat Systemdaten senden
POST /api/v1/agents Firewall pre-registrieren ({name, customer_id?})
GET /api/v1/agents Alle Agents auflisten
GET /api/v1/agents/{id} Agent + Systemdaten abrufen
GET /api/v1/agents/{id}/system Nur Systemdaten
DELETE /api/v1/agents/{id} Agent loeschen

Agent-Status

Status wird automatisch aus last_heartbeat berechnet (kein DB-Feld):

Status Bedingung
online Heartbeat < 2 Minuten
stale Heartbeat < 5 Minuten
offline Heartbeat > 5 Minuten
unknown Kein Heartbeat vorhanden

GET /agents liefert ein status Feld pro Agent, GET /agents/{id} ebenfalls.

Authentifizierung

Das Backend unterstuetzt zwei Auth-Methoden (CombinedAuth Middleware):

  • API-Key (Header X-API-Key): Fuer Agents und automatisierte Zugriffe
  • JWT Bearer Token (Header Authorization: Bearer <token>): Fuer Frontend-Benutzer
Methode Endpoint Beschreibung
POST /api/v1/auth/login Login (username + password → JWT Token, 8h Ablauf)
GET /api/v1/auth/me Eigene Benutzerdaten

Default-Admin wird automatisch beim ersten Start erstellt: admin / Start!123 (min. 6 Zeichen).

Kunden (Customers)

Methode Endpoint Beschreibung
GET /api/v1/customers Alle Kunden auflisten
POST /api/v1/customers Neuen Kunden anlegen ({number, name})
GET /api/v1/customers/{id} Einzelnen Kunden abrufen
PUT /api/v1/customers/{id} Kunden aktualisieren
DELETE /api/v1/customers/{id} Kunden loeschen
GET /api/v1/customers/{id}/agents Agents eines Kunden
PUT /api/v1/agents/{id}/customer Agent einem Kunden zuordnen
DELETE /api/v1/agents/{id}/customer Kundenzuordnung entfernen

Benutzer (Users)

Methode Endpoint Beschreibung
GET /api/v1/users Alle Benutzer auflisten
POST /api/v1/users Neuen Benutzer anlegen
PUT /api/v1/users/{id}/password Passwort aendern (min. 6 Zeichen)
DELETE /api/v1/users/{id} Benutzer loeschen

Firmware / Agent-Update

Multi-Plattform Firmware-Verwaltung (freebsd, linux, windows):

Methode Endpoint Beschreibung
GET /api/v1/firmware Alle Firmware-Versionen (pro Plattform)
GET /api/v1/firmware?platform=freebsd Firmware einer Plattform
POST /api/v1/firmware/upload?version=X&platform=Y Binary hochladen (Body = Raw Binary)
GET /api/v1/firmware/download?platform=Y Binary herunterladen (fuer Updater)
POST /api/v1/agents/{id}/request-update Update-Flag setzen (Updater fuehrt aus)
DELETE /api/v1/agents/{id}/request-update Update-Flag zuruecksetzen
POST /api/v1/agents/request-update-all Update-Flag fuer alle Agents setzen
POST /api/v1/agents/{id}/agent-update Legacy: Binary direkt via WebSocket pushen

Firmware-Upload Beispiel:

# FreeBSD Agent hochladen
curl -sk -X POST "https://192.168.85.13:8443/api/v1/firmware/upload?version=1.0.2&platform=freebsd" \
  -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  --data-binary @build/rmm-agent

# Linux Agent hochladen
curl -sk -X POST "https://192.168.85.13:8443/api/v1/firmware/upload?version=1.0.2&platform=linux" \
  -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  --data-binary @build/rmm-agent-linux

# Update fuer einzelnen Agent anfordern
curl -sk -X POST "https://192.168.85.13:8443/api/v1/agents/e92e87a6.../request-update" \
  -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9"

# Update fuer alle Agents anfordern
curl -sk -X POST "https://192.168.85.13:8443/api/v1/agents/request-update-all" \
  -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9"

Agent-Events

Methode Endpoint Beschreibung
GET /api/v1/agents/events Alle Events (?limit=N&type=X)
GET /api/v1/agents/{id}/events Events eines Agents (?limit=N&type=X)

Event-Typen: online, offline, stale, connected, disconnected

Events werden automatisch geschrieben bei:

  • WebSocket Connect/Disconnect (connected/disconnected)
  • Inaktivitaets-Monitor erkennt Statuswechsel (online/stale/offline)
# Alle Events (letzte 10)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/events?limit=10"

# Nur Offline-Events eines Agents
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/events?type=offline"

WebSocket

Methode Endpoint Beschreibung
GET /api/v1/agent/ws?agent_id=X&api_key=X WebSocket-Verbindung (Agent)
GET /api/v1/hub/status Hub-Status (connected agents, aktive Tunnel)

Updates & Reboot

Methode Endpoint Beschreibung
POST /api/v1/agents/{id}/update-check Verfuegbare Updates pruefen
POST /api/v1/agents/{id}/update Normales Update (Core + Packages)
POST /api/v1/agents/{id}/major-update Major-Upgrade auf Zielversion
POST /api/v1/agents/{id}/reboot Reboot ausfuehren

Metriken (TimescaleDB)

Methode Endpoint Beschreibung
GET /api/v1/agents/{id}/metrics Rohe Metriken abfragen (?metric=X&from=&to=&limit=N)
GET /api/v1/agents/{id}/metrics/summary Aggregierte Metriken (?metric=X&bucket=5+minutes&from=&to=)

Metriken werden automatisch bei jedem Heartbeat geschrieben (alle 60s): cpu_usage, memory_used_percent, memory_used_bytes, memory_total_bytes, uptime_seconds, disk_used_percent, disk_used_bytes, interface_rx_bytes, interface_tx_bytes, gateway_rtt_ms, gateway_loss_percent

Retention: 90 Tage (Raw), Compression nach 7 Tagen.

Config-Backup

Methode Endpoint Beschreibung
POST /api/v1/agents/{id}/backup Backup jetzt ausloesen (via WebSocket)
GET /api/v1/agents/{id}/backups Alle Backups auflisten (?limit=N)
GET /api/v1/agents/{id}/backups/{id|latest} Backup herunterladen (?format=xml fuer Raw-XML)
DELETE /api/v1/agents/{id}/backups/{id} Einzelnes Backup loeschen
GET /api/v1/agents/{id}/backups/diff?a={id}&b={id} Zeilenweises Diff zwischen zwei Backups

Backups werden dedupliziert: gleicher SHA256-Hash = kein neuer Eintrag. Config-XML wird Base64-kodiert vom Agent zum Backend uebertragen und als Klartext in PostgreSQL gespeichert.

Tunnel-Management

Methode Endpoint Beschreibung
POST /api/v1/agents/{id}/tunnel Tunnel oeffnen
GET /api/v1/agents/{id}/tunnels Aktive Tunnel auflisten
DELETE /api/v1/agents/{id}/tunnel/{tid} Tunnel schliessen
POST /api/v1/agents/{id}/exec Remote-Command ausfuehren

Tunnel-Beispiele

# SSH-Tunnel zur OPNsense
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnel \
  -d '{"target_host":"127.0.0.1","target_port":22}'

# Response: {"tunnel_id":"xxx","proxy_port":10000,...}
# Verbinden:
ssh -p 10000 root@192.168.85.13

# WebGUI-Tunnel (OPNsense auf Port 4444)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnel \
  -d '{"target_host":"127.0.0.1","target_port":4444}'

# Response: {"tunnel_id":"xxx","proxy_port":10001,...}
# Im Browser: https://192.168.85.13:10001/

# Aktive Tunnel auflisten
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnels

# Tunnel schliessen
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X DELETE \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/tunnel/TUNNEL_ID

Zugangsdaten

Backend:   https://192.168.85.13:8443
API-Key:   01532e5a7c9e70bf2757df77a2f5b9b9

Agent-IDs:
  6beb8dfd38ecf03eab6c0f21b4ac7bee  OPN-PROD.intra.weidenhaupt.net  (192.168.85.1, READ-ONLY)
  e92e87a684b20db15d8d2344583c5887  OPN-REP.intra.weidenhaupt.net   (192.168.85.33)
  1697d28e57bc126534afc282b5de8ee0  OPN-TEST-UPDATE                 (192.168.85.19)

Agent-Beispiele

# Alle Agents auflisten (ID, Name, IP, Version, letzter Heartbeat)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  https://192.168.85.13:8443/api/v1/agents

# Einzelner Agent mit Systemdaten
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887

# Nur Systemdaten (Hardware, CPU, RAM, Disks, Services, etc.)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/system

# Agent loeschen
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X DELETE \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887

# Hub-Status (verbundene Agents, aktive Tunnel)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  https://192.168.85.13:8443/api/v1/hub/status

# Remote-Command ausfuehren
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/exec \
  -d '{"command":"uptime","timeout":30}'

WireGuard-Peer Management

Methode Endpoint Beschreibung
POST /api/v1/agents/{id}/wireguard/peers Neuen Peer anlegen
GET /api/v1/agents/{id}/wireguard/peers Alle Peers auflisten (?server_name=X)
DELETE /api/v1/agents/{id}/wireguard/peers/{uuid} Peer loeschen

Peer anlegen — Parameter:

Parameter Pflicht Default Beschreibung
name Ja Peer-Name (z.B. "VPN_MUELLER")
allowed_ips Nein 0.0.0.0/0 AllowedIPs fuer Client-Config
dns Nein DNS-Server fuer Client-Config
server_name Nein ALWAYSON_VPN Name der WG-Server-Instanz
endpoint Nein WAN-IP:Port Oeffentlicher Endpoint (Auto-Detect ueber Default-Route)

Ablauf beim Anlegen:

  1. Keypair wird auf der Firewall generiert (wg genkey/wg pubkey)
  2. Naechste freie IP aus dem Tunnel-Subnetz wird automatisch vergeben
  3. Peer wird in config.xml eingetragen und dem Server zugewiesen
  4. configctl wireguard configure laedt die Aenderung
  5. Response enthaelt die komplette Client-Config (ready for import)

Beispiele:

# Peer anlegen (erster Peer bekommt .2, zweiter .3, etc.)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers \
  -d '{"name":"VPN_MUELLER","allowed_ips":"0.0.0.0/0","dns":"10.172.100.210"}'

# Response:
# {
#   "status": "ok",
#   "data": {
#     "peer_uuid": "abc123...",
#     "name": "VPN_MUELLER",
#     "client_ip": "10.33.144.2",
#     "endpoint": "79.238.149.26:8080",
#     "client_config": "[Interface]\nPrivateKey = ...\nAddress = 10.33.144.2/32\n..."
#   }
# }

# Zweiten Peer anlegen (bekommt automatisch .3)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers \
  -d '{"name":"VPN_SCHMIDT","allowed_ips":"0.0.0.0/0","dns":"10.172.100.210"}'

# Peer mit eigenem Endpoint (z.B. DynDNS)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers \
  -d '{"name":"VPN_EXTERN","allowed_ips":"0.0.0.0/0","endpoint":"vpn.kunde.de:8080"}'

# Alle Peers auflisten
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers

# Peers einer bestimmten Instanz auflisten
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers?server_name=ALWAYSON_VPN"

# Peer loeschen (UUID aus der List-Response)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X DELETE \
  https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/wireguard/peers/89c10e2d-148e-11f1-a7af-7c5a1c6c7b73

Metriken-Beispiele

# CPU-Auslastung der letzten 24h
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics?metric=cpu_usage"

# RAM-Nutzung mit Zeitraum
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics?metric=memory_used_percent&from=2026-02-28T00:00:00Z&to=2026-02-28T23:59:59Z"

# 5-Minuten-Durchschnitte der CPU
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics/summary?metric=cpu_usage&bucket=5+minutes"

# Stuendliche RAM-Zusammenfassung
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics/summary?metric=memory_used_percent&bucket=1+hour"

# Alle Metriken eines Agents (alle Typen, letzte 100)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/metrics?limit=100"

Backup-Beispiele

# Backup ausloesen (Produktion .1)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/backup

# Backup ausloesen (Test .33)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/backup

# Alle Backups auflisten
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  https://192.168.85.13:8443/api/v1/agents/6beb8dfd38ecf03eab6c0f21b4ac7bee/backups

# Neuestes Backup als XML herunterladen
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/backups/latest?format=xml" \
  -o config-backup.xml

# Diff zwischen Backup 1 und 3
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" \
  "https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/backups/diff?a=1&b=3"

Update-Beispiele

# Verfuegbare Updates pruefen
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/update-check

# Normales Update (ohne Reboot)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/update

# Normales Update mit Reboot
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/update \
  -d '{"reboot":true}'

# Major-Upgrade auf Version 26.7
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/major-update \
  -d '{"version":"26.7","reboot":true}'

# Reboot (5s Verzoegerung default)
curl -sk -H "X-API-Key: 01532e5a7c9e70bf2757df77a2f5b9b9" -X POST \
  https://192.168.85.13:8443/api/v1/agents/e92e87a684b20db15d8d2344583c5887/reboot

Update-Ablauf

Normales Update (Patches innerhalb der gleichen Version):

  1. opnsense-update — Core-Update herunterladen
  2. pkg upgrade -y — Paket-Updates installieren
  3. Nachpruefung: opnsense-update -c — Exit 1 = Reboot noetig

Response enthaelt reboot_required: true/false fuer das Frontend.

Major-Upgrade (z.B. 25.7 → 26.1):

  1. opnsense-update -r <version> -bkp — Repository wechseln + Base + Kernel + Packages herunterladen
  2. pkg-static update -f — Repository-Katalog forciert aktualisieren
  3. pkg-static upgrade -y — Alle Pakete auf neue Version installieren
  4. Reboot erforderlich (immer bei Major-Upgrade)

Major-Upgrades setzen reboot_required: true und reboot ist standardmaessig aktiviert.

WebSocket Commands (Backend → Agent)

// Remote-Befehl ausfuehren
{"type":"command","id":"cmd1","command":"exec","params":{"command":"uptime","timeout":30}}

// Update-Check (liefert core_update_available, pending_packages, reboot_required)
{"type":"command","id":"cmd2","command":"update_check"}

// Normales Update (reboot optional)
{"type":"command","id":"cmd3","command":"update","params":{"reboot":false}}

// Major-Update (reboot default true)
{"type":"command","id":"cmd4","command":"major_update","params":{"version":"26.1","reboot":true}}

// Reboot
{"type":"command","id":"cmd5","command":"reboot","params":{"delay":5}}

// Config-Backup (liest /conf/config.xml, liefert Base64 + SHA256 Hash)
{"type":"command","id":"cmd8","command":"backup","params":{}}

// Tunnel-Session oeffnen (automatisch vom Backend gesendet)
{"type":"command","id":"cmd6","command":"tunnel_connect","params":{"session_id":"xxx","tunnel_id":"xxx","target_host":"127.0.0.1","target_port":22}}

// Tunnel-Session schliessen
{"type":"command","id":"cmd7","command":"tunnel_disconnect","params":{"session_id":"xxx"}}

Binary WebSocket Messages (Tunnel-Daten)

Format: [session_id_length:1][session_id][tcp_payload]

Tunnel-Daten werden als Binary WebSocket Messages uebertragen (kein Base64-Overhead).

Frontend

React + Vite + TailwindCSS Web-Frontend, deployed als statische Dateien auf Nginx.

  • Server: 192.168.85.20 (Debian 13, Nginx)
  • Tech: React 18, Vite, TailwindCSS, Recharts, Zustand, lucide-react
  • Theme: Dark mit Orange-Akzent, komplett deutsch
  • Auth: JWT Login (8h Token)

Seiten

Seite Beschreibung
Dashboard Uebersicht aller Firewalls, Status-Kacheln, AgentPanel per Klick
Firewalls Suchbare Tabelle, AgentPanel mit Tabs (Uebersicht, Interfaces, Dienste, Tunnel, VPN, WireGuard, Routen, DHCP, Zertifikate, Backups, Agent); Firewall hinzufuegen/loeschen
Tunnel Globale Tunnel-Uebersicht ueber alle Firewalls
Firmware Multi-Plattform Firmware-Upload, Agent-Update-Trigger
Kunden CRUD Kundenverwaltung
Einstellungen Benutzerverwaltung, Passworte aendern

Build & Deploy

cd frontend
npm run build                                              # ~330 KB JS, ~32 KB CSS
ssh root@192.168.85.20 'rm -rf /var/www/html/assets/*'     # Alte Hashes loeschen!
scp -r dist/* root@192.168.85.20:/var/www/html/

Wichtig: Vor Deploy immer assets/ loeschen — Vite generiert Hash-basierte Dateinamen, alte Dateien bleiben sonst auf dem Server und Browser-Cache liefert 404.

Dateistruktur

rmm/
├── Makefile
├── README.md
├── agent/
│   ├── main.go                  # Entry, Registration, Heartbeat-Loop, WS-Start
│   ├── config/
│   │   └── config.go            # YAML Config Loader
│   ├── client/
│   │   └── client.go            # HTTP Client (Register, Heartbeat)
│   ├── collector/
│   │   ├── hardware.go          # Hersteller, Modell, BIOS
│   │   ├── cpu.go               # CPU-Modell, Cores, Usage
│   │   ├── memory.go            # RAM total/used/free
│   │   ├── disk.go              # ZFS Datasets, Belegung
│   │   ├── network.go           # Interfaces, IPs, Status
│   │   ├── services.go          # Laufende Dienste
│   │   ├── wireguard.go         # WG-Tunnel, Peers, Transfer
│   │   ├── dhcp.go              # KEA/ISC/dnsmasq Leases
│   │   ├── opnsense.go          # OPN-Version, FreeBSD, Hostname, Uptime
│   │   ├── routing.go           # Routing-Tabelle (netstat -rn)
│   │   ├── gateways.go          # Gateway-Status (configctl)
│   │   ├── certificates.go      # Zertifikate + CAs (config.xml + PEM)
│   │   ├── plugins.go           # Installierte Plugins (pkg info)
│   │   ├── updates.go           # Pending Updates (core + packages)
│   │   └── cron.go              # Cron-Jobs (System-Crontab + OPNsense config.xml)
│   └── ws/
│       ├── client.go            # WebSocket Client, Reconnect, Ping/Pong
│       ├── handler.go           # Command Handler (exec, backup, tunnel_connect/disconnect)
│       ├── wireguard.go        # WireGuard Peer Management (add/delete/list)
│       └── tunnel.go            # Session-basierter Tunnel Manager
├── backend/
│   ├── main.go                  # Entry, TLS, HTTP Server
│   ├── config/
│   │   └── config.go            # YAML Config Loader
│   ├── db/
│   │   └── postgres.go          # PostgreSQL + TimescaleDB (Agents, Backups, Metriken)
│   ├── models/
│   │   ├── agent.go             # Agent, Register/Heartbeat Structs
│   │   └── system.go            # SystemData, alle Collector-Structs
│   ├── monitor/
│   │   └── monitor.go           # Inaktivitaets-Monitor (prueft Heartbeat, schreibt Events)
│   ├── api/
│   │   ├── handlers.go          # REST API Handler (inkl. Agent-Status + Events)
│   │   ├── tunnel_proxy.go      # Tunnel + Exec + Update REST Endpoints
│   │   ├── backup.go            # Config-Backup Endpoints (trigger, list, download, diff)
│   │   ├── wireguard.go         # WireGuard Peer API Endpoints
│   │   ├── firmware.go          # Firmware Upload/Download/Info, Update-Request
│   │   ├── auth.go              # Login, JWT Token, /me Endpoint
│   │   ├── customers.go         # Kunden CRUD + Agent-Zuordnung
│   │   ├── users.go             # Benutzer CRUD + Passwort-Aenderung
│   │   ├── metrics.go           # Metriken-Abfrage Endpoints (raw + summary)
│   │   └── middleware.go        # CombinedAuth (API-Key OR JWT), CORS, Logging
│   └── ws/
│       ├── handler.go           # WebSocket Upgrade, Connection R/W Pumps
│       ├── hub.go               # Agent-Registry, Message Routing
│       └── tunnel.go            # Session-basierter Tunnel + Proxy Manager
├── updater/
│   ├── main.go                  # Separater Update-Daemon (pollt Firmware, ersetzt Agent-Binary)
│   └── go.mod
├── frontend/
│   ├── src/
│   │   ├── App.jsx              # Router + ProtectedRoute
│   │   ├── api/client.js        # API Client (auth, agents, firmware, tunnels, etc.)
│   │   ├── stores/auth.js       # Zustand Auth Store (JWT Token)
│   │   ├── layouts/AppLayout.jsx # Sidebar + Outlet
│   │   ├── components/
│   │   │   ├── AgentPanel.jsx   # Side Panel mit Tabs (Uebersicht bis Agent)
│   │   │   └── StatusBadge.jsx  # Online/Offline/Stale Badge
│   │   └── pages/
│   │       ├── Dashboard.jsx    # Status-Kacheln + Agent-Liste
│   │       ├── Agents.jsx       # Firewall-Tabelle
│   │       ├── AgentDetail.jsx  # Detail-Ansicht (full page)
│   │       ├── Tunnels.jsx      # Globale Tunnel-Uebersicht
│   │       ├── Firmware.jsx     # Multi-Plattform Firmware + Update-Trigger
│   │       ├── Customers.jsx    # Kunden CRUD
│   │       ├── SettingsPage.jsx # User-Management
│   │       └── Login.jsx        # JWT Login
│   ├── vite.config.js
│   └── package.json
├── opnsense-plugin/
│   ├── install.sh               # Self-contained Installer (Agent + Updater + MVC Plugin)
│   ├── build.sh
│   └── src/                     # MVC Model, Controllers, Views, configd, rc.d
└── build/                       # Kompilierte Binaries (rmm-backend, rmm-agent, rmm-updater)

Technische Details

  • PostgreSQL 17 + TimescaleDB 2.x: Agent-Registry, Config-Backups, Time-Series Metriken
  • Pure-Go: pgx/v5 Driver, kein CGO noetig
  • Cross-Compilation: Backend linux/amd64, Agent freebsd/amd64
  • TLS: Self-signed Zertifikate (automatisch generiert)
  • WebSocket: gorilla/websocket, Binary Messages fuer Tunnel-Daten
  • Persistente Agent-ID: Ueberlebt Agent- und Backend-Neustarts
  • FreeBSD-kompatibel: Volle Pfade (/usr/bin/netstat etc.), daemon statt nohup

Neue Features (v1.0.3)

OPNsense Plugin WebGUI Fix

Der SettingsController wurde gefixt:

  • Config-Parsing: Korrektes Parsen von rmmagent[general][KEY] Formular-Feldern (OPNsense MVC-Konvention)
  • Initialer config.xml Eintrag: Beim ersten Speichern wird automatisch der <rmmagent> Knoten in der config.xml angelegt, sodass die WebGUI-Konfiguration sofort funktioniert

Pre-Registration von Firewalls

Firewalls koennen vorab im System registriert werden, bevor der Agent installiert wird:

  • API: POST /api/v1/agents mit name (Pflicht) und optionalem customer_id
  • Der Agent wird mit hostname="(ausstehend)" angelegt
  • Bei der eigentlichen Agent-Registration (POST /api/v1/agent/register) wird automatisch nach Name gematcht
  • DB-Funktion: GetAgentByName fuer das automatische Pre-Registration Matching
  • Frontend: "Firewall hinzufuegen" Dialog mit Installationsanleitung und Copy-Buttons fuer die Befehle

Firewall loeschen

  • Firewalls koennen direkt im Frontend ueber das Papierkorb-Icon im AgentPanel geloescht werden
  • Loescht den Agent und alle zugehoerigen Daten (Systemdaten, Events, Backups, Metriken)

Service-Filter

Boot- und Init-Scripts werden nicht mehr in der Dienste-Liste angezeigt:

  • Gefiltert: bgfsck, cleanvar, dmesg, hostid, ldconfig, motd, newsyslog, ntpd, resolv, syslogd, var und andere System-Boot-Scripts
  • Angezeigt: Nur echte OPNsense-Dienste (OpenVPN, Unbound, DHCPd, WireGuard, etc.)
  • pluginctl wird mit vollem Pfad /usr/local/sbin/pluginctl aufgerufen (FreeBSD-Kompatibilitaet)

OPNsense Business Edition Lizenz-Anzeige

Im Frontend im Uebersicht-Tab des AgentPanel wird die Lizenzinformation angezeigt:

  • Product Name: z.B. "OPNsense Business Edition"
  • Subscription Key: Lizenzschluessel
  • Ablaufdatum: Wann die Lizenz ablaeuft
  • Restlaufzeit: Visueller Farbbalken (gruen/gelb/rot je nach verbleibender Zeit)

Firmware Management (Frontend)

Die Firmware-Seite im Frontend bietet:

  • Multi-Platform Upload: Agent-Binaries fuer FreeBSD, Linux und Windows hochladen
  • Versions-Uebersicht: Alle hochgeladenen Firmware-Versionen pro Plattform mit SHA256-Hash und Groesse
  • Agent-Update-Buttons: Update fuer einzelne Agents oder "Alle updaten" mit einem Klick

Frontend Agent-Tab

Der Agent-Tab im AgentPanel zeigt:

  • Agent-Info: Version, Agent-ID, Plattform, Uptime
  • Update-Button: Direktes Agent-Update anfordern
  • Befehle: Remote-Shell-Befehle direkt aus dem Frontend ausfuehren

Hinweise

  • Backend braucht kein root — laeuft als normaler User auf Port 8443
  • Agent braucht root auf OPNsense (fuer Hardware-/Service-Daten)
  • OPNsense WebGUI typischerweise auf Port 4444 (nicht 443)
  • FreeBSD csh hat kein $() — Agent verwendet /bin/sh -c fuer Commands
  • Beim Backend-Deploy: alte Prozesse killen bevor neuer startet (sonst "bind: address already in use")

Lizenz

Intern.