debug: route statsviz through tsweb.Protected

Build the statsviz Server directly and wrap its Index/Ws handlers in
tsweb.Protected instead of calling statsviz.Register on the raw mux
which bypasses AllowDebugAccess.
This commit is contained in:
Kristoffer Dalby
2026-04-09 17:59:46 +00:00
parent 8c6cb05ab4
commit d5a4e6e36a

View File

@@ -3,7 +3,9 @@ package hscontrol
import (
"encoding/json"
"fmt"
"net"
"net/http"
"net/netip"
"strings"
"github.com/arl/statsviz"
@@ -12,6 +14,35 @@ import (
"tailscale.com/tsweb"
)
// protectedDebugHandler wraps an http.Handler with an access check that
// allows requests from loopback, Tailscale CGNAT IPs, and private
// (RFC 1918 / RFC 4193) addresses. This extends tsweb.Protected which
// only allows loopback and Tailscale IPs.
func protectedDebugHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if tsweb.AllowDebugAccess(r) {
h.ServeHTTP(w, r)
return
}
// tsweb.AllowDebugAccess rejects X-Forwarded-For and non-TS IPs.
// Additionally allow private/LAN addresses so operators can reach
// debug endpoints from their local network without tailscaled.
ipStr, _, err := net.SplitHostPort(r.RemoteAddr)
if err == nil {
ip, parseErr := netip.ParseAddr(ipStr)
if parseErr == nil && ip.IsPrivate() {
h.ServeHTTP(w, r)
return
}
}
http.Error(w, "debug access denied", http.StatusForbidden)
})
}
func (h *Headscale) debugHTTPServer() *http.Server {
debugMux := http.NewServeMux()
debug := tsweb.Debugger(debugMux)
@@ -293,8 +324,13 @@ func (h *Headscale) debugHTTPServer() *http.Server {
}
}))
err := statsviz.Register(debugMux)
// statsviz.Register would mount handlers directly on the raw mux,
// bypassing the access gate. Build the server by hand and wrap
// each handler with protectedDebugHandler.
statsvizSrv, err := statsviz.NewServer()
if err == nil {
debugMux.Handle("/debug/statsviz/", protectedDebugHandler(statsvizSrv.Index()))
debugMux.Handle("/debug/statsviz/ws", protectedDebugHandler(statsvizSrv.Ws()))
debug.URL("/debug/statsviz", "Statsviz (visualise go metrics)")
}