cmd/tailscale/cli: [funnel] add https:<port> ... ability
Adds all-in-one ability to start a serve + Funnel with one command. Fixes #7844 Signed-off-by: Shayne Sweeney <shayne@tailscale.com>shayne/funnel_cmd
parent
cef0a474f8
commit
99b821c04c
|
@ -33,14 +33,32 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
|
||||||
ShortUsage: strings.TrimSpace(`
|
ShortUsage: strings.TrimSpace(`
|
||||||
funnel <serve-port> {on|off}
|
funnel <serve-port> {on|off}
|
||||||
funnel status [--json]
|
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,
|
Exec: e.runFunnel,
|
||||||
UsageFunc: usageFunc,
|
UsageFunc: usageFunc,
|
||||||
Subcommands: []*ffcli.Command{
|
Subcommands: []*ffcli.Command{
|
||||||
|
@ -58,10 +76,50 @@ funnel <serve-port> {on|off}
|
||||||
}
|
}
|
||||||
|
|
||||||
// runFunnel is the entry point for the "tailscale funnel" subcommand and
|
// 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)
|
// Note: funnel is only supported on single DNS name for now. (2022-11-15)
|
||||||
func (e *serveEnv) runFunnel(ctx context.Context, args []string) error {
|
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 {
|
if len(args) != 2 {
|
||||||
return flag.ErrHelp
|
return flag.ErrHelp
|
||||||
}
|
}
|
||||||
|
|
|
@ -543,6 +543,61 @@ func TestServeConfigMutations(t *testing.T) {
|
||||||
want: &ipn.ServeConfig{},
|
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
|
// tricky steps
|
||||||
add(step{reset: true})
|
add(step{reset: true})
|
||||||
add(step{ // a directory with a trailing slash mount point
|
add(step{ // a directory with a trailing slash mount point
|
||||||
|
@ -652,9 +707,14 @@ func TestServeConfigMutations(t *testing.T) {
|
||||||
var args []string
|
var args []string
|
||||||
if st.command[0] == "funnel" {
|
if st.command[0] == "funnel" {
|
||||||
cmd = newFunnelCommand(e)
|
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:]
|
args = st.command[1:]
|
||||||
} else {
|
} else {
|
||||||
cmd = newServeCommand(e)
|
cmd = newServeCommand(e)
|
||||||
|
// Reset the serveCmd variable from possible funnel command run.
|
||||||
|
serveCmd = cmd
|
||||||
args = st.command
|
args = st.command
|
||||||
}
|
}
|
||||||
err := cmd.ParseAndRun(context.Background(), args)
|
err := cmd.ParseAndRun(context.Background(), args)
|
||||||
|
|
Loading…
Reference in New Issue