|
|
@ -32,12 +32,14 @@ import (
|
|
|
|
"tailscale.com/licenses"
|
|
|
|
"tailscale.com/licenses"
|
|
|
|
"tailscale.com/net/netutil"
|
|
|
|
"tailscale.com/net/netutil"
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
"tailscale.com/tailcfg"
|
|
|
|
|
|
|
|
"tailscale.com/types/logger"
|
|
|
|
"tailscale.com/util/httpm"
|
|
|
|
"tailscale.com/util/httpm"
|
|
|
|
"tailscale.com/version/distro"
|
|
|
|
"tailscale.com/version/distro"
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// Server is the backend server for a Tailscale web client.
|
|
|
|
// Server is the backend server for a Tailscale web client.
|
|
|
|
type Server struct {
|
|
|
|
type Server struct {
|
|
|
|
|
|
|
|
logf logger.Logf
|
|
|
|
lc *tailscale.LocalClient
|
|
|
|
lc *tailscale.LocalClient
|
|
|
|
timeNow func() time.Time
|
|
|
|
timeNow func() time.Time
|
|
|
|
|
|
|
|
|
|
|
@ -136,6 +138,8 @@ type ServerOpts struct {
|
|
|
|
// TimeNow optionally provides a time function.
|
|
|
|
// TimeNow optionally provides a time function.
|
|
|
|
// time.Now is used as default.
|
|
|
|
// time.Now is used as default.
|
|
|
|
TimeNow func() time.Time
|
|
|
|
TimeNow func() time.Time
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Logf logger.Logf
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// NewServer constructs a new Tailscale web client server.
|
|
|
|
// NewServer constructs a new Tailscale web client server.
|
|
|
@ -144,6 +148,7 @@ func NewServer(opts ServerOpts) (s *Server, cleanup func()) {
|
|
|
|
opts.LocalClient = &tailscale.LocalClient{}
|
|
|
|
opts.LocalClient = &tailscale.LocalClient{}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
s = &Server{
|
|
|
|
s = &Server{
|
|
|
|
|
|
|
|
logf: opts.Logf,
|
|
|
|
devMode: opts.DevMode,
|
|
|
|
devMode: opts.DevMode,
|
|
|
|
lc: opts.LocalClient,
|
|
|
|
lc: opts.LocalClient,
|
|
|
|
cgiMode: opts.CGIMode,
|
|
|
|
cgiMode: opts.CGIMode,
|
|
|
@ -153,9 +158,14 @@ func NewServer(opts ServerOpts) (s *Server, cleanup func()) {
|
|
|
|
if s.timeNow == nil {
|
|
|
|
if s.timeNow == nil {
|
|
|
|
s.timeNow = time.Now
|
|
|
|
s.timeNow = time.Now
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.logf == nil {
|
|
|
|
|
|
|
|
s.logf = log.Printf
|
|
|
|
|
|
|
|
}
|
|
|
|
s.tsDebugMode = s.debugMode()
|
|
|
|
s.tsDebugMode = s.debugMode()
|
|
|
|
s.assetsHandler, cleanup = assetsHandler(opts.DevMode)
|
|
|
|
s.assetsHandler, cleanup = assetsHandler(opts.DevMode)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var metric string // clientmetric to report on startup
|
|
|
|
|
|
|
|
|
|
|
|
// Create handler for "/api" requests with CSRF protection.
|
|
|
|
// Create handler for "/api" requests with CSRF protection.
|
|
|
|
// We don't require secure cookies, since the web client is regularly used
|
|
|
|
// We don't require secure cookies, since the web client is regularly used
|
|
|
|
// on network appliances that are served on local non-https URLs.
|
|
|
|
// on network appliances that are served on local non-https URLs.
|
|
|
@ -166,12 +176,19 @@ func NewServer(opts ServerOpts) (s *Server, cleanup func()) {
|
|
|
|
// For the login client, we don't serve the full web client API,
|
|
|
|
// For the login client, we don't serve the full web client API,
|
|
|
|
// only the login endpoints.
|
|
|
|
// only the login endpoints.
|
|
|
|
s.apiHandler = csrfProtect(http.HandlerFunc(s.serveLoginAPI))
|
|
|
|
s.apiHandler = csrfProtect(http.HandlerFunc(s.serveLoginAPI))
|
|
|
|
s.lc.IncrementCounter(context.Background(), "web_login_client_initialization", 1)
|
|
|
|
metric = "web_login_client_initialization"
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
s.apiHandler = csrfProtect(http.HandlerFunc(s.serveAPI))
|
|
|
|
s.apiHandler = csrfProtect(http.HandlerFunc(s.serveAPI))
|
|
|
|
s.lc.IncrementCounter(context.Background(), "web_client_initialization", 1)
|
|
|
|
metric = "web_client_initialization"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// Report metric in separate go routine with 5 second timeout.
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
s.lc.IncrementCounter(ctx, metric, 1)
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
|
|
|
|
return s, cleanup
|
|
|
|
return s, cleanup
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
@ -645,7 +662,7 @@ func (s *Server) servePostNodeUpdate(w http.ResponseWriter, r *http.Request) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
mp.Prefs.WantRunning = true
|
|
|
|
mp.Prefs.WantRunning = true
|
|
|
|
mp.Prefs.AdvertiseRoutes = routes
|
|
|
|
mp.Prefs.AdvertiseRoutes = routes
|
|
|
|
log.Printf("Doing edit: %v", mp.Pretty())
|
|
|
|
s.logf("Doing edit: %v", mp.Pretty())
|
|
|
|
|
|
|
|
|
|
|
|
if _, err := s.lc.EditPrefs(r.Context(), mp); err != nil {
|
|
|
|
if _, err := s.lc.EditPrefs(r.Context(), mp); err != nil {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
@ -661,9 +678,9 @@ func (s *Server) servePostNodeUpdate(w http.ResponseWriter, r *http.Request) {
|
|
|
|
if postData.ForceLogout {
|
|
|
|
if postData.ForceLogout {
|
|
|
|
logout = true
|
|
|
|
logout = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
log.Printf("tailscaleUp(reauth=%v, logout=%v) ...", reauth, logout)
|
|
|
|
s.logf("tailscaleUp(reauth=%v, logout=%v) ...", reauth, logout)
|
|
|
|
url, err := s.tailscaleUp(r.Context(), st, postData)
|
|
|
|
url, err := s.tailscaleUp(r.Context(), st, postData)
|
|
|
|
log.Printf("tailscaleUp = (URL %v, %v)", url != "", err)
|
|
|
|
s.logf("tailscaleUp = (URL %v, %v)", url != "", err)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
|
|
|
json.NewEncoder(w).Encode(mi{"error": err.Error()})
|
|
|
|