|
|
|
@@ -7,6 +7,7 @@ import (
|
|
|
|
|
"io/ioutil"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"regexp"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
"text/tabwriter"
|
|
|
|
@@ -16,6 +17,7 @@ import (
|
|
|
|
|
"github.com/mitchellh/go-homedir"
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
|
"golang.org/x/net/idna"
|
|
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
|
|
|
|
|
|
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
|
|
|
@@ -37,18 +39,19 @@ var (
|
|
|
|
|
listNameFlag = &cli.StringFlag{
|
|
|
|
|
Name: "name",
|
|
|
|
|
Aliases: []string{"n"},
|
|
|
|
|
Usage: "List tunnels with the given name",
|
|
|
|
|
Usage: "List tunnels with the given `NAME`",
|
|
|
|
|
}
|
|
|
|
|
listExistedAtFlag = &cli.TimestampFlag{
|
|
|
|
|
Name: "when",
|
|
|
|
|
Aliases: []string{"w"},
|
|
|
|
|
Usage: fmt.Sprintf("List tunnels that are active at the given time, expect format in RFC3339 (%s)", time.Now().Format(tunnelstore.TimeLayout)),
|
|
|
|
|
Usage: "List tunnels that are active at the given `TIME` in RFC3339 format",
|
|
|
|
|
Layout: tunnelstore.TimeLayout,
|
|
|
|
|
DefaultText: fmt.Sprintf("current time, %s", time.Now().Format(tunnelstore.TimeLayout)),
|
|
|
|
|
}
|
|
|
|
|
listIDFlag = &cli.StringFlag{
|
|
|
|
|
Name: "id",
|
|
|
|
|
Aliases: []string{"i"},
|
|
|
|
|
Usage: "List tunnel by ID",
|
|
|
|
|
Usage: "List tunnel by `ID`",
|
|
|
|
|
}
|
|
|
|
|
showRecentlyDisconnected = &cli.BoolFlag{
|
|
|
|
|
Name: "show-recently-disconnected",
|
|
|
|
@@ -80,15 +83,12 @@ var (
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const hideSubcommands = true
|
|
|
|
|
|
|
|
|
|
func buildCreateCommand() *cli.Command {
|
|
|
|
|
return &cli.Command{
|
|
|
|
|
Name: "create",
|
|
|
|
|
Action: cliutil.ErrorHandler(createCommand),
|
|
|
|
|
Usage: "Create a new tunnel with given name",
|
|
|
|
|
ArgsUsage: "TUNNEL-NAME",
|
|
|
|
|
Hidden: hideSubcommands,
|
|
|
|
|
Flags: []cli.Flag{outputFormatFlag},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -153,7 +153,6 @@ func buildListCommand() *cli.Command {
|
|
|
|
|
Action: cliutil.ErrorHandler(listCommand),
|
|
|
|
|
Usage: "List existing tunnels",
|
|
|
|
|
ArgsUsage: " ",
|
|
|
|
|
Hidden: hideSubcommands,
|
|
|
|
|
Flags: []cli.Flag{outputFormatFlag, showDeletedFlag, listNameFlag, listExistedAtFlag, listIDFlag, showRecentlyDisconnected},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -209,6 +208,7 @@ func fmtAndPrintTunnelList(tunnels []*tunnelstore.Tunnel, showRecentlyDisconnect
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
writer := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags)
|
|
|
|
|
defer writer.Flush()
|
|
|
|
|
|
|
|
|
|
// Print column headers with tabbed columns
|
|
|
|
|
fmt.Fprintln(writer, "ID\tNAME\tCREATED\tCONNECTIONS\t")
|
|
|
|
@@ -224,9 +224,6 @@ func fmtAndPrintTunnelList(tunnels []*tunnelstore.Tunnel, showRecentlyDisconnect
|
|
|
|
|
)
|
|
|
|
|
fmt.Fprintln(writer, formattedStr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write data buffered in tabwriter to output
|
|
|
|
|
writer.Flush()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func fmtConnections(connections []tunnelstore.Connection, showRecentlyDisconnected bool) string {
|
|
|
|
@@ -258,9 +255,8 @@ func buildDeleteCommand() *cli.Command {
|
|
|
|
|
return &cli.Command{
|
|
|
|
|
Name: "delete",
|
|
|
|
|
Action: cliutil.ErrorHandler(deleteCommand),
|
|
|
|
|
Usage: "Delete existing tunnel with given IDs",
|
|
|
|
|
ArgsUsage: "TUNNEL-ID",
|
|
|
|
|
Hidden: hideSubcommands,
|
|
|
|
|
Usage: "Delete existing tunnel by UUID or name",
|
|
|
|
|
ArgsUsage: "TUNNEL",
|
|
|
|
|
Flags: []cli.Flag{credentialsFileFlag, forceDeleteFlag},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -301,12 +297,13 @@ func buildRunCommand() *cli.Command {
|
|
|
|
|
Name: "run",
|
|
|
|
|
Action: cliutil.ErrorHandler(runCommand),
|
|
|
|
|
Usage: "Proxy a local web server by running the given tunnel",
|
|
|
|
|
ArgsUsage: "TUNNEL-ID",
|
|
|
|
|
Description: "Runs the tunnel, creating a high-availability connection between your server and the Cloudflare " +
|
|
|
|
|
"edge. This command requires the tunnel credentials file created when `cloudflared tunnel create` was run, but " +
|
|
|
|
|
"does not require the cert.pem from `cloudflared login`. If you experience problems running the tunnel, " +
|
|
|
|
|
"`cloudflared tunnel cleanup` may help by removing any old connection records.",
|
|
|
|
|
Hidden: hideSubcommands,
|
|
|
|
|
ArgsUsage: "TUNNEL",
|
|
|
|
|
Description: `Runs the tunnel identified by name or UUUD, creating a highly available connection
|
|
|
|
|
between your server and the Cloudflare edge.
|
|
|
|
|
|
|
|
|
|
This command requires the tunnel credentials file created when "cloudflared tunnel create" was run,
|
|
|
|
|
however it does not need access to cert.pem from "cloudflared login". If you experience problems running
|
|
|
|
|
the tunnel, "cloudflared tunnel cleanup" may help by removing any old connection records.`,
|
|
|
|
|
Flags: []cli.Flag{forceFlag, credentialsFileFlag},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
@@ -332,9 +329,8 @@ func buildCleanupCommand() *cli.Command {
|
|
|
|
|
return &cli.Command{
|
|
|
|
|
Name: "cleanup",
|
|
|
|
|
Action: cliutil.ErrorHandler(cleanupCommand),
|
|
|
|
|
Usage: "Cleanup connections for the tunnel with given IDs",
|
|
|
|
|
ArgsUsage: "TUNNEL-IDS",
|
|
|
|
|
Hidden: hideSubcommands,
|
|
|
|
|
Usage: "Cleanup tunnel connections",
|
|
|
|
|
ArgsUsage: "TUNNEL",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -361,51 +357,75 @@ func buildRouteCommand() *cli.Command {
|
|
|
|
|
Name: "route",
|
|
|
|
|
Action: cliutil.ErrorHandler(routeCommand),
|
|
|
|
|
Usage: "Define what hostname or load balancer can route to this tunnel",
|
|
|
|
|
Description: `The route defines what hostname or load balancer can route to this tunnel.
|
|
|
|
|
To route a hostname: cloudflared tunnel route dns <tunnel ID> <hostname>
|
|
|
|
|
To route a load balancer: cloudflared tunnel route lb <tunnel ID> <load balancer name> <load balancer pool>
|
|
|
|
|
If you don't specify a load balancer pool, we will create a new pool called tunnel:<tunnel ID>`,
|
|
|
|
|
ArgsUsage: "dns|lb TUNNEL-ID HOSTNAME [LB-POOL]",
|
|
|
|
|
Hidden: hideSubcommands,
|
|
|
|
|
Description: `The route defines what hostname or load balancer will proxy requests to this tunnel.
|
|
|
|
|
|
|
|
|
|
To route a hostname by creating a CNAME to tunnel's address:
|
|
|
|
|
cloudflared tunnel route dns <tunnel ID> <hostname>
|
|
|
|
|
To use this tunnel as a load balancer origin, creating pool and load balancer if necessary:
|
|
|
|
|
cloudflared tunnel route lb <tunnel ID> <load balancer name> <load balancer pool>`,
|
|
|
|
|
ArgsUsage: "dns|lb TUNNEL HOSTNAME [LB-POOL]",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func dnsRouteFromArg(c *cli.Context, tunnelID uuid.UUID) (tunnelstore.Route, error) {
|
|
|
|
|
func dnsRouteFromArg(c *cli.Context) (tunnelstore.Route, error) {
|
|
|
|
|
const (
|
|
|
|
|
userHostnameIndex = 2
|
|
|
|
|
expectArgs = 3
|
|
|
|
|
expectedNArgs = 3
|
|
|
|
|
)
|
|
|
|
|
if c.NArg() != expectArgs {
|
|
|
|
|
return nil, cliutil.UsageError("Expect %d arguments, got %d", expectArgs, c.NArg())
|
|
|
|
|
if c.NArg() != expectedNArgs {
|
|
|
|
|
return nil, cliutil.UsageError("Expected %d arguments, got %d", expectedNArgs, c.NArg())
|
|
|
|
|
}
|
|
|
|
|
userHostname := c.Args().Get(userHostnameIndex)
|
|
|
|
|
if userHostname == "" {
|
|
|
|
|
return nil, cliutil.UsageError("The third argument should be the hostname")
|
|
|
|
|
} else if !validateHostname(userHostname) {
|
|
|
|
|
return nil, errors.Errorf("%s is not a valid hostname", userHostname)
|
|
|
|
|
}
|
|
|
|
|
return tunnelstore.NewDNSRoute(userHostname), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func lbRouteFromArg(c *cli.Context, tunnelID uuid.UUID) (tunnelstore.Route, error) {
|
|
|
|
|
func lbRouteFromArg(c *cli.Context) (tunnelstore.Route, error) {
|
|
|
|
|
const (
|
|
|
|
|
lbNameIndex = 2
|
|
|
|
|
lbPoolIndex = 3
|
|
|
|
|
expectMinArgs = 3
|
|
|
|
|
expectedNArgs = 4
|
|
|
|
|
)
|
|
|
|
|
if c.NArg() < expectMinArgs {
|
|
|
|
|
return nil, cliutil.UsageError("Expect at least %d arguments, got %d", expectMinArgs, c.NArg())
|
|
|
|
|
if c.NArg() != expectedNArgs {
|
|
|
|
|
return nil, cliutil.UsageError("Expected %d arguments, got %d", expectedNArgs, c.NArg())
|
|
|
|
|
}
|
|
|
|
|
lbName := c.Args().Get(lbNameIndex)
|
|
|
|
|
if lbName == "" {
|
|
|
|
|
return nil, cliutil.UsageError("The third argument should be the load balancer name")
|
|
|
|
|
} else if !validateHostname(lbName) {
|
|
|
|
|
return nil, errors.Errorf("%s is not a valid load balancer name", lbName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lbPool := c.Args().Get(lbPoolIndex)
|
|
|
|
|
if lbPool == "" {
|
|
|
|
|
lbPool = defaultPoolName(tunnelID)
|
|
|
|
|
return nil, cliutil.UsageError("The fourth argument should be the pool name")
|
|
|
|
|
} else if !validateName(lbPool) {
|
|
|
|
|
return nil, errors.Errorf("%s is not a valid pool name", lbPool)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return tunnelstore.NewLBRoute(lbName, lbPool), nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var nameRegex = regexp.MustCompile("^[_a-zA-Z0-9][-_.a-zA-Z0-9]*$")
|
|
|
|
|
|
|
|
|
|
func validateName(s string) bool {
|
|
|
|
|
return nameRegex.MatchString(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func validateHostname(s string) bool {
|
|
|
|
|
// Slightly stricter than PunyCodeProfile
|
|
|
|
|
idnaProfile := idna.New(
|
|
|
|
|
idna.ValidateLabels(true),
|
|
|
|
|
idna.VerifyDNSLength(true))
|
|
|
|
|
|
|
|
|
|
puny, err := idnaProfile.ToASCII(s)
|
|
|
|
|
return err == nil && validateName(puny)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func routeCommand(c *cli.Context) error {
|
|
|
|
|
if c.NArg() < 2 {
|
|
|
|
|
return cliutil.UsageError(`"cloudflared tunnel route" requires the first argument to be the route type(dns or lb), followed by the ID or name of the tunnel`)
|
|
|
|
@@ -426,7 +446,7 @@ func routeCommand(c *cli.Context) error {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
r, err = dnsRouteFromArg(c, tunnelID)
|
|
|
|
|
r, err = dnsRouteFromArg(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@@ -435,7 +455,7 @@ func routeCommand(c *cli.Context) error {
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
r, err = lbRouteFromArg(c, tunnelID)
|
|
|
|
|
r, err = lbRouteFromArg(c)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
@@ -450,6 +470,3 @@ func routeCommand(c *cli.Context) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func defaultPoolName(tunnelID uuid.UUID) string {
|
|
|
|
|
return fmt.Sprintf("tunnel:%v", tunnelID)
|
|
|
|
|
}
|
|
|
|
|