WebSocket-Infrastruktur: bidirektionaler Command-Kanal + TCP-Tunnel
- Agent: WS-Client mit Reconnect, Command-Handler (exec, tunnel_open/close/data)
- Backend: WS-Hub, Tunnel-Manager mit dynamischen Proxy-Ports (10000-20000)
- API: /agents/{id}/exec, /tunnel, /tunnels, /proxy/{tunnel_id}
- Binary WebSocket Messages fuer effiziente Tunnel-Daten
- gorilla/websocket (pure Go, kein CGO)
- README aktualisiert
This commit is contained in:
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/cynfo/rmm-backend/db"
|
||||
"github.com/cynfo/rmm-backend/models"
|
||||
"github.com/cynfo/rmm-backend/ws"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
@@ -22,13 +23,17 @@ func NewHandler(database *db.Database) *Handler {
|
||||
}
|
||||
|
||||
// SetupRoutes - Alle API-Routes registrieren
|
||||
func (h *Handler) SetupRoutes(mux *http.ServeMux) {
|
||||
func (h *Handler) SetupRoutes(mux *http.ServeMux, hub *ws.Hub) {
|
||||
// Bestehende Agent-Routes
|
||||
mux.HandleFunc("POST /api/v1/agent/register", h.registerAgent)
|
||||
mux.HandleFunc("POST /api/v1/agent/heartbeat", h.heartbeat)
|
||||
mux.HandleFunc("GET /api/v1/agents", h.listAgents)
|
||||
mux.HandleFunc("GET /api/v1/agents/{id}", h.getAgent)
|
||||
mux.HandleFunc("GET /api/v1/agents/{id}/system", h.getSystemData)
|
||||
mux.HandleFunc("DELETE /api/v1/agents/{id}", h.deleteAgent)
|
||||
|
||||
// WebSocket und Tunnel-Routes
|
||||
h.setupTunnelRoutes(mux, hub)
|
||||
}
|
||||
|
||||
func generateID() string {
|
||||
|
||||
@@ -0,0 +1,195 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/cynfo/rmm-backend/ws"
|
||||
)
|
||||
|
||||
func (h *Handler) setupTunnelRoutes(mux *http.ServeMux, hub *ws.Hub) {
|
||||
// Command-Endpoints
|
||||
mux.HandleFunc("POST /api/v1/agents/{id}/exec", h.executeCommand(hub))
|
||||
mux.HandleFunc("POST /api/v1/agents/{id}/tunnel", h.openTunnel(hub))
|
||||
mux.HandleFunc("DELETE /api/v1/agents/{id}/tunnel/{tunnel_id}", h.closeTunnel(hub))
|
||||
mux.HandleFunc("GET /api/v1/agents/{id}/tunnels", h.listTunnels(hub))
|
||||
|
||||
// HTTP-Proxy durch Tunnel
|
||||
mux.HandleFunc("GET /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
|
||||
mux.HandleFunc("POST /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
|
||||
mux.HandleFunc("PUT /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
|
||||
mux.HandleFunc("DELETE /api/v1/agents/{id}/proxy/{tunnel_id}/", h.httpProxy(hub))
|
||||
|
||||
// WebSocket-Endpoint
|
||||
mux.HandleFunc("GET /api/v1/agent/ws", ws.NewWebSocketHandler(hub))
|
||||
|
||||
// Hub-Status
|
||||
mux.HandleFunc("GET /api/v1/hub/status", h.hubStatus(hub))
|
||||
}
|
||||
|
||||
func (h *Handler) executeCommand(hub *ws.Hub) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
agentID := r.PathValue("id")
|
||||
|
||||
// Request parsen
|
||||
var req struct {
|
||||
Command string `json:"command"`
|
||||
Timeout int `json:"timeout,omitempty"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Command == "" {
|
||||
writeError(w, http.StatusBadRequest, "Parameter 'command' erforderlich")
|
||||
return
|
||||
}
|
||||
|
||||
if req.Timeout == 0 {
|
||||
req.Timeout = 30
|
||||
}
|
||||
|
||||
// Prüfen ob Agent verbunden ist
|
||||
if !hub.IsAgentConnected(agentID) {
|
||||
writeError(w, http.StatusServiceUnavailable, "Agent nicht verbunden")
|
||||
return
|
||||
}
|
||||
|
||||
// Command-Message erstellen
|
||||
cmd := map[string]interface{}{
|
||||
"type": "command",
|
||||
"id": generateID(),
|
||||
"command": "exec",
|
||||
"params": map[string]interface{}{
|
||||
"command": req.Command,
|
||||
"timeout": req.Timeout,
|
||||
},
|
||||
}
|
||||
|
||||
cmdJSON, _ := json.Marshal(cmd)
|
||||
|
||||
// Command an Agent senden
|
||||
if err := hub.SendToAgent(agentID, cmdJSON); err != nil {
|
||||
log.Printf("Command senden fehlgeschlagen: %v", err)
|
||||
writeError(w, http.StatusInternalServerError, "Command senden fehlgeschlagen")
|
||||
return
|
||||
}
|
||||
|
||||
// Response zurückgeben
|
||||
writeJSON(w, http.StatusOK, map[string]interface{}{
|
||||
"message": "Command gesendet",
|
||||
"command_id": cmd["id"],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) openTunnel(hub *ws.Hub) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
agentID := r.PathValue("id")
|
||||
|
||||
// Request parsen
|
||||
var req struct {
|
||||
TargetHost string `json:"target_host"`
|
||||
TargetPort int `json:"target_port"`
|
||||
}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültige Anfrage")
|
||||
return
|
||||
}
|
||||
|
||||
if req.TargetHost == "" || req.TargetPort <= 0 {
|
||||
writeError(w, http.StatusBadRequest, "target_host und target_port erforderlich")
|
||||
return
|
||||
}
|
||||
|
||||
// Tunnel öffnen
|
||||
tunnel, err := hub.GetTunnelManager().OpenTunnel(agentID, req.TargetHost, req.TargetPort)
|
||||
if err != nil {
|
||||
log.Printf("Tunnel öffnen fehlgeschlagen: %v", err)
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Response
|
||||
writeJSON(w, http.StatusCreated, map[string]interface{}{
|
||||
"tunnel_id": tunnel.ID,
|
||||
"proxy_port": tunnel.ProxyPort,
|
||||
"target_host": tunnel.TargetHost,
|
||||
"target_port": tunnel.TargetPort,
|
||||
"created_at": tunnel.CreatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) closeTunnel(hub *ws.Hub) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tunnelID := r.PathValue("tunnel_id")
|
||||
|
||||
// Tunnel schließen
|
||||
if err := hub.GetTunnelManager().CloseTunnel(tunnelID); err != nil {
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, map[string]string{
|
||||
"message": "Tunnel geschlossen",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) listTunnels(hub *ws.Hub) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
agentID := r.PathValue("id")
|
||||
|
||||
// Tunnel für Agent abrufen
|
||||
tunnels := hub.GetTunnelManager().GetAgentTunnels(agentID)
|
||||
|
||||
// Response formatieren
|
||||
result := make([]map[string]interface{}, len(tunnels))
|
||||
for i, tunnel := range tunnels {
|
||||
result[i] = map[string]interface{}{
|
||||
"tunnel_id": tunnel.ID,
|
||||
"proxy_port": tunnel.ProxyPort,
|
||||
"target_host": tunnel.TargetHost,
|
||||
"target_port": tunnel.TargetPort,
|
||||
"active": tunnel.Active,
|
||||
"created_at": tunnel.CreatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, result)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) httpProxy(hub *ws.Hub) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
tunnelID := r.PathValue("tunnel_id")
|
||||
|
||||
// HTTP-Proxy durch Tunnel
|
||||
if err := hub.GetTunnelManager().CreateHTTPProxy(tunnelID, w, r); err != nil {
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) hubStatus(hub *ws.Hub) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
stats := hub.GetAgentStats()
|
||||
connectedAgents := hub.GetConnectedAgents()
|
||||
|
||||
response := map[string]interface{}{
|
||||
"status": "running",
|
||||
"connected_agents": connectedAgents,
|
||||
"stats": stats,
|
||||
"timestamp": time.Now(),
|
||||
}
|
||||
|
||||
writeJSON(w, http.StatusOK, response)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user