mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
Add admin-gated pprof diagnostics endpoint
This commit is contained in:
56
internal/api/diagnostics_pprof.go
Normal file
56
internal/api/diagnostics_pprof.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const pprofRoutePrefix = "/api/diagnostics/pprof"
|
||||
|
||||
func pprofEnabled() bool {
|
||||
return strings.EqualFold(os.Getenv("PULSE_ENABLE_PPROF"), "true")
|
||||
}
|
||||
|
||||
func (r *Router) handlePprofRedirect(w http.ResponseWriter, req *http.Request) {
|
||||
if !pprofEnabled() {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
target := pprofRoutePrefix + "/"
|
||||
if req.URL.RawQuery != "" {
|
||||
target += "?" + req.URL.RawQuery
|
||||
}
|
||||
http.Redirect(w, req, target, http.StatusMovedPermanently)
|
||||
}
|
||||
|
||||
func (r *Router) handlePprof(w http.ResponseWriter, req *http.Request) {
|
||||
if !pprofEnabled() {
|
||||
http.NotFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
relative := strings.TrimPrefix(req.URL.Path, pprofRoutePrefix)
|
||||
switch relative {
|
||||
case "", "/":
|
||||
pprof.Index(w, req)
|
||||
return
|
||||
case "/cmdline":
|
||||
pprof.Cmdline(w, req)
|
||||
return
|
||||
case "/profile":
|
||||
pprof.Profile(w, req)
|
||||
return
|
||||
case "/symbol":
|
||||
pprof.Symbol(w, req)
|
||||
return
|
||||
case "/trace":
|
||||
pprof.Trace(w, req)
|
||||
return
|
||||
default:
|
||||
profile := strings.TrimPrefix(relative, "/")
|
||||
pprof.Handler(profile).ServeHTTP(w, req)
|
||||
}
|
||||
}
|
||||
67
internal/api/diagnostics_pprof_test.go
Normal file
67
internal/api/diagnostics_pprof_test.go
Normal file
@@ -0,0 +1,67 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
)
|
||||
|
||||
func TestHandlePprofDisabledReturnsNotFound(t *testing.T) {
|
||||
router := &Router{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/diagnostics/pprof/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handlePprof(rec, req)
|
||||
|
||||
if rec.Code != http.StatusNotFound {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandlePprofEnabledServesIndex(t *testing.T) {
|
||||
t.Setenv("PULSE_ENABLE_PPROF", "true")
|
||||
router := &Router{}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/diagnostics/pprof/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.handlePprof(rec, req)
|
||||
|
||||
if rec.Code != http.StatusOK {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
|
||||
}
|
||||
if !strings.Contains(rec.Body.String(), "heap") {
|
||||
t.Fatalf("expected pprof index to include heap profile")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPprofRouteRequiresAdmin(t *testing.T) {
|
||||
t.Setenv("PULSE_ENABLE_PPROF", "true")
|
||||
t.Setenv("ALLOW_ADMIN_BYPASS", "")
|
||||
t.Setenv("PULSE_DEV", "")
|
||||
t.Setenv("NODE_ENV", "")
|
||||
resetAdminBypassState()
|
||||
|
||||
dataDir := t.TempDir()
|
||||
cfg := &config.Config{
|
||||
DataPath: dataDir,
|
||||
ConfigPath: dataDir,
|
||||
AuthUser: "admin",
|
||||
AuthPass: "secret",
|
||||
}
|
||||
|
||||
router := NewRouter(cfg, nil, nil, nil, nil, "1.0.0")
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/api/diagnostics/pprof/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
router.Handler().ServeHTTP(rec, req)
|
||||
|
||||
if rec.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized)
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
func TestProxyAuthAdminGatesAdminEndpoints(t *testing.T) {
|
||||
t.Setenv("PULSE_DEV", "true")
|
||||
t.Setenv("PULSE_ENABLE_PPROF", "true")
|
||||
|
||||
record := newTokenRecord(t, "proxy-admin-gate-token-123.12345678", []string{config.ScopeSettingsRead}, nil)
|
||||
cfg := newTestConfigWithTokens(t, record)
|
||||
@@ -32,6 +33,7 @@ func TestProxyAuthAdminGatesAdminEndpoints(t *testing.T) {
|
||||
{method: http.MethodPost, path: "/api/logs/level", body: `{"level":"info"}`},
|
||||
{method: http.MethodGet, path: "/api/diagnostics", body: ""},
|
||||
{method: http.MethodPost, path: "/api/diagnostics/docker/prepare-token", body: `{}`},
|
||||
{method: http.MethodGet, path: "/api/diagnostics/pprof/", body: ""},
|
||||
{method: http.MethodGet, path: "/api/system/settings", body: ""},
|
||||
{method: http.MethodPost, path: "/api/system/settings/update", body: `{}`},
|
||||
{method: http.MethodPost, path: "/api/security/oidc", body: `{}`},
|
||||
|
||||
@@ -310,6 +310,8 @@ var allRouteAllowlist = []string{
|
||||
"/api/metrics-store/history",
|
||||
"/api/diagnostics",
|
||||
"/api/diagnostics/docker/prepare-token",
|
||||
"/api/diagnostics/pprof",
|
||||
"/api/diagnostics/pprof/",
|
||||
"/api/install/install-docker.sh",
|
||||
"/api/install/install.sh",
|
||||
"/api/install/install.ps1",
|
||||
|
||||
@@ -325,6 +325,10 @@ func (r *Router) setupRoutes() {
|
||||
r.mux.HandleFunc("/api/metrics-store/history", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, r.handleMetricsHistory)))
|
||||
r.mux.HandleFunc("/api/diagnostics", RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.handleDiagnostics)))
|
||||
r.mux.HandleFunc("/api/diagnostics/docker/prepare-token", RequireAdmin(r.config, RequireScope(config.ScopeSettingsWrite, r.handleDiagnosticsDockerPrepareToken)))
|
||||
if pprofEnabled() {
|
||||
r.mux.HandleFunc("/api/diagnostics/pprof", RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.handlePprofRedirect)))
|
||||
r.mux.HandleFunc("/api/diagnostics/pprof/", RequireAdmin(r.config, RequireScope(config.ScopeSettingsRead, r.handlePprof)))
|
||||
}
|
||||
r.mux.HandleFunc("/api/install/install-docker.sh", r.handleDownloadDockerInstallerScript)
|
||||
r.mux.HandleFunc("/api/install/install.sh", r.handleDownloadUnifiedInstallScript)
|
||||
r.mux.HandleFunc("/api/install/install.ps1", r.handleDownloadUnifiedInstallScriptPS)
|
||||
|
||||
Reference in New Issue
Block a user