From a08a7030d1514a8b57f189c2c6c0e5790c59e40e Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Fri, 20 Nov 2020 16:22:32 -0600 Subject: [PATCH] TUN-3578: cloudflared tunnel route dns should allow wildcard subdomains --- cmd/cloudflared/tunnel/subcommands.go | 30 ++++++----- cmd/cloudflared/tunnel/subcommands_test.go | 63 ++++++++++++++++++++-- 2 files changed, 76 insertions(+), 17 deletions(-) diff --git a/cmd/cloudflared/tunnel/subcommands.go b/cmd/cloudflared/tunnel/subcommands.go index 6980b599..79613ab9 100644 --- a/cmd/cloudflared/tunnel/subcommands.go +++ b/cmd/cloudflared/tunnel/subcommands.go @@ -103,7 +103,7 @@ func buildCreateCommand() *cli.Command { Usage: "Create a new tunnel with given 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. + 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: @@ -329,13 +329,13 @@ func buildRunCommand() *cli.Command { Before: SetFlagsFromConfigFile, Usage: "Proxy a local web server by running the given tunnel", UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options] [TUNNEL]", - Description: `Runs the tunnel identified by name or UUUD, creating highly available connections + Description: `Runs the tunnel identified by name or UUUD, creating highly available connections between your server and the Cloudflare edge. You can provide name or UUID of tunnel to run either as the last command line argument or in the configuration file using "tunnel: TUNNEL". - This command requires the tunnel credentials file created when "cloudflared tunnel create" was run, + 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 + If you experience other problems running the tunnel, "cloudflared tunnel cleanup" may help by removing any old connection records. `, Flags: flags, @@ -431,7 +431,7 @@ func dnsRouteFromArg(c *cli.Context) (tunnelstore.Route, error) { userHostname := c.Args().Get(userHostnameIndex) if userHostname == "" { return nil, cliutil.UsageError("The third argument should be the hostname") - } else if !validateHostname(userHostname) { + } else if !validateHostname(userHostname, true) { return nil, errors.Errorf("%s is not a valid hostname", userHostname) } return tunnelstore.NewDNSRoute(userHostname), nil @@ -449,14 +449,14 @@ func lbRouteFromArg(c *cli.Context) (tunnelstore.Route, error) { lbName := c.Args().Get(lbNameIndex) if lbName == "" { return nil, cliutil.UsageError("The third argument should be the load balancer name") - } else if !validateHostname(lbName) { + } else if !validateHostname(lbName, true) { return nil, errors.Errorf("%s is not a valid load balancer name", lbName) } lbPool := c.Args().Get(lbPoolIndex) if lbPool == "" { return nil, cliutil.UsageError("The fourth argument should be the pool name") - } else if !validateName(lbPool) { + } else if !validateName(lbPool, false) { return nil, errors.Errorf("%s is not a valid pool name", lbPool) } @@ -464,19 +464,23 @@ func lbRouteFromArg(c *cli.Context) (tunnelstore.Route, error) { } var nameRegex = regexp.MustCompile("^[_a-zA-Z0-9][-_.a-zA-Z0-9]*$") +var hostNameRegex = regexp.MustCompile("^[*_a-zA-Z0-9][-_.a-zA-Z0-9]*$") -func validateName(s string) bool { +func validateName(s string, allowWildcardSubdomain bool) bool { + if allowWildcardSubdomain { + return hostNameRegex.MatchString(s) + } return nameRegex.MatchString(s) } -func validateHostname(s string) bool { +func validateHostname(s string, allowWildcardSubdomain bool) bool { // Slightly stricter than PunyCodeProfile idnaProfile := idna.New( idna.ValidateLabels(true), idna.VerifyDNSLength(true)) puny, err := idnaProfile.ToASCII(s) - return err == nil && validateName(puny) + return err == nil && validateName(puny, allowWildcardSubdomain) } func routeCommand(c *cli.Context) error { @@ -535,13 +539,13 @@ func commandHelpTemplate() string { } const template = `NAME: {{.HelpName}} - {{.Usage}} - + USAGE: {{.UsageText}} - + DESCRIPTION: {{.Description}} - + TUNNEL COMMAND OPTIONS: %s SUBCOMMAND OPTIONS: diff --git a/cmd/cloudflared/tunnel/subcommands_test.go b/cmd/cloudflared/tunnel/subcommands_test.go index fb1bbbdf..82843e01 100644 --- a/cmd/cloudflared/tunnel/subcommands_test.go +++ b/cmd/cloudflared/tunnel/subcommands_test.go @@ -6,9 +6,8 @@ import ( "testing" "github.com/cloudflare/cloudflared/tunnelstore" - "github.com/mitchellh/go-homedir" - "github.com/google/uuid" + "github.com/mitchellh/go-homedir" "github.com/stretchr/testify/assert" ) @@ -117,8 +116,64 @@ func TestValidateName(t *testing.T) { {name: "_ab_c.-d-ef", want: true}, } for _, tt := range tests { - if got := validateName(tt.name); got != tt.want { + if got := validateName(tt.name, false); got != tt.want { t.Errorf("validateName() = %v, want %v", got, tt.want) } } -} \ No newline at end of file +} + +func Test_validateHostname(t *testing.T) { + type args struct { + s string + allowWildcardSubdomain bool + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "Normal", + args: args{ + s: "example.com", + allowWildcardSubdomain: true, + }, + want: true, + }, + { + name: "wildcard subdomain for TUN-358", + args: args{ + s: "*.ehrig.io", + allowWildcardSubdomain: true, + }, + want: true, + }, + { + name: "Misplaced wildcard", + args: args{ + s: "subdomain.*.ehrig.io", + allowWildcardSubdomain: true, + }, + }, + { + name: "Invalid domain", + args: args{ + s: "..", + allowWildcardSubdomain: true, + }, + }, + { + name: "Invalid domain", + args: args{ + s: "..", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := validateHostname(tt.args.s, tt.args.allowWildcardSubdomain); got != tt.want { + t.Errorf("validateHostname() = %v, want %v", got, tt.want) + } + }) + } +}