web_client.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. //go:build !ios && !android && !ts_omit_webclient
  4. package ipnlocal
  5. import (
  6. "context"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "io"
  11. "net"
  12. "net/http"
  13. "net/netip"
  14. "sync"
  15. "time"
  16. "tailscale.com/client/local"
  17. "tailscale.com/client/web"
  18. "tailscale.com/net/netutil"
  19. "tailscale.com/tailcfg"
  20. "tailscale.com/tsconst"
  21. "tailscale.com/types/logger"
  22. "tailscale.com/util/backoff"
  23. "tailscale.com/util/mak"
  24. )
  25. const webClientPort = tsconst.WebListenPort
  26. // webClient holds state for the web interface for managing this
  27. // tailscale instance. The web interface is not used by default,
  28. // but initialized by calling LocalBackend.WebClientGetOrInit.
  29. type webClient struct {
  30. mu sync.Mutex // protects webClient fields
  31. server *web.Server // or nil, initialized lazily
  32. // lc optionally specifies a local.Client to use to connect
  33. // to the localapi for this tailscaled instance.
  34. // If nil, a default is used.
  35. lc *local.Client
  36. }
  37. // ConfigureWebClient configures b.web prior to use.
  38. // Specifially, it sets b.web.lc to the provided local.Client.
  39. // If provided as nil, b.web.lc is cleared out.
  40. func (b *LocalBackend) ConfigureWebClient(lc *local.Client) {
  41. b.webClient.mu.Lock()
  42. defer b.webClient.mu.Unlock()
  43. b.webClient.lc = lc
  44. }
  45. // webClientGetOrInit gets or initializes the web server for managing
  46. // this tailscaled instance.
  47. // s is always non-nil if err is empty.
  48. func (b *LocalBackend) webClientGetOrInit() (s *web.Server, err error) {
  49. if !b.ShouldRunWebClient() {
  50. return nil, errors.New("web client not enabled for this device")
  51. }
  52. b.webClient.mu.Lock()
  53. defer b.webClient.mu.Unlock()
  54. if b.webClient.server != nil {
  55. return b.webClient.server, nil
  56. }
  57. b.logf("webClientGetOrInit: initializing web ui")
  58. if b.webClient.server, err = web.NewServer(web.ServerOpts{
  59. Mode: web.ManageServerMode,
  60. LocalClient: b.webClient.lc,
  61. Logf: b.logf,
  62. NewAuthURL: b.newWebClientAuthURL,
  63. WaitAuthURL: b.waitWebClientAuthURL,
  64. }); err != nil {
  65. return nil, fmt.Errorf("web.NewServer: %w", err)
  66. }
  67. b.logf("webClientGetOrInit: started web ui")
  68. return b.webClient.server, nil
  69. }
  70. // WebClientShutdown shuts down any running b.webClient servers and
  71. // clears out b.webClient state (besides the b.webClient.lc field,
  72. // which is left untouched because required for future web startups).
  73. // WebClientShutdown obtains the b.mu lock.
  74. func (b *LocalBackend) webClientShutdown() {
  75. b.mu.Lock()
  76. for ap, ln := range b.webClientListeners {
  77. ln.Close()
  78. delete(b.webClientListeners, ap)
  79. }
  80. b.mu.Unlock()
  81. b.webClient.mu.Lock() // webClient struct uses its own mutext
  82. server := b.webClient.server
  83. b.webClient.server = nil
  84. b.webClient.mu.Unlock() // release lock before shutdown
  85. if server != nil {
  86. server.Shutdown()
  87. b.logf("WebClientShutdown: shut down web ui")
  88. }
  89. }
  90. // handleWebClientConn serves web client requests.
  91. func (b *LocalBackend) handleWebClientConn(c net.Conn) error {
  92. webServer, err := b.webClientGetOrInit()
  93. if err != nil {
  94. return err
  95. }
  96. s := http.Server{Handler: webServer}
  97. return s.Serve(netutil.NewOneConnListener(c, nil))
  98. }
  99. // updateWebClientListenersLocked creates listeners on the web client port (5252)
  100. // for each of the local device's Tailscale IP addresses. This is needed to properly
  101. // route local traffic when using kernel networking mode.
  102. func (b *LocalBackend) updateWebClientListenersLocked() {
  103. nm := b.currentNode().NetMap()
  104. if nm == nil {
  105. return
  106. }
  107. addrs := nm.GetAddresses()
  108. for _, pfx := range addrs.All() {
  109. addrPort := netip.AddrPortFrom(pfx.Addr(), webClientPort)
  110. if _, ok := b.webClientListeners[addrPort]; ok {
  111. continue // already listening
  112. }
  113. sl := b.newWebClientListener(context.Background(), addrPort, b.logf)
  114. mak.Set(&b.webClientListeners, addrPort, sl)
  115. go sl.Run()
  116. }
  117. }
  118. // newWebClientListener returns a listener for local connections to the built-in web client
  119. // used to manage this Tailscale instance.
  120. func (b *LocalBackend) newWebClientListener(ctx context.Context, ap netip.AddrPort, logf logger.Logf) *localListener {
  121. ctx, cancel := context.WithCancel(ctx)
  122. return &localListener{
  123. b: b,
  124. ap: ap,
  125. ctx: ctx,
  126. cancel: cancel,
  127. logf: logf,
  128. handler: b.handleWebClientConn,
  129. bo: backoff.NewBackoff("webclient-listener", logf, 30*time.Second),
  130. }
  131. }
  132. // newWebClientAuthURL talks to the control server to create a new auth
  133. // URL that can be used to validate a browser session to manage this
  134. // tailscaled instance via the web client.
  135. func (b *LocalBackend) newWebClientAuthURL(ctx context.Context, src tailcfg.NodeID) (*tailcfg.WebClientAuthResponse, error) {
  136. return b.doWebClientNoiseRequest(ctx, "", src)
  137. }
  138. // waitWebClientAuthURL connects to the control server and blocks
  139. // until the associated auth URL has been completed by its user,
  140. // or until ctx is canceled.
  141. func (b *LocalBackend) waitWebClientAuthURL(ctx context.Context, id string, src tailcfg.NodeID) (*tailcfg.WebClientAuthResponse, error) {
  142. return b.doWebClientNoiseRequest(ctx, id, src)
  143. }
  144. // doWebClientNoiseRequest handles making the "/machine/webclient"
  145. // noise requests to the control server for web client user auth.
  146. //
  147. // It either creates a new control auth URL or waits for an existing
  148. // one to be completed, based on the presence or absence of the
  149. // provided id value.
  150. func (b *LocalBackend) doWebClientNoiseRequest(ctx context.Context, id string, src tailcfg.NodeID) (*tailcfg.WebClientAuthResponse, error) {
  151. nm := b.NetMap()
  152. if nm == nil || !nm.SelfNode.Valid() {
  153. return nil, errors.New("[unexpected] no self node")
  154. }
  155. dst := nm.SelfNode.ID()
  156. var noiseURL string
  157. if id != "" {
  158. noiseURL = fmt.Sprintf("https://unused/machine/webclient/wait/%d/to/%d/%s", src, dst, id)
  159. } else {
  160. noiseURL = fmt.Sprintf("https://unused/machine/webclient/init/%d/to/%d", src, dst)
  161. }
  162. req, err := http.NewRequestWithContext(ctx, "POST", noiseURL, nil)
  163. if err != nil {
  164. return nil, err
  165. }
  166. resp, err := b.DoNoiseRequest(req)
  167. if err != nil {
  168. return nil, err
  169. }
  170. body, _ := io.ReadAll(resp.Body)
  171. resp.Body.Close()
  172. if resp.StatusCode != http.StatusOK {
  173. return nil, fmt.Errorf("failed request: %s", body)
  174. }
  175. var authResp *tailcfg.WebClientAuthResponse
  176. if err := json.Unmarshal(body, &authResp); err != nil {
  177. return nil, err
  178. }
  179. return authResp, nil
  180. }