mirror of
https://github.com/tailscale/tailscale.git
synced 2024-12-04 23:45:34 +00:00
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>
This commit is contained in:
parent
cef0a474f8
commit
99b821c04c
@ -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 @@ func newFunnelCommand(e *serveEnv) *ffcli.Command {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -543,6 +543,61 @@ type step struct {
|
||||
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 @@ type step struct {
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user