mirror of
https://github.com/juanfont/headscale.git
synced 2026-04-17 06:19:51 +02:00
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:
@@ -3,7 +3,9 @@ package hscontrol
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/netip"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/arl/statsviz"
|
"github.com/arl/statsviz"
|
||||||
@@ -12,6 +14,35 @@ import (
|
|||||||
"tailscale.com/tsweb"
|
"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 {
|
func (h *Headscale) debugHTTPServer() *http.Server {
|
||||||
debugMux := http.NewServeMux()
|
debugMux := http.NewServeMux()
|
||||||
debug := tsweb.Debugger(debugMux)
|
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 {
|
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)")
|
debug.URL("/debug/statsviz", "Statsviz (visualise go metrics)")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user