a81bbb2bcf
---ci--- project: orca phase: 9 milestone: v0.12 status: execute ---/ci--- - Start() refuses plaintext mode (mTLS required, R-021/REQ-123). - bodyLimitMiddleware wraps all handlers with MaxBytesReader (1 MiB, REQ-124/F24). - pprof loopback-only (isLoopback check; non-loopback refused with clear error, REQ-123). 2 new pprof loopback tests + existing daemon tests pass. Full build + vet green.
76 lines
2.2 KiB
Go
76 lines
2.2 KiB
Go
package daemon
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log/slog"
|
|
"net"
|
|
"net/http"
|
|
"net/http/pprof"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// isLoopback reports whether the address binds to a loopback interface
|
|
// (127.0.0.1, ::1, localhost). REQ-123: pprof must be loopback-only.
|
|
func isLoopback(addr string) bool {
|
|
host, _, err := net.SplitHostPort(addr)
|
|
if err != nil {
|
|
host = addr
|
|
}
|
|
host = strings.TrimSpace(host)
|
|
if host == "" || host == "localhost" {
|
|
return true
|
|
}
|
|
ip := net.ParseIP(host)
|
|
if ip != nil {
|
|
return ip.IsLoopback()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func StartPprof(addr string, log *slog.Logger) (*http.Server, error) {
|
|
if addr == "" {
|
|
return nil, nil
|
|
}
|
|
// REQ-123: pprof must bind to loopback only. Non-loopback addresses
|
|
// require explicit --pprof-allow-public confirmation (which the CLI
|
|
// passes after a warning). We refuse non-loopback here by default.
|
|
if !isLoopback(addr) {
|
|
log.Error("pprof refuses non-loopback bind",
|
|
slog.String("addr", addr),
|
|
slog.String("reason", "REQ-123: pprof is unauthenticated; use --pprof-allow-public to override (operator-only)"))
|
|
return nil, fmt.Errorf("pprof: refusing non-loopback bind %s (REQ-123; unauthenticated; use --pprof-allow-public)", addr)
|
|
}
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/debug/pprof/", pprof.Index)
|
|
mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
|
|
mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
|
|
mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
|
|
mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
|
|
mux.Handle("/debug/pprof/heap", pprof.Handler("heap"))
|
|
mux.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
|
|
mux.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
|
|
mux.Handle("/debug/pprof/block", pprof.Handler("block"))
|
|
mux.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))
|
|
|
|
server := &http.Server{
|
|
Addr: addr,
|
|
Handler: mux,
|
|
ReadHeaderTimeout: 5 * time.Second,
|
|
}
|
|
|
|
log.Warn("pprof endpoint exposed",
|
|
slog.String("addr", addr),
|
|
slog.String("warning", "unauthenticated, operator-only — do not expose publicly"))
|
|
|
|
go func() {
|
|
err := server.ListenAndServe()
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
log.Error("pprof server stopped", slog.String("addr", addr), slog.Any("err", err))
|
|
}
|
|
}()
|
|
|
|
return server, nil
|
|
}
|