Fix Tunnel: ID-Sync Backend<->Agent, bidirektionaler Proxy, API-Key via Query-Param

- Backend gibt tunnel_id vor, Agent uebernimmt sie
- Proxy-Verbindung bidirektional: Daten vom Agent zurueck an TCP-Client
- Middleware akzeptiert api_key als Query-Parameter (fuer WebSocket)
- Getestet: OPNsense WebUI erreichbar ueber Tunnel
This commit is contained in:
cynfo3000 2026-02-28 07:52:38 +01:00
parent f47154787d
commit fa68e191f9
4 changed files with 53 additions and 20 deletions

View File

@ -116,8 +116,11 @@ func (h *Handler) handleTunnelOpen(msg Message) Message {
} }
targetPort := int(targetPortFloat) targetPort := int(targetPortFloat)
// Tunnel öffnen // Tunnel-ID vom Backend uebernehmen (falls vorhanden)
tunnelID, err := h.client.tunnelManager.OpenTunnel(targetHost, targetPort) backendTunnelID, _ := msg.Params["tunnel_id"].(string)
// Tunnel oeffnen
tunnelID, err := h.client.tunnelManager.OpenTunnel(targetHost, targetPort, backendTunnelID)
if err != nil { if err != nil {
response.Status = "error" response.Status = "error"
response.Error = fmt.Sprintf("Tunnel öffnen fehlgeschlagen: %v", err) response.Error = fmt.Sprintf("Tunnel öffnen fehlgeschlagen: %v", err)

View File

@ -33,9 +33,14 @@ func NewTunnelManager(client *Client) *TunnelManager {
} }
} }
func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int) (string, error) { func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int, tunnelID ...string) (string, error) {
// Eindeutige Tunnel-ID generieren // Tunnel-ID vom Backend uebernehmen oder selbst generieren
tunnelID := tm.generateTunnelID() id := ""
if len(tunnelID) > 0 && tunnelID[0] != "" {
id = tunnelID[0]
} else {
id = tm.generateTunnelID()
}
// Verbindung zum Ziel aufbauen // Verbindung zum Ziel aufbauen
target := fmt.Sprintf("%s:%d", targetHost, targetPort) target := fmt.Sprintf("%s:%d", targetHost, targetPort)
@ -46,7 +51,7 @@ func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int) (string,
// Tunnel erstellen // Tunnel erstellen
tunnel := &Tunnel{ tunnel := &Tunnel{
ID: tunnelID, ID: id,
TargetHost: targetHost, TargetHost: targetHost,
TargetPort: targetPort, TargetPort: targetPort,
Conn: conn, Conn: conn,
@ -56,13 +61,13 @@ func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int) (string,
// Tunnel registrieren // Tunnel registrieren
tm.mutex.Lock() tm.mutex.Lock()
tm.tunnels[tunnelID] = tunnel tm.tunnels[id] = tunnel
tm.mutex.Unlock() tm.mutex.Unlock()
// Data-Forwarding starten // Data-Forwarding starten
go tm.forwardData(tunnel) go tm.forwardData(tunnel)
return tunnelID, nil return id, nil
} }
func (tm *TunnelManager) CloseTunnel(tunnelID string) error { func (tm *TunnelManager) CloseTunnel(tunnelID string) error {

View File

@ -15,6 +15,10 @@ func APIKeyAuth(validKeys []string, next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
key := r.Header.Get("X-API-Key") key := r.Header.Get("X-API-Key")
// Fallback: Query-Parameter (fuer WebSocket-Verbindungen)
if key == "" {
key = r.URL.Query().Get("api_key")
}
if key == "" || !keySet[key] { if key == "" || !keySet[key] {
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized) http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
return return

View File

@ -21,6 +21,9 @@ type BackendTunnel struct {
Listener net.Listener Listener net.Listener
Active bool Active bool
CreatedAt time.Time CreatedAt time.Time
// Aktive Proxy-Verbindungen (fuer Rueckkanal)
proxyConn net.Conn
proxyMutex sync.Mutex
} }
type TunnelManager struct { type TunnelManager struct {
@ -86,12 +89,13 @@ func (tm *TunnelManager) OpenTunnel(agentID, targetHost string, targetPort int)
// Proxy-Server starten // Proxy-Server starten
go tm.runTunnelProxy(tunnel) go tm.runTunnelProxy(tunnel)
// Command an Agent senden // Command an Agent senden — tunnel_id vorgeben damit Backend und Agent dieselbe ID nutzen
cmd := map[string]interface{}{ cmd := map[string]interface{}{
"type": "command", "type": "command",
"id": tm.generateTunnelID(), "id": tm.generateTunnelID(),
"command": "tunnel_open", "command": "tunnel_open",
"params": map[string]interface{}{ "params": map[string]interface{}{
"tunnel_id": tunnelID,
"target_host": targetHost, "target_host": targetHost,
"target_port": targetPort, "target_port": targetPort,
}, },
@ -220,28 +224,38 @@ func (tm *TunnelManager) handleProxyConnection(tunnel *BackendTunnel, conn net.C
log.Printf("Tunnel %s: Proxy-Verbindung von %s", tunnel.ID, conn.RemoteAddr()) log.Printf("Tunnel %s: Proxy-Verbindung von %s", tunnel.ID, conn.RemoteAddr())
// Buffer für TCP-Data // Aktive Proxy-Verbindung registrieren (fuer Rueckkanal vom Agent)
tunnel.proxyMutex.Lock()
tunnel.proxyConn = conn
tunnel.proxyMutex.Unlock()
defer func() {
tunnel.proxyMutex.Lock()
tunnel.proxyConn = nil
tunnel.proxyMutex.Unlock()
log.Printf("Tunnel %s: Proxy-Verbindung beendet", tunnel.ID)
}()
// Buffer fuer TCP-Data
buffer := make([]byte, 32768) buffer := make([]byte, 32768)
for { for tunnel.Active {
n, err := conn.Read(buffer) n, err := conn.Read(buffer)
if err != nil { if err != nil {
if err.Error() != "EOF" { if err.Error() != "EOF" && tunnel.Active {
log.Printf("Tunnel %s: Proxy-Read-Fehler: %v", tunnel.ID, err) log.Printf("Tunnel %s: Proxy-Read-Fehler: %v", tunnel.ID, err)
} }
break break
} }
if n > 0 { if n > 0 {
// Daten über WebSocket an Agent weiterleiten // Daten ueber WebSocket an Agent weiterleiten
if err := tm.sendTunnelData(tunnel.ID, buffer[:n]); err != nil { if err := tm.sendTunnelData(tunnel.ID, buffer[:n]); err != nil {
log.Printf("Tunnel %s: Weiterleitung fehlgeschlagen: %v", tunnel.ID, err) log.Printf("Tunnel %s: Weiterleitung fehlgeschlagen: %v", tunnel.ID, err)
break break
} }
} }
} }
log.Printf("Tunnel %s: Proxy-Verbindung beendet", tunnel.ID)
} }
func (tm *TunnelManager) sendTunnelData(tunnelID string, data []byte) error { func (tm *TunnelManager) sendTunnelData(tunnelID string, data []byte) error {
@ -284,12 +298,19 @@ func (tm *TunnelManager) ProcessBinaryMessage(agentID string, data []byte) error
return fmt.Errorf("Tunnel %s nicht gefunden oder inaktiv", tunnelID) return fmt.Errorf("Tunnel %s nicht gefunden oder inaktiv", tunnelID)
} }
// Hier würden wir die Daten an aktive Proxy-Verbindungen weiterleiten // Daten an aktive Proxy-Verbindung weiterleiten
// Da das komplexer ist, implementieren wir erstmal nur das Logging tunnel.proxyMutex.Lock()
log.Printf("Tunnel %s: %d Bytes von Agent erhalten", tunnelID, len(payload)) conn := tunnel.proxyConn
tunnel.proxyMutex.Unlock()
// TODO: Implement actual forwarding to active proxy connections if conn == nil {
// Das würde einen Connection-Pool pro Tunnel erfordern return fmt.Errorf("Tunnel %s: keine aktive Proxy-Verbindung", tunnelID)
}
_, writeErr := conn.Write(payload)
if writeErr != nil {
return fmt.Errorf("Tunnel %s: Proxy-Write fehlgeschlagen: %w", tunnelID, writeErr)
}
return nil return nil
} }