microproxy.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. // Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. // microproxy proxies incoming HTTPS connections to another
  5. // destination. Instead of managing its own TLS certificates, it
  6. // borrows issued certificates and keys from an autocert directory.
  7. package main
  8. import (
  9. "crypto/tls"
  10. "encoding/json"
  11. "flag"
  12. "fmt"
  13. "io"
  14. "io/ioutil"
  15. "log"
  16. "net/http"
  17. "net/http/httputil"
  18. "net/url"
  19. "path/filepath"
  20. "sync"
  21. "time"
  22. "tailscale.com/logpolicy"
  23. "tailscale.com/tsweb"
  24. )
  25. var (
  26. addr = flag.String("addr", ":4430", "server address")
  27. certdir = flag.String("certdir", "", "directory to borrow LetsEncrypt certificates from")
  28. hostname = flag.String("hostname", "", "hostname to serve")
  29. logCollection = flag.String("logcollection", "", "If non-empty, logtail collection to log to")
  30. nodeExporter = flag.String("node-exporter", "http://localhost:9100", "URL of the local prometheus node exporter")
  31. goVarsURL = flag.String("go-vars-url", "http://localhost:8383/debug/vars", "URL of a local Go server's /debug/vars endpoint")
  32. )
  33. func main() {
  34. flag.Parse()
  35. if *logCollection != "" {
  36. logpolicy.New(*logCollection)
  37. }
  38. ne, err := url.Parse(*nodeExporter)
  39. if err != nil {
  40. log.Fatalf("Couldn't parse URL %q: %v", *nodeExporter, err)
  41. }
  42. proxy := httputil.NewSingleHostReverseProxy(ne)
  43. proxy.FlushInterval = time.Second
  44. if _, err = url.Parse(*goVarsURL); err != nil {
  45. log.Fatalf("Couldn't parse URL %q: %v", *goVarsURL, err)
  46. }
  47. mux := tsweb.NewMux(http.HandlerFunc(debugHandler))
  48. mux.Handle("/metrics", tsweb.Protected(proxy))
  49. mux.Handle("/varz", tsweb.Protected(tsweb.StdHandler(&goVarsHandler{*goVarsURL}, log.Printf)))
  50. ch := &certHolder{
  51. hostname: *hostname,
  52. path: filepath.Join(*certdir, *hostname),
  53. }
  54. httpsrv := &http.Server{
  55. Addr: *addr,
  56. Handler: mux,
  57. TLSConfig: &tls.Config{
  58. GetCertificate: ch.GetCertificate,
  59. },
  60. }
  61. if err := httpsrv.ListenAndServeTLS("", ""); err != nil && err != http.ErrServerClosed {
  62. log.Fatal(err)
  63. }
  64. }
  65. type goVarsHandler struct {
  66. url string
  67. }
  68. func promPrint(w io.Writer, prefix string, obj map[string]interface{}) {
  69. for k, i := range obj {
  70. if prefix != "" {
  71. k = prefix + "_" + k
  72. }
  73. switch v := i.(type) {
  74. case map[string]interface{}:
  75. promPrint(w, k, v)
  76. case float64:
  77. fmt.Fprintf(w, "%s %f\n", k, v)
  78. default:
  79. fmt.Fprintf(w, "# Skipping key %q, unhandled type %T\n", k, v)
  80. }
  81. }
  82. }
  83. func (h *goVarsHandler) ServeHTTPReturn(w http.ResponseWriter, r *http.Request) error {
  84. resp, err := http.Get(h.url)
  85. if err != nil {
  86. return tsweb.Error(http.StatusInternalServerError, "fetch failed", err)
  87. }
  88. defer resp.Body.Close()
  89. var mon map[string]interface{}
  90. if err := json.NewDecoder(resp.Body).Decode(&mon); err != nil {
  91. return tsweb.Error(http.StatusInternalServerError, "fetch failed", err)
  92. }
  93. w.WriteHeader(http.StatusOK)
  94. promPrint(w, "", mon)
  95. return nil
  96. }
  97. // certHolder loads and caches a TLS certificate from disk, reloading
  98. // it every hour.
  99. type certHolder struct {
  100. hostname string // only hostname allowed in SNI
  101. path string // path of certificate+key combined PEM file
  102. mu sync.Mutex
  103. cert *tls.Certificate // cached parsed cert+key
  104. loaded time.Time
  105. }
  106. func (c *certHolder) GetCertificate(ch *tls.ClientHelloInfo) (*tls.Certificate, error) {
  107. if ch.ServerName != c.hostname {
  108. return nil, fmt.Errorf("wrong client SNI %q", ch.ServerName)
  109. }
  110. c.mu.Lock()
  111. defer c.mu.Unlock()
  112. if time.Since(c.loaded) > time.Hour {
  113. if err := c.loadLocked(); err != nil {
  114. log.Printf("Reloading cert %q: %v", c.path, err)
  115. // continue anyway, we might be able to serve off the stale cert.
  116. }
  117. }
  118. return c.cert, nil
  119. }
  120. // load reloads the TLS certificate and key from disk. Caller must
  121. // hold mu.
  122. func (c *certHolder) loadLocked() error {
  123. bs, err := ioutil.ReadFile(c.path)
  124. if err != nil {
  125. return fmt.Errorf("reading %q: %v", c.path, err)
  126. }
  127. cert, err := tls.X509KeyPair(bs, bs)
  128. if err != nil {
  129. return fmt.Errorf("parsing %q: %v", c.path, err)
  130. }
  131. c.cert = &cert
  132. c.loaded = time.Now()
  133. return nil
  134. }
  135. // debugHandler serves a page with links to tsweb-managed debug URLs
  136. // at /debug/.
  137. func debugHandler(w http.ResponseWriter, r *http.Request) {
  138. f := func(format string, args ...interface{}) { fmt.Fprintf(w, format, args...) }
  139. f(`<html><body>
  140. <h1>microproxy debug</h1>
  141. <ul>
  142. `)
  143. f("<li><b>Hostname:</b> %v</li>\n", *hostname)
  144. f("<li><b>Uptime:</b> %v</li>\n", tsweb.Uptime())
  145. f(`<li><a href="/debug/vars">/debug/vars</a> (Go)</li>
  146. <li><a href="/debug/varz">/debug/varz</a> (Prometheus)</li>
  147. <li><a href="/debug/pprof/">/debug/pprof/</a></li>
  148. <li><a href="/debug/pprof/goroutine?debug=1">/debug/pprof/goroutine</a> (collapsed)</li>
  149. <li><a href="/debug/pprof/goroutine?debug=2">/debug/pprof/goroutine</a> (full)</li>
  150. <ul>
  151. </html>
  152. `)
  153. }