
Adds a cached self node to the web client Server struct, which will be used from the web client api to verify that request came from the node's own machine (i.e. came from the web client frontend). We'll be using when we switch the web client api over to acting as a proxy to the localapi, to protect against DNS rebinding attacks. Updates tailscale/corp#13775 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
117 lines
2.9 KiB
Go
117 lines
2.9 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package cli
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
_ "embed"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"net"
|
|
"net/http"
|
|
"net/http/cgi"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/peterbourgon/ff/v3/ffcli"
|
|
"tailscale.com/client/web"
|
|
"tailscale.com/util/cmpx"
|
|
)
|
|
|
|
var webCmd = &ffcli.Command{
|
|
Name: "web",
|
|
ShortUsage: "web [flags]",
|
|
ShortHelp: "Run a web server for controlling Tailscale",
|
|
|
|
LongHelp: strings.TrimSpace(`
|
|
"tailscale web" runs a webserver for controlling the Tailscale daemon.
|
|
|
|
It's primarily intended for use on Synology, QNAP, and other
|
|
NAS devices where a web interface is the natural place to control
|
|
Tailscale, as opposed to a CLI or a native app.
|
|
`),
|
|
|
|
FlagSet: (func() *flag.FlagSet {
|
|
webf := newFlagSet("web")
|
|
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]")
|
|
return webf
|
|
})(),
|
|
Exec: runWeb,
|
|
}
|
|
|
|
var webArgs struct {
|
|
listen string
|
|
cgi bool
|
|
dev bool
|
|
}
|
|
|
|
func tlsConfigFromEnvironment() *tls.Config {
|
|
crt := os.Getenv("TLS_CRT_PEM")
|
|
key := os.Getenv("TLS_KEY_PEM")
|
|
if crt == "" || key == "" {
|
|
return nil
|
|
}
|
|
|
|
// We support passing in the complete certificate and key from environment
|
|
// variables because pfSense stores its cert+key in the PHP config. We populate
|
|
// TLS_CRT_PEM and TLS_KEY_PEM from PHP code before starting tailscale web.
|
|
// These are the PEM-encoded Certificate and Private Key.
|
|
|
|
cert, err := tls.X509KeyPair([]byte(crt), []byte(key))
|
|
if err != nil {
|
|
log.Printf("tlsConfigFromEnvironment: %v", err)
|
|
|
|
// Fallback to unencrypted HTTP.
|
|
return nil
|
|
}
|
|
|
|
return &tls.Config{Certificates: []tls.Certificate{cert}}
|
|
}
|
|
|
|
func runWeb(ctx context.Context, args []string) error {
|
|
if len(args) > 0 {
|
|
return fmt.Errorf("too many non-flag arguments: %q", args)
|
|
}
|
|
|
|
webServer, cleanup := web.NewServer(ctx, web.ServerOpts{
|
|
DevMode: webArgs.dev,
|
|
CGIMode: webArgs.cgi,
|
|
LocalClient: &localClient,
|
|
})
|
|
defer cleanup()
|
|
|
|
if webArgs.cgi {
|
|
if err := cgi.Serve(webServer); err != nil {
|
|
log.Printf("tailscale.cgi: %v", err)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
tlsConfig := tlsConfigFromEnvironment()
|
|
if tlsConfig != nil {
|
|
server := &http.Server{
|
|
Addr: webArgs.listen,
|
|
TLSConfig: tlsConfig,
|
|
Handler: webServer,
|
|
}
|
|
|
|
log.Printf("web server running on: https://%s", server.Addr)
|
|
return server.ListenAndServeTLS("", "")
|
|
} else {
|
|
log.Printf("web server running on: %s", urlOfListenAddr(webArgs.listen))
|
|
return http.ListenAndServe(webArgs.listen, webServer)
|
|
}
|
|
}
|
|
|
|
// urlOfListenAddr parses a given listen address into a formatted URL
|
|
func urlOfListenAddr(addr string) string {
|
|
host, port, _ := net.SplitHostPort(addr)
|
|
return fmt.Sprintf("http://%s", net.JoinHostPort(cmpx.Or(host, "127.0.0.1"), port))
|
|
}
|