From 86a7af3dc421745c1666093fe6b876a1dd97dced Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Thu, 8 Oct 2020 13:00:32 -0500 Subject: [PATCH] TUN-3451: Cloudflared tunnel ingress command --- cmd/cloudflared/tunnel/cmd.go | 35 ++++++++++++++++++++++++-- cmd/cloudflared/tunnel/ingress.go | 28 ++++++++++----------- cmd/cloudflared/tunnel/ingress_test.go | 8 ++++++ 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index 74f648b0..229248ad 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -182,8 +182,7 @@ func Commands() []*cli.Command { subcommands = append(subcommands, buildRunCommand()) subcommands = append(subcommands, buildCleanupCommand()) subcommands = append(subcommands, buildRouteCommand()) - subcommands = append(subcommands, buildValidateCommand()) - subcommands = append(subcommands, buildRuleCommand()) + subcommands = append(subcommands, buildIngressSubcommand()) cmds = append(cmds, buildTunnelCommand(subcommands)) @@ -221,6 +220,38 @@ func buildTunnelCommand(subcommands []*cli.Command) *cli.Command { } } +func buildIngressSubcommand() *cli.Command { + return &cli.Command{ + Name: "ingress", + Category: "Tunnel", + Usage: "Validate and test cloudflared tunnel's ingress configuration", + Hidden: true, + Description: ` + Cloudflared lets you route traffic from the internet to multiple different addresses on your + origin. Multiple-origin routing is configured by a set of rules. Each rule matches traffic + by its hostname or path, and routes it to an address. These rules are configured under the + 'ingress' key of your config.yaml, for example: + + ingress: + - hostname: www.example.com + service: https://localhost:8000 + - hostname: *.example.xyz + path: /[a-zA-Z]+.html + service: https://localhost:8001 + - hostname: * + service: https://localhost:8002 + + To ensure cloudflared can route all incoming requests, the last rule must be a catch-all + 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. + `, + Subcommands: []*cli.Command{buildValidateCommand(), buildRuleCommand()}, + Flags: tunnelFlags(false), + } +} + func TunnelCommand(c *cli.Context) error { if name := c.String("name"); name != "" { // Start a named tunnel return adhocNamedTunnel(c, name) diff --git a/cmd/cloudflared/tunnel/ingress.go b/cmd/cloudflared/tunnel/ingress.go index ae5eb201..1c202433 100644 --- a/cmd/cloudflared/tunnel/ingress.go +++ b/cmd/cloudflared/tunnel/ingress.go @@ -9,7 +9,6 @@ import ( "github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil" "github.com/cloudflare/cloudflared/cmd/cloudflared/config" - "github.com/cloudflare/cloudflared/logger" "github.com/pkg/errors" "github.com/urfave/cli/v2" @@ -91,6 +90,9 @@ func (ing ingress) validate() ([]rule, error) { if err != nil { return nil, err } + if service.Scheme == "" || service.Hostname() == "" { + return nil, fmt.Errorf("The service %s must have a scheme and a hostname", r.Service) + } // Ensure that there are no wildcards anywhere except the first character // of the hostname. @@ -148,32 +150,28 @@ func parseIngress(rawYAML []byte) ([]rule, error) { return ing.validate() } -func ingressContext(c *cli.Context) ([]rule, *logger.OutputWriter, error) { - log, err := createLogger(c, false, false) - if err != nil { - return nil, nil, err - } +func ingressContext(c *cli.Context) ([]rule, error) { configFilePath := c.String("config") if configFilePath == "" { - return nil, nil, config.ErrNoConfigFile + return nil, config.ErrNoConfigFile } - log.Infof("Validating %s", configFilePath) + fmt.Printf("Reading from config file %s\n", configFilePath) configBytes, err := ioutil.ReadFile(configFilePath) if err != nil { - return nil, nil, err + return nil, err } rules, err := parseIngress(configBytes) - return rules, log, err + return rules, err } // Validates the ingress rules in the cloudflared config file func validateCommand(c *cli.Context) error { - _, log, err := ingressContext(c) + _, err := ingressContext(c) if err != nil { - log.Error(err.Error()) + fmt.Println(err.Error()) return errors.New("Validation failed") } - log.Infof("OK") + fmt.Println("OK") return nil } @@ -189,7 +187,7 @@ func buildValidateCommand() *cli.Command { // Checks which ingress rule matches the given URL. func ruleCommand(c *cli.Context) error { - rules, log, err := ingressContext(c) + rules, err := ingressContext(c) if err != nil { return err } @@ -206,7 +204,7 @@ func ruleCommand(c *cli.Context) error { } for i, r := range rules { if r.matches(requestURL) { - log.Infof("Matched rule #%d", i+1) + fmt.Printf("Matched rule #%d\n", i+1) fmt.Println(r.String()) return nil } diff --git a/cmd/cloudflared/tunnel/ingress_test.go b/cmd/cloudflared/tunnel/ingress_test.go index 6538e2b2..b8cd96ca 100644 --- a/cmd/cloudflared/tunnel/ingress_test.go +++ b/cmd/cloudflared/tunnel/ingress_test.go @@ -127,6 +127,14 @@ ingress: service: https://localhost:8000 path: "*/subpath2" - service: https://localhost:8001 +`}, + wantErr: true, + }, + { + name: "Service must have a scheme", + args: args{rawYAML: ` +ingress: + - service: localhost:8000 `}, wantErr: true, },