diff --git a/docs/API.md b/docs/API.md index 9df5c7f76..018ff7378 100644 --- a/docs/API.md +++ b/docs/API.md @@ -228,8 +228,9 @@ POST /api/config/import # Import encrypted config **Authentication**: Requires one of: - Active session (when logged in with password) -- API token via X-API-Token header -- ALLOW_UNPROTECTED_EXPORT=true (for unprotected instances) +- API token via X-API-Token header +- Private network access (automatic for homelab users on 192.168.x.x, 10.x.x.x, 172.16.x.x) +- ALLOW_UNPROTECTED_EXPORT=true (to explicitly allow on public networks) **Export includes**: All nodes, credentials (encrypted), alerts, webhooks, email config, system settings, and guest metadata (custom console URLs) diff --git a/docs/SECURITY.md b/docs/SECURITY.md index d628fdd1a..e91ba11a6 100644 --- a/docs/SECURITY.md +++ b/docs/SECURITY.md @@ -282,7 +282,7 @@ This checks: ## Troubleshooting -**Export blocked?** Login with password, set API_TOKEN, or set ALLOW_UNPROTECTED_EXPORT=true +**Export blocked?** You're on a public network - login with password, set API_TOKEN, or set ALLOW_UNPROTECTED_EXPORT=true **Rate limited?** Wait 1 minute and try again **Can't login?** Check PULSE_AUTH_USER and PULSE_AUTH_PASS environment variables **API access denied?** Verify API_TOKEN is correct (use original token, not hash) diff --git a/internal/api/router.go b/internal/api/router.go index 0cd5e16c7..18b299031 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -553,13 +553,30 @@ ENABLE_AUDIT_LOG=true http.Error(w, "Unauthorized", http.StatusUnauthorized) return } - } else if os.Getenv("ALLOW_UNPROTECTED_EXPORT") != "true" { - // No auth configured and unprotected export not explicitly allowed - log.Warn(). - Str("ip", req.RemoteAddr). - Msg("Export blocked - authentication required") - http.Error(w, "Export requires authentication (set ALLOW_UNPROTECTED_EXPORT=true for homelab use)", http.StatusForbidden) - return + } else { + // No auth configured - check if this is a homelab/private network + clientIP := utils.GetClientIP(req.RemoteAddr, + req.Header.Get("X-Forwarded-For"), + req.Header.Get("X-Real-IP")) + + isPrivate := utils.IsPrivateIP(clientIP) + allowUnprotected := os.Getenv("ALLOW_UNPROTECTED_EXPORT") == "true" + + if !isPrivate && !allowUnprotected { + // Public network access without auth - definitely block + log.Warn(). + Str("ip", req.RemoteAddr). + Bool("private_network", isPrivate). + Msg("Export blocked - public network requires authentication") + http.Error(w, "Export requires authentication on public networks", http.StatusForbidden) + return + } else if isPrivate && !allowUnprotected { + // Private network but ALLOW_UNPROTECTED_EXPORT not set - show helpful message + log.Info(). + Str("ip", req.RemoteAddr). + Msg("Export allowed - private network with no auth") + // Continue - allow export on private networks for homelab users + } } // Log successful export attempt @@ -609,13 +626,30 @@ ENABLE_AUDIT_LOG=true http.Error(w, "Unauthorized", http.StatusUnauthorized) return } - } else if os.Getenv("ALLOW_UNPROTECTED_EXPORT") != "true" { - // No auth configured and unprotected import not explicitly allowed - log.Warn(). - Str("ip", req.RemoteAddr). - Msg("Import blocked - authentication required") - http.Error(w, "Import requires authentication (set ALLOW_UNPROTECTED_EXPORT=true for homelab use)", http.StatusForbidden) - return + } else { + // No auth configured - check if this is a homelab/private network + clientIP := utils.GetClientIP(req.RemoteAddr, + req.Header.Get("X-Forwarded-For"), + req.Header.Get("X-Real-IP")) + + isPrivate := utils.IsPrivateIP(clientIP) + allowUnprotected := os.Getenv("ALLOW_UNPROTECTED_EXPORT") == "true" + + if !isPrivate && !allowUnprotected { + // Public network access without auth - definitely block + log.Warn(). + Str("ip", req.RemoteAddr). + Bool("private_network", isPrivate). + Msg("Import blocked - public network requires authentication") + http.Error(w, "Import requires authentication on public networks", http.StatusForbidden) + return + } else if isPrivate && !allowUnprotected { + // Private network but ALLOW_UNPROTECTED_EXPORT not set - show helpful message + log.Info(). + Str("ip", req.RemoteAddr). + Msg("Import allowed - private network with no auth") + // Continue - allow import on private networks for homelab users + } } // Log successful import attempt diff --git a/pulse-linux-amd64 b/pulse-linux-amd64 index 1df712713..a45f1c8dc 100755 Binary files a/pulse-linux-amd64 and b/pulse-linux-amd64 differ