rmm2/README.md

18 KiB

OPNsense RMM System

Remote Monitoring & Management fuer OPNsense Firewalls. Go-basierter Agent + 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      │
│  - Collectors  │  WebSocket     │  WebSocket Hub │
│  - WS Client   │  Binary Msgs   │  Tunnel Mgr    │
│  - Tunnel Mgr  │                │  SQLite DB     │
└────────────────┘                └────────────────┘

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 Dienste, Status service -e, configctl
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

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
tunnel_connect POST /agents/{id}/tunnel TCP-Tunnel zu lokalem Dienst oeffnen
tunnel_disconnect DELETE /agents/{id}/tunnel/{tid} Tunnel schliessen

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
  • SQLite DB: Persistente Agent- und Systemdaten

Build

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

Binaries in build/. Benoetigt Go 1.22+.

Konfiguration

Backend (config.yaml)

listen_addr: ":8443"
tls_cert: "certs/server.crt"   # Wird automatisch generiert
tls_key: "certs/server.key"
db_path: "rmm.db"
api_keys:
  - "dein-api-key-hier"

Agent (config.yaml)

backend_url: "https://backend-ip:8443"
api_key: "dein-api-key-hier"
interval_seconds: 60
agent_name: "opnsense-fw01"
insecure: true                  # Self-signed Certs akzeptieren

Deployment

Backend

# Build + Copy
make backend
scp build/rmm-backend user@backend:/pfad/rmm-backend

# Starten (als normaler User, kein root noetig)
cd /pfad && nohup ./rmm-backend config.yaml > rmm-backend.log 2>&1 &

Agent (OPNsense/FreeBSD)

# Build + Copy
make agent
scp build/rmm-agent root@opnsense:/usr/local/rmm/rmm-agent

# Starten via FreeBSD daemon
daemon -f -p /var/run/rmm-agent.pid -o /usr/local/rmm/rmm-agent.log \
  /usr/local/rmm/rmm-agent --config /usr/local/rmm/config.yaml --insecure

Wichtig: Agent-ID wird persistent in /usr/local/rmm/agent_id gespeichert.

API

Agent-Management

Methode Endpoint Beschreibung
POST /api/v1/agent/register Agent registrieren (mit optionaler agent_id)
POST /api/v1/agent/heartbeat Systemdaten senden
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

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

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 SQLite 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-ID: e92e87a684b20db15d8d2344583c5887  (OPN-REP.intra.weidenhaupt.net)

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}'

Backup-Beispiele

# Backup ausloesen
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/e92e87a684b20db15d8d2344583c5887/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).

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)
│   └── ws/
│       ├── client.go            # WebSocket Client, Reconnect, Ping/Pong
│       ├── handler.go           # Command Handler (exec, tunnel_connect/disconnect)
│       └── tunnel.go            # Session-basierter Tunnel Manager
├── backend/
│   ├── main.go                  # Entry, TLS, HTTP Server
│   ├── config/
│   │   └── config.go            # YAML Config Loader
│   ├── db/
│   │   └── sqlite.go            # SQLite (Agent + Systemdaten)
│   ├── models/
│   │   ├── agent.go             # Agent, Register/Heartbeat Structs
│   │   └── system.go            # SystemData, alle Collector-Structs
│   ├── api/
│   │   ├── handlers.go          # REST API Handler
│   │   ├── tunnel_proxy.go      # Tunnel + Exec + Update REST Endpoints
│   │   ├── backup.go            # Config-Backup Endpoints (trigger, list, download, diff)
│   │   └── middleware.go        # API-Key Auth, Logging
│   └── ws/
│       ├── handler.go           # WebSocket Upgrade, Connection R/W Pumps
│       ├── hub.go               # Agent-Registry, Message Routing
│       └── tunnel.go            # Session-basierter Tunnel + Proxy Manager
└── build/                       # Kompilierte Binaries

Technische Details

  • CGO-frei: CGO_ENABLED=0, pure-Go SQLite (modernc.org/sqlite)
  • 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

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.