Parcourir la source

client/web,cmd/tailscale: add prefix flag for web command

We already had a path on the web client server struct, but hadn't
plumbed it through to the CLI. Add that now and use it for Synology and
QNAP instead of hard-coding the path. (Adding flag for QNAP is
tailscale/tailscale-qpkg#112) This will allow supporting other
environments (like unraid) without additional changes to the client/web
package.

Also fix a small bug in unraid handling to only include the csrf token
on POST requests.

Updates tailscale/corp#13775

Signed-off-by: Will Norris <[email protected]>
Will Norris il y a 2 ans
Parent
commit
9a3bc9049c

+ 0 - 2
client/web/qnap.go

@@ -16,8 +16,6 @@ import (
 	"net/url"
 )
 
-const qnapPrefix = "/cgi-bin/qpkg/Tailscale/index.cgi/"
-
 // authorizeQNAP authenticates the logged-in QNAP user and verifies
 // that they are authorized to use the web client.  It returns true if the
 // request was handled and no further processing is required.

+ 1 - 1
client/web/src/api.ts

@@ -23,7 +23,7 @@ export function apiFetch(
   const url = `api${endpoint}${search ? `?${search}` : ""}`
 
   var contentType: string
-  if (unraidCsrfToken) {
+  if (unraidCsrfToken && method === "POST") {
     const params = new URLSearchParams()
     params.append("csrf_token", unraidCsrfToken)
     if (body) {

+ 0 - 2
client/web/synology.go

@@ -15,8 +15,6 @@ import (
 	"tailscale.com/util/groupmember"
 )
 
-const synologyPrefix = "/webman/3rdparty/Tailscale/index.cgi/"
-
 // authorizeSynology authenticates the logged-in Synology user and verifies
 // that they are authorized to use the web client.  It returns true if the
 // request was handled and no further processing is required.

+ 23 - 22
client/web/web.go

@@ -58,7 +58,7 @@ type Server struct {
 	devProxy *httputil.ReverseProxy // only filled when devMode is on
 
 	cgiMode    bool
-	cgiPath    string
+	pathPrefix string
 	apiHandler http.Handler // csrf-protected api handler
 }
 
@@ -69,8 +69,8 @@ type ServerOpts struct {
 	// CGIMode indicates if the server is running as a CGI script.
 	CGIMode bool
 
-	// If running in CGIMode, CGIPath is the URL path prefix to the CGI script.
-	CGIPath string
+	// PathPrefix is the URL prefix added to requests by CGI or reverse proxy.
+	PathPrefix string
 
 	// LocalClient is the tailscale.LocalClient to use for this web server.
 	// If nil, a new one will be created.
@@ -84,10 +84,10 @@ func NewServer(ctx context.Context, opts ServerOpts) (s *Server, cleanup func())
 		opts.LocalClient = &tailscale.LocalClient{}
 	}
 	s = &Server{
-		devMode: opts.DevMode,
-		lc:      opts.LocalClient,
-		cgiMode: opts.CGIMode,
-		cgiPath: opts.CGIPath,
+		devMode:    opts.DevMode,
+		lc:         opts.LocalClient,
+		cgiMode:    opts.CGIMode,
+		pathPrefix: opts.PathPrefix,
 	}
 	cleanup = func() {}
 	if s.devMode {
@@ -116,20 +116,9 @@ func init() {
 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	handler := s.serve
 
-	// if running in cgi mode, strip the cgi path prefix
-	if s.cgiMode {
-		prefix := s.cgiPath
-		if prefix == "" {
-			switch distro.Get() {
-			case distro.Synology:
-				prefix = synologyPrefix
-			case distro.QNAP:
-				prefix = qnapPrefix
-			}
-		}
-		if prefix != "" {
-			handler = enforcePrefix(prefix, handler)
-		}
+	// if path prefix is defined, strip it from requests.
+	if s.pathPrefix != "" {
+		handler = enforcePrefix(s.pathPrefix, handler)
 	}
 
 	handler(w, r)
@@ -334,7 +323,6 @@ func (s *Server) servePostNodeUpdate(w http.ResponseWriter, r *http.Request) {
 	} else {
 		io.WriteString(w, "{}")
 	}
-	return
 }
 
 func (s *Server) tailscaleUp(ctx context.Context, st *ipnstate.Status, postData nodeUpdate) (authURL string, retErr error) {
@@ -487,6 +475,19 @@ func (s *Server) csrfKey() []byte {
 // Unlike http.StripPrefix, it does not return a 404 if the prefix is not present.
 // Instead, it returns a redirect to the prefix path.
 func enforcePrefix(prefix string, h http.HandlerFunc) http.HandlerFunc {
+	if prefix == "" {
+		return h
+	}
+
+	// ensure that prefix always has both a leading and trailing slash so
+	// that relative links for JS and CSS assets work correctly.
+	if !strings.HasPrefix(prefix, "/") {
+		prefix = "/" + prefix
+	}
+	if !strings.HasSuffix(prefix, "/") {
+		prefix += "/"
+	}
+
 	return func(w http.ResponseWriter, r *http.Request) {
 		if !strings.HasPrefix(r.URL.Path, prefix) {
 			http.Redirect(w, r, prefix, http.StatusFound)

+ 3 - 0
cmd/tailscale/cli/web.go

@@ -39,6 +39,7 @@ Tailscale, as opposed to a CLI or a native app.
 		webf.StringVar(&webArgs.listen, "listen", "localhost:8088", "listen address; use port 0 for automatic")
 		webf.BoolVar(&webArgs.cgi, "cgi", false, "run as CGI script")
 		webf.BoolVar(&webArgs.dev, "dev", false, "run web client in developer mode [this flag is in development, use is unsupported]")
+		webf.StringVar(&webArgs.prefix, "prefix", "", "URL prefix added to requests (for cgi or reverse proxies)")
 		return webf
 	})(),
 	Exec: runWeb,
@@ -48,6 +49,7 @@ var webArgs struct {
 	listen string
 	cgi    bool
 	dev    bool
+	prefix string
 }
 
 func tlsConfigFromEnvironment() *tls.Config {
@@ -81,6 +83,7 @@ func runWeb(ctx context.Context, args []string) error {
 	webServer, cleanup := web.NewServer(ctx, web.ServerOpts{
 		DevMode:     webArgs.dev,
 		CGIMode:     webArgs.cgi,
+		PathPrefix:  webArgs.prefix,
 		LocalClient: &localClient,
 	})
 	defer cleanup()

+ 1 - 1
release/dist/synology/files/index.cgi

@@ -1,2 +1,2 @@
 #! /bin/sh
-exec /var/packages/Tailscale/target/bin/tailscale web -cgi
+exec /var/packages/Tailscale/target/bin/tailscale web -cgi -prefix="/webman/3rdparty/Tailscale/index.cgi/"