2025-02-11 11:18:59 +01:00
|
|
|
package hscontrol
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2025-05-27 16:27:16 +02:00
|
|
|
"os"
|
2025-02-11 11:18:59 +01:00
|
|
|
|
|
|
|
"github.com/arl/statsviz"
|
|
|
|
"github.com/juanfont/headscale/hscontrol/types"
|
2025-05-27 16:27:16 +02:00
|
|
|
"github.com/juanfont/headscale/hscontrol/util"
|
2025-02-11 11:18:59 +01:00
|
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
"tailscale.com/tsweb"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (h *Headscale) debugHTTPServer() *http.Server {
|
|
|
|
debugMux := http.NewServeMux()
|
|
|
|
debug := tsweb.Debugger(debugMux)
|
|
|
|
debug.Handle("notifier", "Connected nodes in notifier", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write([]byte(h.nodeNotifier.String()))
|
|
|
|
}))
|
|
|
|
debug.Handle("config", "Current configuration", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
config, err := json.MarshalIndent(h.cfg, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2025-03-15 13:57:25 +01:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2025-02-11 11:18:59 +01:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(config)
|
|
|
|
}))
|
|
|
|
debug.Handle("policy", "Current policy", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2025-05-27 16:27:16 +02:00
|
|
|
switch h.cfg.Policy.Mode {
|
|
|
|
case types.PolicyModeDB:
|
|
|
|
p, err := h.state.GetPolicy()
|
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write([]byte(p.Data))
|
|
|
|
case types.PolicyModeFile:
|
|
|
|
// Read the file directly for debug purposes
|
|
|
|
absPath := util.AbsolutePathFromConfigPath(h.cfg.Policy.Path)
|
|
|
|
pol, err := os.ReadFile(absPath)
|
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(pol)
|
|
|
|
default:
|
|
|
|
httpError(w, fmt.Errorf("unsupported policy mode: %s", h.cfg.Policy.Mode))
|
2025-02-11 11:18:59 +01:00
|
|
|
}
|
|
|
|
}))
|
|
|
|
debug.Handle("filter", "Current filter", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2025-05-27 16:27:16 +02:00
|
|
|
filter, _ := h.state.Filter()
|
2025-02-11 11:18:59 +01:00
|
|
|
|
|
|
|
filterJSON, err := json.MarshalIndent(filter, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2025-03-15 13:57:25 +01:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2025-02-11 11:18:59 +01:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(filterJSON)
|
|
|
|
}))
|
|
|
|
debug.Handle("ssh", "SSH Policy per node", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2025-05-27 16:27:16 +02:00
|
|
|
nodes, err := h.state.ListNodes()
|
2025-02-11 11:18:59 +01:00
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sshPol := make(map[string]*tailcfg.SSHPolicy)
|
|
|
|
for _, node := range nodes {
|
2025-07-05 23:31:13 +02:00
|
|
|
pol, err := h.state.SSHPolicy(node.View())
|
2025-02-11 11:18:59 +01:00
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
sshPol[fmt.Sprintf("id:%d hostname:%s givenname:%s", node.ID, node.Hostname, node.GivenName)] = pol
|
|
|
|
}
|
|
|
|
|
|
|
|
sshJSON, err := json.MarshalIndent(sshPol, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2025-03-15 13:57:25 +01:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2025-02-11 11:18:59 +01:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(sshJSON)
|
|
|
|
}))
|
|
|
|
debug.Handle("derpmap", "Current DERPMap", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2025-05-27 16:27:16 +02:00
|
|
|
dm := h.state.DERPMap()
|
2025-02-11 11:18:59 +01:00
|
|
|
|
|
|
|
dmJSON, err := json.MarshalIndent(dm, "", " ")
|
|
|
|
if err != nil {
|
|
|
|
httpError(w, err)
|
|
|
|
return
|
|
|
|
}
|
2025-03-15 13:57:25 +01:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2025-02-11 11:18:59 +01:00
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
w.Write(dmJSON)
|
|
|
|
}))
|
|
|
|
debug.Handle("registration-cache", "Pending registrations", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
2025-05-27 16:27:16 +02:00
|
|
|
// TODO(kradalby): This should be replaced with a proper state method that returns registration info
|
2025-03-15 13:57:25 +01:00
|
|
|
w.Header().Set("Content-Type", "application/json")
|
2025-02-11 11:18:59 +01:00
|
|
|
w.WriteHeader(http.StatusOK)
|
2025-05-27 16:27:16 +02:00
|
|
|
w.Write([]byte("{}")) // For now, return empty object
|
2025-02-11 11:18:59 +01:00
|
|
|
}))
|
2025-02-26 07:22:55 -08:00
|
|
|
debug.Handle("routes", "Routes", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
2025-05-27 16:27:16 +02:00
|
|
|
w.Write([]byte(h.state.PrimaryRoutesString()))
|
2025-02-26 07:22:55 -08:00
|
|
|
}))
|
2025-03-10 16:20:29 +01:00
|
|
|
debug.Handle("policy-manager", "Policy Manager", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Header().Set("Content-Type", "text/plain")
|
|
|
|
w.WriteHeader(http.StatusOK)
|
2025-05-27 16:27:16 +02:00
|
|
|
w.Write([]byte(h.state.PolicyDebugString()))
|
2025-03-10 16:20:29 +01:00
|
|
|
}))
|
2025-02-11 11:18:59 +01:00
|
|
|
|
|
|
|
err := statsviz.Register(debugMux)
|
|
|
|
if err == nil {
|
|
|
|
debug.URL("/debug/statsviz", "Statsviz (visualise go metrics)")
|
|
|
|
}
|
|
|
|
|
|
|
|
debug.URL("/metrics", "Prometheus metrics")
|
|
|
|
debugMux.Handle("/metrics", promhttp.Handler())
|
|
|
|
|
|
|
|
debugHTTPServer := &http.Server{
|
|
|
|
Addr: h.cfg.MetricsAddr,
|
|
|
|
Handler: debugMux,
|
|
|
|
ReadTimeout: types.HTTPTimeout,
|
|
|
|
WriteTimeout: 0,
|
|
|
|
}
|
|
|
|
|
|
|
|
return debugHTTPServer
|
|
|
|
}
|