mirror of https://github.com/tailscale/tailscale/
ipn/ipnlocal,client/web: add web client to tailscaled
Allows for serving the web interface from tailscaled, with the ability to start and stop the server via localapi endpoints (/web/start and /web/stop). This will be used to run the new full management web client, which will only be accessible over Tailscale (with an extra auth check step over noise) from the daemon. This switch also allows us to run the web interface as a long-lived service in environments where the CLI version is restricted to CGI, allowing us to manage certain auth state in memory. ipn/ipnlocal/web is stubbed out in ipn/ipnlocal/web_stub for ios builds to satisfy ios restriction from adding "text/template" and "html/template" dependencies. Updates tailscale/corp#14335 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>pull/9987/head
parent
93aa8a8cff
commit
89953b015b
@ -0,0 +1,110 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build !ios && !android
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"tailscale.com/client/tailscale"
|
||||
"tailscale.com/client/web"
|
||||
"tailscale.com/envknob"
|
||||
)
|
||||
|
||||
// webServer holds state for the web interface for managing
|
||||
// this tailscale instance. The web interface is not used by
|
||||
// default, but initialized by calling LocalBackend.WebOrInit.
|
||||
type webServer struct {
|
||||
ws *web.Server // or nil, initialized lazily
|
||||
httpServer *http.Server // or nil, initialized lazily
|
||||
|
||||
// lc optionally specifies a LocalClient to use to connect
|
||||
// to the localapi for this tailscaled instance.
|
||||
// If nil, a default is used.
|
||||
lc *tailscale.LocalClient
|
||||
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// SetWebLocalClient sets the b.web.lc function.
|
||||
// If lc is provided as nil, b.web.lc is cleared out.
|
||||
func (b *LocalBackend) SetWebLocalClient(lc *tailscale.LocalClient) {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
b.web.lc = lc
|
||||
}
|
||||
|
||||
// WebInit initializes the web interface for managing
|
||||
// this tailscaled instance. If the web interface is
|
||||
// already running, WebInit is a no-op.
|
||||
func (b *LocalBackend) WebInit() (err error) {
|
||||
if !envknob.Bool("TS_DEBUG_WEB_UI") {
|
||||
return errors.New("web ui flag unset")
|
||||
}
|
||||
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
if b.web.ws != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
b.logf("WebInit: initializing web ui")
|
||||
if b.web.ws, err = web.NewServer(web.ServerOpts{
|
||||
// TODO(sonia): allow passing back dev mode flag
|
||||
LocalClient: b.web.lc,
|
||||
Logf: b.logf,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("web.NewServer: %w", err)
|
||||
}
|
||||
|
||||
// Start up the server.
|
||||
b.web.wg.Add(1)
|
||||
go func() {
|
||||
defer b.web.wg.Done()
|
||||
// TODO(sonia/will): only listen on Tailscale IP addresses
|
||||
addr := ":5252"
|
||||
b.web.httpServer = &http.Server{
|
||||
Addr: addr,
|
||||
Handler: http.HandlerFunc(b.web.ws.ServeHTTP),
|
||||
}
|
||||
b.logf("WebInit: serving web ui on %s", addr)
|
||||
if err := b.web.httpServer.ListenAndServe(); err != nil {
|
||||
if err != http.ErrServerClosed {
|
||||
b.logf("[unexpected] WebInit: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
b.logf("WebInit: started web ui")
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebShutdown shuts down any running b.web servers and
|
||||
// clears out b.web state (besides the b.web.lc field,
|
||||
// which is left untouched because required for future
|
||||
// web startups).
|
||||
// WebShutdown obtains the b.mu lock.
|
||||
func (b *LocalBackend) WebShutdown() {
|
||||
b.mu.Lock()
|
||||
webS := b.web.ws
|
||||
httpS := b.web.httpServer
|
||||
b.web.ws = nil
|
||||
b.web.httpServer = nil
|
||||
b.mu.Unlock() // release lock before shutdown
|
||||
if webS != nil {
|
||||
b.web.ws.Shutdown()
|
||||
}
|
||||
if httpS != nil {
|
||||
if err := b.web.httpServer.Shutdown(context.Background()); err != nil {
|
||||
b.logf("[unexpected] WebShutdown: %v", err)
|
||||
}
|
||||
}
|
||||
b.web.wg.Wait()
|
||||
b.logf("WebShutdown: shut down web ui")
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
// Copyright (c) Tailscale Inc & AUTHORS
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build ios || android
|
||||
|
||||
package ipnlocal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"tailscale.com/client/tailscale"
|
||||
)
|
||||
|
||||
type webServer struct{}
|
||||
|
||||
func (b *LocalBackend) SetWebLocalClient(lc *tailscale.LocalClient) {}
|
||||
|
||||
func (b *LocalBackend) WebInit() error {
|
||||
return errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (b *LocalBackend) WebShutdown() {}
|
Loading…
Reference in New Issue