Skip to content

Commit

Permalink
Merge pull request #1033 from redpanda-data/nf/pprof-loopback
Browse files Browse the repository at this point in the history
pprof: bind it to the loopback interface by default
  • Loading branch information
nicolaferraro authored Jan 25, 2024
2 parents 64fca3b + 41d1a35 commit 1cf3736
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 3 deletions.
24 changes: 24 additions & 0 deletions backend/pkg/api/middlewares.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ package api

import (
"context"
"net"
"net/http"
"strconv"
"strings"
"time"

"github.com/cloudhut/common/rest"
"github.com/go-chi/chi/v5"
"go.uber.org/zap"
)

// BasePathCtxKey is a helper to avoid allocations, idea taken from chi
Expand Down Expand Up @@ -134,3 +137,24 @@ func createSetVersionInfoHeader(builtAt string) func(next http.Handler) http.Han
return http.HandlerFunc(fn)
}
}

// forceLoopbackMiddleware blocks requests not coming from the loopback interface.
func forceLoopbackMiddleware(logger *zap.Logger) func(next http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
addr, ok := r.Context().Value(http.LocalAddrContextKey).(*net.TCPAddr)
if !ok {
logger.Info("request does not contain interface binding information")
rest.HandleNotFound(logger).ServeHTTP(w, r)
return
}
if !addr.IP.IsLoopback() {
logger.Info("blocking request not directed to the loopback interface")
rest.HandleNotFound(logger).ServeHTTP(w, r)
return
}

next.ServeHTTP(w, r)
})
}
}
14 changes: 11 additions & 3 deletions backend/pkg/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,12 +207,20 @@ func (api *API) routes() *chi.Mux {
r.Handle("/admin/metrics", promhttp.Handler())
r.Handle("/admin/health", api.handleLivenessProbe())
r.Handle("/admin/startup", api.handleStartupProbe())
})

// Debug routes
if api.Cfg.REST.Debug.Enabled {
router.Group(func(r chi.Router) {
api.Hooks.Route.ConfigInternalRouter(r)
if api.Cfg.REST.Debug.ForceLoopback {
r.Use(forceLoopbackMiddleware(api.Logger))
}

if api.Cfg.REST.Debug.Enabled {
// Path must be prefixed with /debug otherwise it will be overridden, see: https://golang.org/pkg/net/http/pprof/
r.Mount("/debug", chimiddleware.Profiler())
}
})
})
}

// API routes
router.Group(func(r chi.Router) {
Expand Down
6 changes: 6 additions & 0 deletions backend/pkg/config/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ type Server struct {

// DebugConfig contains configuration for the pprof debug handler.
type DebugConfig struct {
// Enabled allows to toggle the debug endpoint.
Enabled bool `yaml:"enabled"`

// ForceLoopback binds the debug endpoint only to the host's loopback interface.
ForceLoopback bool `yaml:"forceLoopback"`
}

// SetDefaults for server config.
Expand All @@ -50,4 +54,6 @@ func (s *Server) SetDefaults() {
s.AllowedOrigins = nil
// Debug is enabled by default for backward compatibility.
s.Debug.Enabled = true
// Forcing loopback by default on debug is better security-wise.
s.Debug.ForceLoopback = true
}
6 changes: 6 additions & 0 deletions docs/config/console.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,12 @@ kafka:
# # API. By default, a same-site policy is enforced to prevent CSRF-attacks.
# # Only in very specific deployment models you may need to change the secure default.
# allowedOrigins: []
# # Debug allows to configure the pprof debug handler options. The pprof handler is exposed
# # by default in the /debug path and only served through the system loopback interface,
# # e.g. '127.0.0.1' or '::1'.
# debug:
# enabled: true # allows to toggle the debug endpoint
# forceLoopback: true # binds the debug endpoint only to the host's loopback interface

# logger:
# level: info # Valid values are: debug, info, warn, error, fatal
Expand Down

0 comments on commit 1cf3736

Please sign in to comment.