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)
// Tunnel öffnen
tunnelID, err := h.client.tunnelManager.OpenTunnel(targetHost, targetPort)
// Tunnel-ID vom Backend uebernehmen (falls vorhanden)
backendTunnelID, _ := msg.Params["tunnel_id"].(string)
// Tunnel oeffnen
tunnelID, err := h.client.tunnelManager.OpenTunnel(targetHost, targetPort, backendTunnelID)
if err != nil {
response.Status = "error"
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) {
// Eindeutige Tunnel-ID generieren
tunnelID := tm.generateTunnelID()
func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int, tunnelID ...string) (string, error) {
// Tunnel-ID vom Backend uebernehmen oder selbst generieren
id := ""
if len(tunnelID) > 0 && tunnelID[0] != "" {
id = tunnelID[0]
} else {
id = tm.generateTunnelID()
}
// Verbindung zum Ziel aufbauen
target := fmt.Sprintf("%s:%d", targetHost, targetPort)
@ -46,7 +51,7 @@ func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int) (string,
// Tunnel erstellen
tunnel := &Tunnel{
ID: tunnelID,
ID: id,
TargetHost: targetHost,
TargetPort: targetPort,
Conn: conn,
@ -56,13 +61,13 @@ func (tm *TunnelManager) OpenTunnel(targetHost string, targetPort int) (string,
// Tunnel registrieren
tm.mutex.Lock()
tm.tunnels[tunnelID] = tunnel
tm.tunnels[id] = tunnel
tm.mutex.Unlock()
// Data-Forwarding starten
go tm.forwardData(tunnel)
return tunnelID, nil
return id, nil
}
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) {
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] {
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
return

View File

@ -21,6 +21,9 @@ type BackendTunnel struct {
Listener net.Listener
Active bool
CreatedAt time.Time
// Aktive Proxy-Verbindungen (fuer Rueckkanal)
proxyConn net.Conn
proxyMutex sync.Mutex
}
type TunnelManager struct {
@ -86,12 +89,13 @@ func (tm *TunnelManager) OpenTunnel(agentID, targetHost string, targetPort int)
// Proxy-Server starten
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{}{
"type": "command",
"id": tm.generateTunnelID(),
"command": "tunnel_open",
"params": map[string]interface{}{
"tunnel_id": tunnelID,
"target_host": targetHost,
"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())
// 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)
for {
for tunnel.Active {
n, err := conn.Read(buffer)
if err != nil {
if err.Error() != "EOF" {
if err.Error() != "EOF" && tunnel.Active {
log.Printf("Tunnel %s: Proxy-Read-Fehler: %v", tunnel.ID, err)
}
break
}
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 {
log.Printf("Tunnel %s: Weiterleitung fehlgeschlagen: %v", tunnel.ID, err)
break
}
}
}
log.Printf("Tunnel %s: Proxy-Verbindung beendet", tunnel.ID)
}
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)
}
// Hier würden wir die Daten an aktive Proxy-Verbindungen weiterleiten
// Da das komplexer ist, implementieren wir erstmal nur das Logging
log.Printf("Tunnel %s: %d Bytes von Agent erhalten", tunnelID, len(payload))
// Daten an aktive Proxy-Verbindung weiterleiten
tunnel.proxyMutex.Lock()
conn := tunnel.proxyConn
tunnel.proxyMutex.Unlock()
// TODO: Implement actual forwarding to active proxy connections
// Das würde einen Connection-Pool pro Tunnel erfordern
if conn == nil {
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
}