# 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 ```bash 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`) ```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`) ```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 ```bash # 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) ```bash # 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-Beispiele ```bash # 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 ```bash # 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}' ``` ### Update-Beispiele ```bash # 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 ``` ### WebSocket Commands (Backend → Agent) ```json // 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.