rmm2/README.md
cynfo3000 ebc70f17eb Update-Commands: update, major_update, update_check, reboot
Agent:
- update: opnsense-update + pkg upgrade -y, optionaler Reboot
- major_update: opnsense-update -r <version>, optionaler Reboot
- update_check: Prueft Core- und Paket-Updates, liefert Details
- reboot: Shutdown mit konfigurierbarer Verzoegerung

Backend:
- Synchrone API-Endpoints (warten auf Agent-Response)
- Response-Waiter-System im Hub (Command-ID basiert)
- Konfigurierbare Timeouts pro Command-Typ
- POST /agents/{id}/update[-check] und /major-update und /reboot

README mit Update-Beispielen und API-Doku aktualisiert
2026-02-28 09:01:04 +01:00

11 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

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

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 erstellen

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

# Response: {"tunnel_id":"xxx","proxy_port":10000,...}

# Verbinden:
ssh -p 10000 root@backend-ip
# WebGUI-Tunnel (OPNsense auf Port 4444)
curl -sk -H "X-API-Key: KEY" -X POST \
  https://backend:8443/api/v1/agents/AGENT_ID/tunnel \
  -d '{"target_host":"127.0.0.1","target_port":4444}'

# Response: {"tunnel_id":"xxx","proxy_port":10001,...}

# Im Browser: https://backend-ip:10001/

Update-Beispiele

# Verfuegbare Updates pruefen
curl -sk -H "X-API-Key: KEY" -X POST \
  https://backend:8443/api/v1/agents/AGENT_ID/update-check

# Normales Update (ohne Reboot)
curl -sk -H "X-API-Key: KEY" -X POST \
  https://backend:8443/api/v1/agents/AGENT_ID/update

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

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

# Reboot (5s Verzoegerung default)
curl -sk -H "X-API-Key: KEY" -X POST \
  https://backend:8443/api/v1/agents/AGENT_ID/reboot

WebSocket Commands (Backend → Agent)

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

// Update-Check
{"type":"command","id":"cmd2","command":"update_check"}

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

// Major-Update
{"type":"command","id":"cmd4","command":"major_update","params":{"version":"26.7","reboot":true}}

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

// 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 REST Endpoints
│   │   └── 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.