debug.go 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package tsweb
  4. import (
  5. "expvar"
  6. "fmt"
  7. "html"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "os"
  12. "runtime"
  13. "tailscale.com/feature"
  14. "tailscale.com/tsweb/varz"
  15. "tailscale.com/version"
  16. )
  17. // DebugHandler is an http.Handler that serves a debugging "homepage",
  18. // and provides helpers to register more debug endpoints and reports.
  19. //
  20. // The rendered page consists of three sections: informational
  21. // key/value pairs, links to other pages, and additional
  22. // program-specific HTML. Callers can add to these sections using the
  23. // KV, URL and Section helpers respectively.
  24. //
  25. // Additionally, the Handle method offers a shorthand for correctly
  26. // registering debug handlers and cross-linking them from /debug/.
  27. type DebugHandler struct {
  28. mux *http.ServeMux // where this handler is registered
  29. kvs []func(io.Writer) // output one <li>...</li> each, see KV()
  30. urls []string // one <li>...</li> block with link each
  31. sections []func(io.Writer, *http.Request) // invoked in registration order prior to outputting </body>
  32. title string // title displayed on index page
  33. }
  34. // PrometheusHandler is an optional hook to enable native Prometheus
  35. // support in the debug handler. It is disabled by default. Import the
  36. // tailscale.com/tsweb/promvarz package to enable this feature.
  37. var PrometheusHandler feature.Hook[func(*DebugHandler)]
  38. // Debugger returns the DebugHandler registered on mux at /debug/,
  39. // creating it if necessary.
  40. func Debugger(mux *http.ServeMux) *DebugHandler {
  41. h, pat := mux.Handler(&http.Request{URL: &url.URL{Path: "/debug/"}})
  42. if d, ok := h.(*DebugHandler); ok && pat == "/debug/" {
  43. return d
  44. }
  45. ret := &DebugHandler{
  46. mux: mux,
  47. title: fmt.Sprintf("%s debug", version.CmdName()),
  48. }
  49. mux.Handle("/debug/", ret)
  50. ret.KVFunc("Uptime", func() any { return varz.Uptime() })
  51. ret.KV("Version", version.Long())
  52. ret.Handle("vars", "Metrics (Go)", expvar.Handler())
  53. if PrometheusHandler.IsSet() {
  54. PrometheusHandler.Get()(ret)
  55. } else {
  56. ret.Handle("varz", "Metrics (Prometheus)", http.HandlerFunc(varz.Handler))
  57. }
  58. addProfilingHandlers(ret)
  59. ret.Handle("gc", "force GC", http.HandlerFunc(gcHandler))
  60. hostname, err := os.Hostname()
  61. if err == nil {
  62. ret.KV("Machine", hostname)
  63. }
  64. return ret
  65. }
  66. // ServeHTTP implements http.Handler.
  67. func (d *DebugHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  68. if !AllowDebugAccess(r) {
  69. http.Error(w, "debug access denied", http.StatusForbidden)
  70. return
  71. }
  72. if r.URL.Path != "/debug/" {
  73. // Sub-handlers are handled by the parent mux directly.
  74. http.NotFound(w, r)
  75. return
  76. }
  77. AddBrowserHeaders(w)
  78. f := func(format string, args ...any) { fmt.Fprintf(w, format, args...) }
  79. f("<html><body><h1>%s</h1><ul>", html.EscapeString(d.title))
  80. for _, kv := range d.kvs {
  81. kv(w)
  82. }
  83. for _, url := range d.urls {
  84. io.WriteString(w, url)
  85. }
  86. for _, section := range d.sections {
  87. section(w, r)
  88. }
  89. }
  90. func (d *DebugHandler) handle(slug string, handler http.Handler) string {
  91. href := "/debug/" + slug
  92. d.mux.Handle(href, Protected(debugBrowserHeaderHandler(handler)))
  93. return href
  94. }
  95. // Handle registers handler at /debug/<slug> and adds a link to it
  96. // on /debug/ with the provided description.
  97. func (d *DebugHandler) Handle(slug, desc string, handler http.Handler) {
  98. href := d.handle(slug, handler)
  99. d.URL(href, desc)
  100. }
  101. // Handle registers handler at /debug/<slug> and adds a link to it
  102. // on /debug/ with the provided description.
  103. func (d *DebugHandler) HandleFunc(slug, desc string, handler http.HandlerFunc) {
  104. d.Handle(slug, desc, handler)
  105. }
  106. // HandleSilent registers handler at /debug/<slug>. It does not add
  107. // a descriptive entry in /debug/ for it. This should be used
  108. // sparingly, for things that need to be registered but would pollute
  109. // the list of debug links.
  110. func (d *DebugHandler) HandleSilent(slug string, handler http.Handler) {
  111. d.handle(slug, handler)
  112. }
  113. // HandleSilent registers handler at /debug/<slug>. It does not add
  114. // a descriptive entry in /debug/ for it. This should be used
  115. // sparingly, for things that need to be registered but would pollute
  116. // the list of debug links.
  117. func (d *DebugHandler) HandleSilentFunc(slug string, handler http.HandlerFunc) {
  118. d.HandleSilent(slug, handler)
  119. }
  120. // KV adds a key/value list item to /debug/.
  121. func (d *DebugHandler) KV(k string, v any) {
  122. val := html.EscapeString(fmt.Sprintf("%v", v))
  123. d.kvs = append(d.kvs, func(w io.Writer) {
  124. fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val)
  125. })
  126. }
  127. // KVFunc adds a key/value list item to /debug/. v is called on every
  128. // render of /debug/.
  129. func (d *DebugHandler) KVFunc(k string, v func() any) {
  130. d.kvs = append(d.kvs, func(w io.Writer) {
  131. val := html.EscapeString(fmt.Sprintf("%v", v()))
  132. fmt.Fprintf(w, "<li><b>%s:</b> %s</li>", k, val)
  133. })
  134. }
  135. // URL adds a URL and description list item to /debug/.
  136. func (d *DebugHandler) URL(url, desc string) {
  137. if desc != "" {
  138. desc = " (" + desc + ")"
  139. }
  140. d.urls = append(d.urls, fmt.Sprintf(`<li><a href="%s">%s</a>%s</li>`, url, url, html.EscapeString(desc)))
  141. }
  142. // Section invokes f on every render of /debug/ to add supplemental
  143. // HTML to the page body.
  144. func (d *DebugHandler) Section(f func(w io.Writer, r *http.Request)) {
  145. d.sections = append(d.sections, f)
  146. }
  147. // Title sets the title at the top of the debug page.
  148. func (d *DebugHandler) Title(title string) {
  149. d.title = title
  150. }
  151. func gcHandler(w http.ResponseWriter, r *http.Request) {
  152. w.Write([]byte("running GC...\n"))
  153. if f, ok := w.(http.Flusher); ok {
  154. f.Flush()
  155. }
  156. runtime.GC()
  157. w.Write([]byte("Done.\n"))
  158. }
  159. // debugBrowserHeaderHandler is a wrapper around BrowserHeaderHandler with a
  160. // more relaxed Content-Security-Policy that's acceptable for internal debug
  161. // pages. It should not be used on any public-facing handlers!
  162. func debugBrowserHeaderHandler(h http.Handler) http.Handler {
  163. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  164. AddBrowserHeaders(w)
  165. // The only difference from AddBrowserHeaders is that this policy
  166. // allows inline CSS styles. They make debug pages much easier to
  167. // prototype, while the risk of user-injected CSS is relatively low.
  168. 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'")
  169. h.ServeHTTP(w, r)
  170. })
  171. }