diff options
| author | Shayne Sweeney <shayne@tailscale.com> | 2023-04-10 22:57:51 -0400 |
|---|---|---|
| committer | Shayne Sweeney <shayne@tailscale.com> | 2023-04-10 23:17:19 -0400 |
| commit | 99b821c04cc43b31036f085dba1e5a9e2bae782e (patch) | |
| tree | 381423d34c941ad9a120e22440f5806d783bf7fb | |
| parent | cef0a474f8fb1a09e4ce3191ff000fcc21858284 (diff) | |
| download | tailscale-shayne/funnel_cmd.tar.xz tailscale-shayne/funnel_cmd.zip | |
cmd/tailscale/cli: [funnel] add https:<port> ... abilityshayne/funnel_cmd
Adds all-in-one ability to start a serve + Funnel with one command.
Fixes #7844
Signed-off-by: Shayne Sweeney <shayne@tailscale.com>
| -rw-r--r-- | cmd/tailscale/cli/funnel.go | 74 | ||||
| -rw-r--r-- | cmd/tailscale/cli/serve_test.go | 60 |
2 files changed, 126 insertions, 8 deletions
diff --git a/cmd/tailscale/cli/funnel.go b/cmd/tailscale/cli/funnel.go index 8e3e80004..74b337455 100644 --- a/cmd/tailscale/cli/funnel.go +++ b/cmd/tailscale/cli/funnel.go @@ -33,14 +33,32 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command { ShortUsage: strings.TrimSpace(` funnel <serve-port> {on|off} funnel status [--json] + funnel https:<port> <mount-point> <source> [off] +`), + LongHelp: strings.TrimSpace(` +*** BETA; all of this is subject to change *** + +Funnel allows you to publish a Tailscale Serve +server publicly, open to the entire internet. + +EXAMPLES + - To toggle Funnel on HTTPS port 443 (default): + $ tailscale funnel 443 on + $ tailscale funnel 443 off + + Turning off Funnel only turns off serving to the internet. + It does not affect serving to your tailnet. + + - To proxy requests to a web server at 127.0.0.1:3000: + $ tailscale funnel https:443 / http://127.0.0.1:3000 + + Or, using the default port: + $ tailscale funnel https / http://127.0.0.1:3000 + + - To serve a single file or a directory of files: + $ tailscale funnel https / /home/alice/blog/index.html + $ tailscale funnel https /images/ /home/alice/blog/images `), - LongHelp: strings.Join([]string{ - "Funnel allows you to publish a 'tailscale serve'", - "server publicly, open to the entire internet.", - "", - "Turning off Funnel only turns off serving to the internet.", - "It does not affect serving to your tailnet.", - }, "\n"), Exec: e.runFunnel, UsageFunc: usageFunc, Subcommands: []*ffcli.Command{ @@ -58,10 +76,50 @@ funnel <serve-port> {on|off} } // runFunnel is the entry point for the "tailscale funnel" subcommand and -// manages turning on/off funnel. Funnel is off by default. +// handles the following cases: +// +// 1. `tailscale funnel status` +// - Prints the current status of the Funnel service. +// +// 2. `tailscale funnel <serve-port> {on|off}` +// - Turns the Funnel service on or off. +// +// 3. `tailsclae funnel https(:<serve-port>) <mount-point> <source>` +// - Starts a serve command and turns the Funnel service on. // // Note: funnel is only supported on single DNS name for now. (2022-11-15) func (e *serveEnv) runFunnel(ctx context.Context, args []string) error { + if len(args) == 2 { + switch args[1] { + case "on", "off": + return e.doToggleFunnel(ctx, args) + default: + return flag.ErrHelp + } + } + + if len(args) > 2 { + if err := serveCmd.Exec(ctx, args); err != nil { + return err + } + _, portStr, _ := strings.Cut(args[0], ":") + if portStr == "" { + portStr = "443" + } + onOrOff := args[len(args)-1] + if onOrOff != "off" { + onOrOff = "on" + } + + return e.doToggleFunnel(ctx, []string{portStr, onOrOff}) + } + + return flag.ErrHelp +} + +// doToggleFunnel is the handler for "funnel <serve-port> {on|off}". It sets the +// Funnel service to on or off for the given port. +func (e *serveEnv) doToggleFunnel(ctx context.Context, args []string) error { if len(args) != 2 { return flag.ErrHelp } diff --git a/cmd/tailscale/cli/serve_test.go b/cmd/tailscale/cli/serve_test.go index 8031b2b02..e111e3a1a 100644 --- a/cmd/tailscale/cli/serve_test.go +++ b/cmd/tailscale/cli/serve_test.go @@ -543,6 +543,61 @@ func TestServeConfigMutations(t *testing.T) { want: &ipn.ServeConfig{}, }) + // Funnel HTTP tests + add(step{reset: true}) + add(step{ + command: cmd("funnel https / http://127.0.0.1:3000"), + want: &ipn.ServeConfig{ + AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, + TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ + "/": {Proxy: "http://127.0.0.1:3000"}, + }}, + }, + }, + }) + add(step{ + command: cmd("funnel https / http://127.0.0.1:3000 off"), + want: &ipn.ServeConfig{}, + }) + add(step{ + command: cmd("funnel https:443 / http://127.0.0.1:3000"), + want: &ipn.ServeConfig{ + AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:443": true}, + TCP: map[uint16]*ipn.TCPPortHandler{443: {HTTPS: true}}, + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:443": {Handlers: map[string]*ipn.HTTPHandler{ + "/": {Proxy: "http://127.0.0.1:3000"}, + }}, + }, + }, + }) + add(step{ + command: cmd("funnel https:443 / http://127.0.0.1:3000 off"), + want: &ipn.ServeConfig{}, + }) + add(step{ + command: cmd("funnel https:8443 / http://127.0.0.1:3000"), + want: &ipn.ServeConfig{ + AllowFunnel: map[ipn.HostPort]bool{"foo.test.ts.net:8443": true}, + TCP: map[uint16]*ipn.TCPPortHandler{8443: {HTTPS: true}}, + Web: map[ipn.HostPort]*ipn.WebServerConfig{ + "foo.test.ts.net:8443": {Handlers: map[string]*ipn.HTTPHandler{ + "/": {Proxy: "http://127.0.0.1:3000"}, + }}, + }, + }, + }) + add(step{ + command: cmd("funnel https:8443 / http://127.0.0.1:3000 off"), + want: &ipn.ServeConfig{}, + }) + add(step{ // invalid port + command: cmd("funnel https:8080 / http://127.0.0.1:3000"), + wantErr: anyErr(), + }) + // tricky steps add(step{reset: true}) add(step{ // a directory with a trailing slash mount point @@ -652,9 +707,14 @@ func TestServeConfigMutations(t *testing.T) { var args []string if st.command[0] == "funnel" { cmd = newFunnelCommand(e) + // The funnel command can call serve command, so we need to set the + // serveCmd variable. + serveCmd = newServeCommand(e) args = st.command[1:] } else { cmd = newServeCommand(e) + // Reset the serveCmd variable from possible funnel command run. + serveCmd = cmd args = st.command } err := cmd.ParseAndRun(context.Background(), args) |
