mirror of
				https://github.com/juanfont/headscale.git
				synced 2025-11-04 01:51:04 +01:00 
			
		
		
		
	This PR switches the homegrown debug endpoint to using tsweb.Debugger, a neat toolkit with batteries included for pprof and friends, and making it easy to add additional debug info: I've started out by adding a bunch of "introspect" endpoints image So users can see the acl, filter, config, derpmap and connected nodes as headscale sees them.
		
			
				
	
	
		
			121 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			121 lines
		
	
	
		
			3.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package hscontrol
 | 
						|
 | 
						|
import (
 | 
						|
	"encoding/json"
 | 
						|
	"fmt"
 | 
						|
	"net/http"
 | 
						|
 | 
						|
	"github.com/arl/statsviz"
 | 
						|
	"github.com/juanfont/headscale/hscontrol/types"
 | 
						|
	"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
 | 
						|
		}
 | 
						|
		w.Header().Set("Content-Type", "text/plain")
 | 
						|
		w.WriteHeader(http.StatusOK)
 | 
						|
		w.Write(config)
 | 
						|
	}))
 | 
						|
	debug.Handle("policy", "Current policy", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		pol, err := h.policyBytes()
 | 
						|
		if err != nil {
 | 
						|
			httpError(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		w.Header().Set("Content-Type", "text/plain")
 | 
						|
		w.WriteHeader(http.StatusOK)
 | 
						|
		w.Write(pol)
 | 
						|
	}))
 | 
						|
	debug.Handle("filter", "Current filter", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		filter := h.polMan.Filter()
 | 
						|
 | 
						|
		filterJSON, err := json.MarshalIndent(filter, "", "  ")
 | 
						|
		if err != nil {
 | 
						|
			httpError(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		w.Header().Set("Content-Type", "text/plain")
 | 
						|
		w.WriteHeader(http.StatusOK)
 | 
						|
		w.Write(filterJSON)
 | 
						|
	}))
 | 
						|
	debug.Handle("ssh", "SSH Policy per node", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		nodes, err := h.db.ListNodes()
 | 
						|
		if err != nil {
 | 
						|
			httpError(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
 | 
						|
		sshPol := make(map[string]*tailcfg.SSHPolicy)
 | 
						|
		for _, node := range nodes {
 | 
						|
			pol, err := h.polMan.SSHPolicy(node)
 | 
						|
			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
 | 
						|
		}
 | 
						|
		w.Header().Set("Content-Type", "text/plain")
 | 
						|
		w.WriteHeader(http.StatusOK)
 | 
						|
		w.Write(sshJSON)
 | 
						|
	}))
 | 
						|
	debug.Handle("derpmap", "Current DERPMap", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		dm := h.DERPMap
 | 
						|
 | 
						|
		dmJSON, err := json.MarshalIndent(dm, "", "  ")
 | 
						|
		if err != nil {
 | 
						|
			httpError(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		w.Header().Set("Content-Type", "text/plain")
 | 
						|
		w.WriteHeader(http.StatusOK)
 | 
						|
		w.Write(dmJSON)
 | 
						|
	}))
 | 
						|
	debug.Handle("registration-cache", "Pending registrations", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | 
						|
		registrationsJSON, err := json.MarshalIndent(h.registrationCache.Items(), "", "  ")
 | 
						|
		if err != nil {
 | 
						|
			httpError(w, err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		w.Header().Set("Content-Type", "text/plain")
 | 
						|
		w.WriteHeader(http.StatusOK)
 | 
						|
		w.Write(registrationsJSON)
 | 
						|
	}))
 | 
						|
 | 
						|
	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
 | 
						|
}
 |