mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 23:41:48 +01:00
fix(security): add proper authorization to sensitive endpoints
- /api/agent-install-command: require admin + settings:write scope Previously only RequireAuth, allowing any authenticated user to mint high-privilege API tokens (host-agent:manage) - /api/system/ssh-config: require settings:write scope Previously any authenticated token could modify ~/.ssh/config - /api/system/verify-temperature-ssh: require settings:write scope Previously any authenticated token could trigger SSH connection attempts to arbitrary nodes (network scanning risk) - /api/diagnostics: require admin privileges Previously exposed API token metadata (IDs, hints, usage mapping) to any authenticated token, enabling enumeration attacks
This commit is contained in:
@@ -314,7 +314,7 @@ func (r *Router) setupRoutes() {
|
||||
r.mux.HandleFunc("/api/charts", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, r.handleCharts)))
|
||||
r.mux.HandleFunc("/api/metrics-store/stats", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, r.handleMetricsStoreStats)))
|
||||
r.mux.HandleFunc("/api/metrics-store/history", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, r.handleMetricsHistory)))
|
||||
r.mux.HandleFunc("/api/diagnostics", RequireAuth(r.config, r.handleDiagnostics))
|
||||
r.mux.HandleFunc("/api/diagnostics", RequireAdmin(r.config, r.handleDiagnostics))
|
||||
r.mux.HandleFunc("/api/diagnostics/docker/prepare-token", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.handleDiagnosticsDockerPrepareToken)))
|
||||
r.mux.HandleFunc("/api/install/install-docker.sh", r.handleDownloadDockerInstallerScript)
|
||||
r.mux.HandleFunc("/api/install/install.sh", r.handleDownloadUnifiedInstallScript)
|
||||
@@ -1177,7 +1177,7 @@ func (r *Router) setupRoutes() {
|
||||
r.mux.HandleFunc("/api/setup-script-url", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.configHandlers.HandleSetupScriptURL)))
|
||||
|
||||
// Generate agent install command with API token (for authenticated users)
|
||||
r.mux.HandleFunc("/api/agent-install-command", RequireAuth(r.config, r.configHandlers.HandleAgentInstallCommand))
|
||||
r.mux.HandleFunc("/api/agent-install-command", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.configHandlers.HandleAgentInstallCommand)))
|
||||
|
||||
// Auto-register route for setup scripts
|
||||
r.mux.HandleFunc("/api/auto-register", r.configHandlers.HandleAutoRegister)
|
||||
@@ -1766,6 +1766,7 @@ func (r *Router) handleVerifyTemperatureSSH(w http.ResponseWriter, req *http.Req
|
||||
return
|
||||
}
|
||||
|
||||
// Check setup token first (for setup scripts)
|
||||
if token := extractSetupToken(req); token != "" {
|
||||
if r.configHandlers.ValidateSetupToken(token) {
|
||||
r.configHandlers.HandleVerifyTemperatureSSH(w, req)
|
||||
@@ -1773,24 +1774,30 @@ func (r *Router) handleVerifyTemperatureSSH(w http.ResponseWriter, req *http.Req
|
||||
}
|
||||
}
|
||||
|
||||
if CheckAuth(r.config, w, req) {
|
||||
r.configHandlers.HandleVerifyTemperatureSSH(w, req)
|
||||
// Require authentication
|
||||
if !CheckAuth(r.config, w, req) {
|
||||
log.Warn().
|
||||
Str("ip", req.RemoteAddr).
|
||||
Str("path", req.URL.Path).
|
||||
Str("method", req.Method).
|
||||
Msg("Unauthorized access attempt (verify-temperature-ssh)")
|
||||
|
||||
if strings.HasPrefix(req.URL.Path, "/api/") || strings.Contains(req.Header.Get("Accept"), "application/json") {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(`{"error":"Authentication required"}`))
|
||||
} else {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Warn().
|
||||
Str("ip", req.RemoteAddr).
|
||||
Str("path", req.URL.Path).
|
||||
Str("method", req.Method).
|
||||
Msg("Unauthorized access attempt (verify-temperature-ssh)")
|
||||
|
||||
if strings.HasPrefix(req.URL.Path, "/api/") || strings.Contains(req.Header.Get("Accept"), "application/json") {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(`{"error":"Authentication required"}`))
|
||||
} else {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
// Require settings:write scope for API tokens (SSH probes are a privileged operation)
|
||||
if !ensureScope(w, req, config.ScopeSettingsWrite) {
|
||||
return
|
||||
}
|
||||
|
||||
r.configHandlers.HandleVerifyTemperatureSSH(w, req)
|
||||
}
|
||||
|
||||
// handleSSHConfig handles SSH config writes with setup token or API auth
|
||||
@@ -1808,12 +1815,34 @@ func (r *Router) handleSSHConfig(w http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to standard API authentication
|
||||
if CheckAuth(r.config, w, req) {
|
||||
r.systemSettingsHandler.HandleSSHConfig(w, req)
|
||||
// Require authentication
|
||||
if !CheckAuth(r.config, w, req) {
|
||||
log.Warn().
|
||||
Str("ip", req.RemoteAddr).
|
||||
Str("path", req.URL.Path).
|
||||
Str("method", req.Method).
|
||||
Msg("Unauthorized access attempt (ssh-config)")
|
||||
|
||||
if strings.HasPrefix(req.URL.Path, "/api/") || strings.Contains(req.Header.Get("Accept"), "application/json") {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
w.Write([]byte(`{"error":"Authentication required"}`))
|
||||
} else {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Require settings:write scope for API tokens (SSH config writes are a privileged operation)
|
||||
if !ensureScope(w, req, config.ScopeSettingsWrite) {
|
||||
return
|
||||
}
|
||||
|
||||
r.systemSettingsHandler.HandleSSHConfig(w, req)
|
||||
}
|
||||
|
||||
// handleSSHConfigUnauthorized logs an unauthorized access attempt (legacy helper, no longer used)
|
||||
func (r *Router) handleSSHConfigUnauthorized(w http.ResponseWriter, req *http.Request) {
|
||||
log.Warn().
|
||||
Str("ip", req.RemoteAddr).
|
||||
Str("path", req.URL.Path).
|
||||
|
||||
Reference in New Issue
Block a user