Something went wrong. Try again.
forked from tailscale/tailscale
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194// Copyright (c) Tailscale Inc & contributors// SPDX-License-Identifier: BSD-3-Clause
package tsweb
import ( "expvar" "fmt" "html" "io" "net/http" "net/url" "os" "runtime"
"tailscale.com/feature" "tailscale.com/tsweb/varz" "tailscale.com/version")
// DebugHandler is an http.Handler that serves a debugging "homepage",// and provides helpers to register more debug endpoints and reports.//// The rendered page consists of three sections: informational// key/value pairs, links to other pages, and additional// program-specific HTML. Callers can add to these sections using the// KV, URL and Section helpers respectively.//// Additionally, the Handle method offers a shorthand for correctly// registering debug handlers and cross-linking them from /debug/.type DebugHandler struct { mux *http.ServeMux // where this handler is registered kvs []func(io.Writer) // output one <li>...</li> each, see KV() urls []string // one <li>...</li> block with link each sections []func(io.Writer, *http.Request) // invoked in registration order prior to outputting </body> title string // title displayed on index page}
// PrometheusHandler is an optional hook to enable native Prometheus// support in the debug handler. It is disabled by default. Import the// tailscale.com/tsweb/promvarz package to enable this feature.var PrometheusHandler feature.Hook[func(*DebugHandler)]
// Debugger returns the DebugHandler registered on mux at /debug/,// creating it if necessary.func Debugger(mux *http.ServeMux) *DebugHandler { h, pat := mux.Handler(&http.Request{URL: &url.URL{Path: "/debug/"}}) if d, ok := h.(*DebugHandler); ok && pat == "/debug/" { return d } ret := &DebugHandler{ mux: mux, title: fmt.Sprintf("%s debug", version.CmdName()), } mux.Handle("/debug/", ret)
ret.KVFunc("Uptime", func() any { return varz.Uptime() }) ret.KV("Version", version.Long()) ret.Handle("vars", "Metrics (Go)", expvar.Handler()) if PrometheusHandler.IsSet() { PrometheusHandler.Get()(ret) } else { ret.Handle("varz", "Metrics (Prometheus)", http.HandlerFunc(varz.Handler)) }
addProfilingHandlers(ret) ret.Handle("gc", "force GC", http.HandlerFunc(gcHandler)) hostname, err := os.Hostname() if err == nil { ret.KV("Machine", hostname) } return ret}
// ServeHTTP implements http.Handler.func (d *DebugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if !AllowDebugAccess(r) { http.Error(w, "debug access denied", http.StatusForbidden) return } if r.URL.Path != "/debug/" { // Sub-handlers are handled by the parent mux directly. http.NotFound(w, r) return }
AddBrowserHeaders(w) f := func(format string, args ...any) { fmt.Fprintf(w, format, args...) } f("<html><body><h1>%s</h1><ul>", html.EscapeString(d.title)) for _, kv := range d.kvs { kv(w) } for _, url := range d.urls { io.WriteString(w, url) } for _, section := range d.sections { section(w, r) }}
func (d *DebugHandler) handle(slug string, handler http.Handler) string { href := "/debug/" + slug d.mux.Handle(href, Protected(debugBrowserHeaderHandler(handler))) return href}
// Handle registers handler at /debug/<slug> and adds a link to it// on /debug/ with the provided description.func (d *DebugHandler) Handle(slug, desc string, handler http.Handler) { href := d.handle(slug, handler) d.URL(href, desc)}
// Handle registers handler at /debug/<slug> and adds a link to it// on /debug/ with the provided description.func (d *DebugHandler) HandleFunc(slug, desc string, handler http.HandlerFunc) { d.Handle(slug, desc, handler)}
// HandleSilent registers handler at /debug/<slug>. It does not add// a descriptive entry in /debug/ for it. This should be used// sparingly, for things that need to be registered but would pollute// the list of debug links.func (d *DebugHandler) HandleSilent(slug string, handler http.Handler) { d.handle(slug, handler)}
// HandleSilent registers handler at /debug/<slug>. It does not add// a descriptive entry in /debug/ for it. This should be used// sparingly, for things that need to be registered but would pollute// the list of debug links.func (d *DebugHandler) HandleSilentFunc(slug string, handler http.HandlerFunc) { d.HandleSilent(slug, handler)}
// KV adds a key/value list item to /debug/.func (d *DebugHandler) KV(k string, v any) { val := html.EscapeString(fmt.Sprintf("%v", v)) d.kvs = append(d.kvs, func(w io.Writer) { fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val) })}
// KVFunc adds a key/value list item to /debug/. v is called on every// render of /debug/.func (d *DebugHandler) KVFunc(k string, v func() any) { d.kvs = append(d.kvs, func(w io.Writer) { val := html.EscapeString(fmt.Sprintf("%v", v())) fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val) })}
// URL adds a URL and description list item to /debug/.func (d *DebugHandler) URL(url, desc string) { if desc != "" { desc = " (" + desc + ")" } d.urls = append(d.urls, fmt.Sprintf(`<li><a href="%s">%s</a>%s</li>`, url, url, html.EscapeString(desc)))}
// Section invokes f on every render of /debug/ to add supplemental// HTML to the page body.func (d *DebugHandler) Section(f func(w io.Writer, r *http.Request)) { d.sections = append(d.sections, f)}
// Title sets the title at the top of the debug page.func (d *DebugHandler) Title(title string) { d.title = title}
func gcHandler(w http.ResponseWriter, r *http.Request) { w.Write([]byte("running GC...\n")) if f, ok := w.(http.Flusher); ok { f.Flush() } runtime.GC() w.Write([]byte("Done.\n"))}
// debugBrowserHeaderHandler is a wrapper around BrowserHeaderHandler with a// more relaxed Content-Security-Policy that's acceptable for internal debug// pages. It should not be used on any public-facing handlers!func debugBrowserHeaderHandler(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { AddBrowserHeaders(w) // The only difference from AddBrowserHeaders is that this policy // allows inline CSS styles. They make debug pages much easier to // prototype, while the risk of user-injected CSS is relatively low. w.Header().Set("Content-Security-Policy", "default-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; block-all-mixed-content; object-src 'none'; style-src 'self' 'unsafe-inline'") h.ServeHTTP(w, r) })}