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" "log"
"net" "net"
"net/http" "net/http"
"net/http/pprof"
"net/url" "net/url"
"reflect" "reflect"
"regexp" "regexp"
runtimepprof "runtime/pprof"
"slices" "slices"
"strings" "strings"
"sync" "sync"
@ -66,6 +68,7 @@ type BackendServer struct {
roomSessions RoomSessions roomSessions RoomSessions
version string version string
debug bool
welcomeMessage string welcomeMessage string
turnapikey string turnapikey string
@ -111,8 +114,8 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac
if !statsAllowedIps.Empty() { if !statsAllowedIps.Empty() {
log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed)
} else { } else {
log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1")
statsAllowedIps = DefaultAllowedIps() statsAllowedIps = DefaultAllowedIps()
log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps)
} }
invalidSecret := make([]byte, 32) invalidSecret := make([]byte, 32)
@ -120,11 +123,14 @@ func NewBackendServer(config *goconf.ConfigFile, hub *Hub, version string) (*Bac
return nil, err return nil, err
} }
debug, _ := config.GetBool("app", "debug")
result := &BackendServer{ result := &BackendServer{
hub: hub, hub: hub,
events: hub.events, events: hub.events,
roomSessions: hub.roomSessions, roomSessions: hub.roomSessions,
version: version, version: version,
debug: debug,
turnapikey: turnapikey, turnapikey: turnapikey,
turnsecret: []byte(turnsecret), turnsecret: []byte(turnsecret),
@ -145,8 +151,8 @@ func (b *BackendServer) Reload(config *goconf.ConfigFile) {
if !statsAllowedIps.Empty() { if !statsAllowedIps.Empty() {
log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed) log.Printf("Only allowing access to the stats endpoint from %s", statsAllowed)
} else { } else {
log.Printf("No IPs configured for the stats endpoint, only allowing access from 127.0.0.1")
statsAllowedIps = DefaultAllowedIps() statsAllowedIps = DefaultAllowedIps()
log.Printf("No IPs configured for the stats endpoint, only allowing access from %s", statsAllowedIps)
} }
b.statsAllowedIps.Store(statsAllowedIps) b.statsAllowedIps.Store(statsAllowedIps)
} else { } else {
@ -167,22 +173,42 @@ func (b *BackendServer) Start(r *mux.Router) error {
b.welcomeMessage = string(welcomeMessage) + "\n" 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 := r.PathPrefix("/api/v1").Subrouter()
s.HandleFunc("/welcome", b.setComonHeaders(b.welcomeFunc)).Methods("GET") s.HandleFunc("/welcome", b.setCommonHeaders(b.welcomeFunc)).Methods("GET")
s.HandleFunc("/room/{roomid}", b.setComonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST") s.HandleFunc("/room/{roomid}", b.setCommonHeaders(b.parseRequestBody(b.roomHandler))).Methods("POST")
s.HandleFunc("/stats", b.setComonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET") s.HandleFunc("/stats", b.setCommonHeaders(b.validateStatsRequest(b.statsHandler))).Methods("GET")
s.HandleFunc("/serverinfo", b.setComonHeaders(b.validateStatsRequest(b.serverinfoHandler))).Methods("GET") s.HandleFunc("/serverinfo", b.setCommonHeaders(b.validateStatsRequest(b.serverinfoHandler))).Methods("GET")
// Expose prometheus metrics at "/metrics". // 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. // Provide a REST service to get TURN credentials.
// See https://tools.ietf.org/html/draft-uberti-behave-turn-rest-00 // 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 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) { return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Server", "nextcloud-spreed-signaling/"+b.version) w.Header().Set("Server", "nextcloud-spreed-signaling/"+b.version)
w.Header().Set("X-Spreed-Signaling-Features", strings.Join(b.hub.info.Features, ", ")) 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 #listen = 127.0.0.1:9090
[app] [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. # See "https://golang.org/pkg/net/http/pprof/" for further information.
#debug = false #debug = false
@ -90,8 +92,9 @@ url = ws://localhost:8188/
#blockedcandidates = 1.2.3.0/24 #blockedcandidates = 1.2.3.0/24
[stats] [stats]
# Comma-separated list of IP addresses that are allowed to access the stats # Comma-separated list of IP addresses that are allowed to access the debug,
# endpoint. Leave empty (or commented) to only allow access from "127.0.0.1". # stats and metrics endpoints.
# Leave empty (or commented) to only allow access from localhost.
#allowed_ips = #allowed_ips =
[etcd] [etcd]

View file

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

View file

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

View file

@ -30,7 +30,6 @@ import (
"log" "log"
"net" "net"
"net/http" "net/http"
"net/http/pprof"
"os" "os"
"os/signal" "os/signal"
"runtime" "runtime"
@ -310,19 +309,6 @@ func main() {
log.Fatal("Could not start backend server: ", err) 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 var listeners Listeners
if saddr, _ := signaling.GetStringOptionWithEnv(config, "https", "listen"); saddr != "" { if saddr, _ := signaling.GetStringOptionWithEnv(config, "https", "listen"); saddr != "" {