diff --git a/cmd/cloudflared/config/configuration.go b/cmd/cloudflared/config/configuration.go index 20de4eb8..a442e2e4 100644 --- a/cmd/cloudflared/config/configuration.go +++ b/cmd/cloudflared/config/configuration.go @@ -9,7 +9,6 @@ import ( homedir "github.com/mitchellh/go-homedir" "github.com/urfave/cli/v2" - "github.com/urfave/cli/v2/altsrc" "gopkg.in/yaml.v2" "github.com/cloudflare/cloudflared/validation" @@ -91,14 +90,6 @@ func FileExists(path string) (bool, error) { return true, nil } -// FindInputSourceContext pulls the input source from the config flag. -func FindInputSourceContext(context *cli.Context) (altsrc.InputSourceContext, error) { - if context.String("config") != "" { - return altsrc.NewYamlSourceFromFile(context.String("config")) - } - return nil, nil -} - // FindDefaultConfigPath returns the first path that contains a config file. // If none of the combination of DefaultConfigSearchDirectories() and DefaultConfigFiles // contains a config file, return empty string. diff --git a/cmd/cloudflared/main.go b/cmd/cloudflared/main.go index f352d0f5..833b3090 100644 --- a/cmd/cloudflared/main.go +++ b/cmd/cloudflared/main.go @@ -13,6 +13,7 @@ import ( log "github.com/cloudflare/cloudflared/logger" "github.com/cloudflare/cloudflared/metrics" "github.com/cloudflare/cloudflared/overwatch" + "github.com/cloudflare/cloudflared/tunneldns" "github.com/cloudflare/cloudflared/watcher" raven "github.com/getsentry/raven-go" @@ -76,7 +77,7 @@ func main() { and configure access control.` app.Flags = flags() app.Action = action(Version, shutdownC, graceShutdownC) - app.Before = tunnel.Before + app.Before = tunnel.SetFlagsFromConfigFile app.Commands = commands(cli.ShowVersion) tunnel.Init(Version, shutdownC, graceShutdownC) // we need this to support the tunnel sub command... @@ -129,6 +130,7 @@ To determine if an update happened in a script, check for error code 64.`, }, } cmds = append(cmds, tunnel.Commands()...) + cmds = append(cmds, tunneldns.Command(false)) cmds = append(cmds, access.Commands()...) return cmds } diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index 229248ad..5c6a6e28 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -113,87 +113,33 @@ func Flags() []cli.Flag { } func Commands() []*cli.Command { - cmds := []*cli.Command{ - { - Name: "login", - Action: cliutil.ErrorHandler(login), - Usage: "Generate a configuration file with your login details", - ArgsUsage: " ", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "url", - Hidden: true, - }, - }, - Hidden: true, - }, - { - Name: "proxy-dns", - Action: cliutil.ErrorHandler(tunneldns.Run), - Usage: "Run a DNS over HTTPS proxy server.", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "metrics", - Value: "localhost:", - Usage: "Listen address for metrics reporting.", - EnvVars: []string{"TUNNEL_METRICS"}, - }, - &cli.StringFlag{ - Name: "address", - Usage: "Listen address for the DNS over HTTPS proxy server.", - Value: "localhost", - EnvVars: []string{"TUNNEL_DNS_ADDRESS"}, - }, - &cli.IntFlag{ - Name: "port", - Usage: "Listen on given port for the DNS over HTTPS proxy server.", - Value: 53, - EnvVars: []string{"TUNNEL_DNS_PORT"}, - }, - &cli.StringSliceFlag{ - Name: "upstream", - Usage: "Upstream endpoint URL, you can specify multiple endpoints for redundancy.", - Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"), - EnvVars: []string{"TUNNEL_DNS_UPSTREAM"}, - }, - &cli.StringSliceFlag{ - Name: "bootstrap", - Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.", - Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"), - EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"}, - }, - }, - ArgsUsage: " ", // can't be the empty string or we get the default output - Hidden: false, - }, + subcommands := []*cli.Command{ + buildLoginSubcommand(false), + buildCreateCommand(), + buildRouteCommand(), + buildRunCommand(), + buildListCommand(), + buildIngressSubcommand(), + buildDeleteCommand(), + buildCleanupCommand(), + // for compatibility, allow following as tunnel subcommands + tunneldns.Command(true), dbConnectCmd(), } - var subcommands []*cli.Command - for _, cmd := range cmds { - c := *cmd - c.Hidden = false - subcommands = append(subcommands, &c) + return []*cli.Command { + buildTunnelCommand(subcommands), + // for compatibility, allow following as top-level subcommands + buildLoginSubcommand(true), + dbConnectCmd(), } - - subcommands = append(subcommands, buildCreateCommand()) - subcommands = append(subcommands, buildListCommand()) - subcommands = append(subcommands, buildDeleteCommand()) - subcommands = append(subcommands, buildRunCommand()) - subcommands = append(subcommands, buildCleanupCommand()) - subcommands = append(subcommands, buildRouteCommand()) - subcommands = append(subcommands, buildIngressSubcommand()) - - cmds = append(cmds, buildTunnelCommand(subcommands)) - - return cmds } func buildTunnelCommand(subcommands []*cli.Command) *cli.Command { return &cli.Command{ Name: "tunnel", Action: cliutil.ErrorHandler(TunnelCommand), - Before: Before, + Before: SetFlagsFromConfigFile, Category: "Tunnel", Usage: "Make a locally-running web service accessible over the internet using Argo Tunnel.", ArgsUsage: " ", @@ -245,8 +191,7 @@ func buildIngressSubcommand() *cli.Command { rule that matches all traffic. You can validate these rules with the 'ingress validate' command, and test which rule matches a particular URL with 'ingress rule '. - Multiple-origin routing is incompatible with the --url flag. - `, + Multiple-origin routing is incompatible with the --url flag.`, Subcommands: []*cli.Command{buildValidateCommand(), buildRuleCommand()}, Flags: tunnelFlags(false), } @@ -624,26 +569,34 @@ func forceSetFlag(c *cli.Context, name, value string) { } } -func Before(c *cli.Context) error { +func SetFlagsFromConfigFile(c *cli.Context) error { logger, err := createLogger(c, false, false) if err != nil { return cliutil.PrintLoggerSetupError("error setting up logger", err) } - if c.String("config") == "" { + configFile := c.String("config") + if configFile == "" { logger.Debugf(config.ErrNoConfigFile.Error()) + return nil } - inputSource, err := config.FindInputSourceContext(c) + + inputSource, err := altsrc.NewYamlSourceFromFile(configFile) if err != nil { - logger.Errorf("Cannot load configuration from %s: %s", c.String("config"), err) + logger.Errorf("Cannot load configuration from %s: %s", configFile, err) return err - } else if inputSource != nil { - err := altsrc.ApplyInputSourceValues(c, inputSource, c.App.Flags) + } + if inputSource != nil { + targetFlags := c.Command.Flags + if c.Command.Name == "" { + targetFlags = c.App.Flags + } + err := altsrc.ApplyInputSourceValues(c, inputSource, targetFlags) if err != nil { - logger.Errorf("Cannot apply configuration from %s: %s", c.String("config"), err) + logger.Errorf("Cannot apply configuration from %s: %s", configFile, err) return err } - logger.Debugf("Applied configuration from %s", c.String("config")) + logger.Debugf("Applied configuration from %s", configFile) } return nil } @@ -735,7 +688,7 @@ func dbConnectCmd() *cli.Command { // Override before to run tunnel validation before dbconnect validation. cmd.Before = func(c *cli.Context) error { - err := Before(c) + err := SetFlagsFromConfigFile(c) if err == nil { err = dbconnect.CmdBefore(c) } diff --git a/cmd/cloudflared/tunnel/login.go b/cmd/cloudflared/tunnel/login.go index e50d8764..48cba5f3 100644 --- a/cmd/cloudflared/tunnel/login.go +++ b/cmd/cloudflared/tunnel/login.go @@ -7,12 +7,14 @@ import ( "path/filepath" "syscall" - "github.com/cloudflare/cloudflared/cmd/cloudflared/config" - "github.com/cloudflare/cloudflared/cmd/cloudflared/transfer" - "github.com/cloudflare/cloudflared/logger" homedir "github.com/mitchellh/go-homedir" "github.com/pkg/errors" cli "github.com/urfave/cli/v2" + + "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" + "github.com/cloudflare/cloudflared/cmd/cloudflared/config" + "github.com/cloudflare/cloudflared/cmd/cloudflared/transfer" + "github.com/cloudflare/cloudflared/logger" ) const ( @@ -20,6 +22,22 @@ const ( callbackStoreURL = "https://login.argotunnel.com/" ) +func buildLoginSubcommand(hidden bool) *cli.Command { + return &cli.Command{ + Name: "login", + Action: cliutil.ErrorHandler(login), + Usage: "Generate a configuration file with your login details", + ArgsUsage: " ", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "url", + Hidden: true, + }, + }, + Hidden: hidden, + } +} + func login(c *cli.Context) error { logger, err := logger.New() if err != nil { diff --git a/cmd/cloudflared/tunnel/subcommands.go b/cmd/cloudflared/tunnel/subcommands.go index 581e29b2..a3162f85 100644 --- a/cmd/cloudflared/tunnel/subcommands.go +++ b/cmd/cloudflared/tunnel/subcommands.go @@ -17,6 +17,7 @@ import ( "github.com/mitchellh/go-homedir" "github.com/pkg/errors" "github.com/urfave/cli/v2" + "github.com/urfave/cli/v2/altsrc" "golang.org/x/net/idna" "gopkg.in/yaml.v2" @@ -58,24 +59,24 @@ var ( Aliases: []string{"rd"}, Usage: "Include connections that have recently disconnected in the list", } - outputFormatFlag = &cli.StringFlag{ + outputFormatFlag = altsrc.NewStringFlag(&cli.StringFlag{ Name: "output", Aliases: []string{"o"}, Usage: "Render output using given `FORMAT`. Valid options are 'json' or 'yaml'", - } - forceFlag = &cli.BoolFlag{ + }) + forceFlag = altsrc.NewBoolFlag(&cli.BoolFlag{ Name: "force", Aliases: []string{"f"}, Usage: "By default, if a tunnel is currently being run from a cloudflared, you can't " + "simultaneously rerun it again from a second cloudflared. The --force flag lets you " + "overwrite the previous tunnel. If you want to use a single hostname with multiple " + "tunnels, you can do so with Cloudflare's Load Balancer product.", - } - credentialsFileFlag = &cli.StringFlag{ + }) + credentialsFileFlag = altsrc.NewStringFlag(&cli.StringFlag{ Name: "credentials-file", Aliases: []string{credFileFlagAlias}, Usage: "File path of tunnel credentials", - } + }) forceDeleteFlag = &cli.BoolFlag{ Name: "force", Aliases: []string{"f"}, @@ -88,9 +89,13 @@ func buildCreateCommand() *cli.Command { Name: "create", Action: cliutil.ErrorHandler(createCommand), Usage: "Create a new tunnel with given name", - UsageText: "cloudflared tunnel [tunnel command options] create [create command options]", - Description: "cloudflared tunnel create example will create a tunnel named example, and generate a credential file to run the tunnel", - ArgsUsage: "TUNNEL-NAME", + UsageText: "cloudflared tunnel [tunnel command options] create [subcommand options] NAME", + Description: `Creates a tunnel, registers it with Cloudflare edge and generates credential file used to run this tunnel. + Use "cloudflared tunnel route" subcommand to map a DNS name to this tunnel and "cloudflared tunnel run" to start the connection. + + For example, to create a tunnel named 'my-tunnel' run: + + $ cloudflared tunnel create my-tunnel`, Flags: []cli.Flag{outputFormatFlag}, CustomHelpTemplate: commandHelpTemplate(), } @@ -156,8 +161,7 @@ func buildListCommand() *cli.Command { Action: cliutil.ErrorHandler(listCommand), Usage: "List existing tunnels", UsageText: "cloudflared tunnel [tunnel command options] list [subcommand options]", - Description: "cloudflared tunnel list will return all active tunnels, their created time and associated connections. Use -d flag to include deleted tunnels. See the list of options to filter the list", - ArgsUsage: " ", + Description: "cloudflared tunnel list will display all active tunnels, their created time and associated connections. Use -d flag to include deleted tunnels. See the list of options to filter the list", Flags: []cli.Flag{outputFormatFlag, showDeletedFlag, listNameFlag, listExistedAtFlag, listIDFlag, showRecentlyDisconnected}, CustomHelpTemplate: commandHelpTemplate(), } @@ -262,9 +266,8 @@ func buildDeleteCommand() *cli.Command { Name: "delete", Action: cliutil.ErrorHandler(deleteCommand), Usage: "Delete existing tunnel by UUID or name", - UsageText: "cloudflared tunnel [tunnel command options] delete [subcommand options]", + UsageText: "cloudflared tunnel [tunnel command options] delete [subcommand options] TUNNEL", Description: "cloudflared tunnel delete will delete tunnels with the given tunnel UUIDs or names. A tunnel cannot be deleted if it has active connections. To delete the tunnel unconditionally, use -f flag.", - ArgsUsage: "TUNNEL", Flags: []cli.Flag{credentialsFileFlag, forceDeleteFlag}, CustomHelpTemplate: commandHelpTemplate(), } @@ -310,15 +313,16 @@ func buildRunCommand() *cli.Command { return &cli.Command{ Name: "run", Action: cliutil.ErrorHandler(runCommand), + Before: SetFlagsFromConfigFile, Usage: "Proxy a local web server by running the given tunnel", - UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options]", - ArgsUsage: "TUNNEL", + UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options] TUNNEL", Description: `Runs the tunnel identified by name or UUUD, creating highly available connections - between your server and the Cloudflare edge. + 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 identify the tunnel by UUID. - If you experience other problems running gitthe tunnel, "cloudflared tunnel cleanup" may help by removing any old connection records. + 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 identify the tunnel by UUID. + If you experience other problems running the tunnel, "cloudflared tunnel cleanup" may help by removing + any old connection records. `, Flags: flags, CustomHelpTemplate: commandHelpTemplate(), @@ -347,9 +351,8 @@ func buildCleanupCommand() *cli.Command { Name: "cleanup", Action: cliutil.ErrorHandler(cleanupCommand), Usage: "Cleanup tunnel connections", - UsageText: "cloudflared tunnel [tunnel command options] cleanup [subcommand options]", + UsageText: "cloudflared tunnel [tunnel command options] cleanup [subcommand options] TUNNEL", Description: "Delete connections for tunnels with the given UUIDs or names.", - ArgsUsage: "TUNNEL", CustomHelpTemplate: commandHelpTemplate(), } } @@ -377,14 +380,13 @@ func buildRouteCommand() *cli.Command { Name: "route", Action: cliutil.ErrorHandler(routeCommand), Usage: "Define what hostname or load balancer can route to this tunnel", - UsageText: "cloudflared tunnel [tunnel command options] route [subcommand options]", + UsageText: "cloudflared tunnel [tunnel command options] route [subcommand options] dns|lb TUNNEL HOSTNAME [LB-POOL]", 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 To use this tunnel as a load balancer origin, creating pool and load balancer if necessary: cloudflared tunnel route lb `, - ArgsUsage: "dns|lb TUNNEL HOSTNAME [LB-POOL]", CustomHelpTemplate: commandHelpTemplate(), } } @@ -513,8 +515,7 @@ DESCRIPTION: TUNNEL COMMAND OPTIONS: %s - -SUBCOMMAND COMMAND OPTIONS: +SUBCOMMAND OPTIONS: {{range .VisibleFlags}}{{.}} {{end}} ` diff --git a/tunneldns/tunnel.go b/tunneldns/tunnel.go index e8311f31..d0524f6d 100644 --- a/tunneldns/tunnel.go +++ b/tunneldns/tunnel.go @@ -27,6 +27,48 @@ type Listener struct { logger logger.Service } +func Command(hidden bool) *cli.Command { + return &cli.Command{ + Name: "proxy-dns", + Action: cliutil.ErrorHandler(Run), + Usage: "Run a DNS over HTTPS proxy server.", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "metrics", + Value: "localhost:", + Usage: "Listen address for metrics reporting.", + EnvVars: []string{"TUNNEL_METRICS"}, + }, + &cli.StringFlag{ + Name: "address", + Usage: "Listen address for the DNS over HTTPS proxy server.", + Value: "localhost", + EnvVars: []string{"TUNNEL_DNS_ADDRESS"}, + }, + &cli.IntFlag{ + Name: "port", + Usage: "Listen on given port for the DNS over HTTPS proxy server.", + Value: 53, + EnvVars: []string{"TUNNEL_DNS_PORT"}, + }, + &cli.StringSliceFlag{ + Name: "upstream", + Usage: "Upstream endpoint URL, you can specify multiple endpoints for redundancy.", + Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"), + EnvVars: []string{"TUNNEL_DNS_UPSTREAM"}, + }, + &cli.StringSliceFlag{ + Name: "bootstrap", + Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.", + Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"), + EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"}, + }, + }, + ArgsUsage: " ", // can't be the empty string or we get the default output + Hidden: hidden, + } +} + // Run implements a foreground runner func Run(c *cli.Context) error { logDirectory, logLevel := config.FindLogSettings()