Protect access to the debug pprof handlers.

This commit is contained in:
Joachim Bauch 2025-10-08 10:46:06 +02:00
commit 41c18d2531
No known key found for this signature in database
GPG key ID: 77C1D22D53E15F02
5 changed files with 62 additions and 37 deletions

View file

@ -34,9 +34,11 @@ import (
"log"
"net"
"net/http"
"net/http/pprof"
"net/url"
"reflect"
"regexp"
runtimepprof "runtime/pprof"
"slices"
"strings"
"sync"
@ -66,6 +68,7 @@ type BackendServer struct {
roomSessions RoomSessions
version string
debug bool
welcomeMessage string
turnapikey string
@ -111,8 +114,8 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac
if !statsAllowedIps.Empty() {
log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed)
} else {
log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1")
statsAllowedIps = DefaultAllowedIps()
log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps)
}
invalidSecret := make([]byte, 32)
@ -120,11 +123,14 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac
return nil, err
}
debug, _ := config.GetBool("app", "debug")
result := &BackendServer{
hub: hub,
events: hub.events,
roomSessions: hub.roomSessions,
version: version,
debug: debug,
turnapikey: turnapikey,
turnsecret: []byte(turnsecret),
@ -145,8 +151,8 @@ func (b *BackendServer) Reload(config *goconf.ConfigFile) {
if !statsAllowedIps.Empty() {
log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed)
} else {
log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1")
statsAllowedIps = DefaultAllowedIps()
log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps)
}
b.statsAllowedIps.Store(statsAllowedIps)
} else {
@ -167,22 +173,42 @@ func (b *BackendServer) Start(r *mux.Router) error {
b.welcomeMessage = string(welcomeMessage) + "\n"
if b.debug {
log.Println("Installing debug handlers in \"/debug/pprof\"")
s := r.PathPrefix("/debug/pprof").Subrouter()
s.HandleFunc("", b.setCommonHeaders(b.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/debug/pprof/", http.StatusTemporaryRedirect)
})))
s.HandleFunc("/", b.setCommonHeaders(b.validateStatsRequest(pprof.Index)))
s.HandleFunc("/cmdline", b.setCommonHeaders(b.validateStatsRequest(pprof.Cmdline)))
s.HandleFunc("/profile", b.setCommonHeaders(b.validateStatsRequest(pprof.Profile)))
s.HandleFunc("/symbol", b.setCommonHeaders(b.validateStatsRequest(pprof.Symbol)))
s.HandleFunc("/trace", b.setCommonHeaders(b.validateStatsRequest(pprof.Trace)))
for _, profile := range runtimepprof.Profiles() {
name := profile.Name()
handler := pprof.Handler(name)
s.HandleFunc("/"+name, b.setCommonHeaders(b.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) {
handler.ServeHTTP(w, r)
})))
}
}
s := r.PathPrefix("/api/v1").Subrouter()
s.HandleFunc("/welcome", b.setComonHeaders(b.welcomeFunc)).Methods("GET")
s.HandleFunc("/room/{roomid}", b.setComonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST")
s.HandleFunc("/stats", b.setComonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET")
s.HandleFunc("/serverinfo", b.setComonHeaders(b.validateStatsRequest(b.serverinfoHandler))).Methods("GET")
s.HandleFunc("/welcome", b.setCommonHeaders(b.welcomeFunc)).Methods("GET")
s.HandleFunc("/room/{roomid}", b.setCommonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST")
s.HandleFunc("/stats", b.setCommonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET")
s.HandleFunc("/serverinfo", b.setCommonHeaders(b.validateStatsRequest(b.serverinfoHandler))).Methods("GET")
// Expose prometheus metrics at "/metrics".
r.HandleFunc("/metrics", b.setComonHeaders(b.validateStatsRequest(b.metricsHandler))).Methods("GET")
r.HandleFunc("/metrics", b.setCommonHeaders(b.validateStatsRequest(b.metricsHandler))).Methods("GET")
// Provide a REST service to get TURN credentials.
// See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00
r.HandleFunc("/turn/credentials", b.setComonHeaders(b.getTurnCredentials)).Methods("GET")
r.HandleFunc("/turn/credentials", b.setCommonHeaders(b.getTurnCredentials)).Methods("GET")
return nil
}
func (b *BackendServer) setComonHeaders(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
func (b *BackendServer) setCommonHeaders(f func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "nextcloud-spreed-signaling/"+b.version)
w.Header().Set("X-Spreed-Signaling-Features", strings.Join(b.hub.info.Features, ", "))

View file

@ -4,7 +4,9 @@
#listen = 127.0.0.1:9090
[app]
# Set to "true" to install pprof debug handlers.
# Set to "true" to install pprof debug handlers. Access will only be possible
# from IPs allowed through the "allowed_ips" option below.
#
# See "https://golang.org/pkg/net/http/pprof/" for further information.
#debug = false
@ -90,8 +92,9 @@ url = ws://localhost:8188/
#blockedcandidates = 1.2.3.0/24
[stats]
# Comma-separated list of IP addresses that are allowed to access the stats
# endpoint. Leave empty (or commented) to only allow access from "127.0.0.1".
# Comma-separated list of IP addresses that are allowed to access the debug,
# stats and metrics endpoints.
# Leave empty (or commented) to only allow access from localhost.
#allowed_ips =
[etcd]

View file

@ -246,8 +246,8 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
if !statsAllowedIps.Empty() {
log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed)
} else {
log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1")
statsAllowedIps = signaling.DefaultAllowedIps()
log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps)
}
trustedProxies, _ := config.GetString("app", "trustedproxies")
@ -377,14 +377,21 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
if debug, _ := config.GetBool("app", "debug"); debug {
log.Println("Installing debug handlers in \"/debug/pprof\"")
r.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
r.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
r.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
r.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
r.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
s := r.PathPrefix("/debug/pprof").Subrouter()
s.HandleFunc("", result.setCommonHeaders(result.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/debug/pprof/", http.StatusTemporaryRedirect)
})))
s.HandleFunc("/", result.setCommonHeaders(result.validateStatsRequest(pprof.Index)))
s.HandleFunc("/cmdline", result.setCommonHeaders(result.validateStatsRequest(pprof.Cmdline)))
s.HandleFunc("/profile", result.setCommonHeaders(result.validateStatsRequest(pprof.Profile)))
s.HandleFunc("/symbol", result.setCommonHeaders(result.validateStatsRequest(pprof.Symbol)))
s.HandleFunc("/trace", result.setCommonHeaders(result.validateStatsRequest(pprof.Trace)))
for _, profile := range runtimepprof.Profiles() {
name := profile.Name()
r.Handle("/debug/pprof/"+name, pprof.Handler(name))
handler := pprof.Handler(name)
s.HandleFunc("/"+name, result.setCommonHeaders(result.validateStatsRequest(func(w http.ResponseWriter, r *http.Request) {
handler.ServeHTTP(w, r)
})))
}
}
@ -594,8 +601,8 @@ func (s *ProxyServer) Reload(config *goconf.ConfigFile) {
if !statsAllowedIps.Empty() {
log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed)
} else {
log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1")
statsAllowedIps = signaling.DefaultAllowedIps()
log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps)
}
s.statsAllowedIps.Store(statsAllowedIps)
} else {

View file

@ -25,7 +25,9 @@ certificate = /etc/nginx/ssl/server.crt
key = /etc/nginx/ssl/server.key
[app]
# Set to "true" to install pprof debug handlers.
# Set to "true" to install pprof debug handlers. Access will only be possible
# from IPs allowed through the "allowed_ips" option below.
#
# See "https://golang.org/pkg/net/http/pprof/" for further information.
debug = false
@ -270,8 +272,9 @@ connectionsperhost = 8
#SA = NA
[stats]
# Comma-separated list of IP addresses that are allowed to access the stats
# endpoint. Leave empty (or commented) to only allow access from "127.0.0.1".
# Comma-separated list of IP addresses that are allowed to access the debug,
# stats and metrics endpoints.
# Leave empty (or commented) to only allow access from localhost.
#allowed_ips =
[etcd]

View file

@ -30,7 +30,6 @@ import (
"log"
"net"
"net/http"
"net/http/pprof"
"os"
"os/signal"
"runtime"
@ -310,19 +309,6 @@ func main() {
log.Fatal("Could not start backend server: ", err)
}
if debug, _ := config.GetBool("app", "debug"); debug {
log.Println("Installing debug handlers in \"/debug/pprof\"")
r.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index))
r.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline))
r.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile))
r.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol))
r.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace))
for _, profile := range runtimepprof.Profiles() {
name := profile.Name()
r.Handle("/debug/pprof/"+name, pprof.Handler(name))
}
}
var listeners Listeners
if saddr, _ := signaling.GetStringOptionWithEnv(config, "https", "listen"); saddr != "" {