# 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) | ### 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 ```bash # 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 ``` ```bash # 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/ ``` ### WebSocket Commands (Backend → Agent) ```json // Remote-Befehl ausfuehren {"type":"command","id":"cmd1","command":"exec","params":{"command":"uptime","timeout":30}} // Tunnel-Session oeffnen (wird automatisch vom Backend gesendet) {"type":"command","id":"cmd2","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":"cmd3","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.