(out chan<- wsMsg, dockerEvents <-chan interface{}, backoff *time.Duration)
| 1372 | } |
| 1373 | |
| 1374 | func connectOnce(out chan<- wsMsg, dockerEvents <-chan interface{}, backoff *time.Duration) (connected bool, err error) { |
| 1375 | server := cfgManager.GetConfig().PatchmonServer |
| 1376 | if server == "" { |
| 1377 | return false, nil |
| 1378 | } |
| 1379 | apiID := cfgManager.GetCredentials().APIID |
| 1380 | apiKey := cfgManager.GetCredentials().APIKey |
| 1381 | |
| 1382 | // Convert http(s) -> ws(s) |
| 1383 | wsURL := server |
| 1384 | if strings.HasPrefix(wsURL, "https://") { |
| 1385 | wsURL = "wss://" + strings.TrimPrefix(wsURL, "https://") |
| 1386 | } else if strings.HasPrefix(wsURL, "http://") { |
| 1387 | wsURL = "ws://" + strings.TrimPrefix(wsURL, "http://") |
| 1388 | } else if strings.HasPrefix(wsURL, "wss://") || strings.HasPrefix(wsURL, "ws://") { |
| 1389 | // Already a WebSocket URL, use as-is - no conversion needed |
| 1390 | _ = wsURL // URL is already in correct format, no action needed |
| 1391 | } else { |
| 1392 | // No protocol prefix - assume HTTPS and use WSS |
| 1393 | logger.WithField("server", logutil.Sanitize(server)).Warn("Server URL missing protocol prefix, assuming HTTPS") |
| 1394 | wsURL = "wss://" + wsURL |
| 1395 | } |
| 1396 | if strings.HasSuffix(wsURL, "/") { |
| 1397 | wsURL = strings.TrimRight(wsURL, "/") |
| 1398 | } |
| 1399 | wsURL = wsURL + "/api/" + cfgManager.GetConfig().APIVersion + "/agents/ws" |
| 1400 | header := http.Header{} |
| 1401 | header.Set("X-API-ID", apiID) |
| 1402 | header.Set("X-API-KEY", apiKey) |
| 1403 | |
| 1404 | // SECURITY: Configure WebSocket dialer for insecure connections if needed |
| 1405 | // WARNING: This exposes the agent to man-in-the-middle attacks! |
| 1406 | dialer := websocket.DefaultDialer |
| 1407 | if cfgManager.GetConfig().SkipSSLVerify || client.IsSkipSSLVerifyEnvSet() { |
| 1408 | logger.Warn("TLS verification disabled for WebSocket") |
| 1409 | // Operator-gated insecure TLS for lab/air-gapped deployments with self-signed certs. |
| 1410 | dialer = &websocket.Dialer{ |
| 1411 | TLSClientConfig: &tls.Config{ |
| 1412 | InsecureSkipVerify: true, |
| 1413 | }, |
| 1414 | } |
| 1415 | } |
| 1416 | |
| 1417 | conn, _, err := dialer.Dial(wsURL, header) |
| 1418 | if err != nil { |
| 1419 | return false, err |
| 1420 | } |
| 1421 | // Reset reconnect backoff now that the session is live. Without this, a |
| 1422 | // long-lived agent that has escalated backoff from earlier drops would wait |
| 1423 | // the full capped interval on the next disconnect even though the link |
| 1424 | // recovered immediately. |
| 1425 | connected = true |
| 1426 | *backoff = time.Second |
| 1427 | |
| 1428 | // Create a done channel to signal goroutines to stop when connection closes |
| 1429 | done := make(chan struct{}) |
| 1430 | defer func() { |
| 1431 | close(done) // Signal all goroutines to stop |
no test coverage detected