diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index 5d3f27e5..88b79ffe 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -181,7 +181,8 @@ func Init(ver string, gracefulShutdown chan struct{}) { func runAdhocNamedTunnel(sc *subcommandContext, name, credentialsOutputPath string) error { tunnel, ok, err := sc.tunnelActive(name) if err != nil || !ok { - tunnel, err = sc.create(name, credentialsOutputPath) + // pass empty string as secret to generate one + tunnel, err = sc.create(name, credentialsOutputPath, "") if err != nil { return errors.Wrap(err, "failed to create tunnel") } diff --git a/cmd/cloudflared/tunnel/subcommand_context.go b/cmd/cloudflared/tunnel/subcommand_context.go index 0335bd27..41f0a358 100644 --- a/cmd/cloudflared/tunnel/subcommand_context.go +++ b/cmd/cloudflared/tunnel/subcommand_context.go @@ -1,6 +1,7 @@ package tunnel import ( + "encoding/base64" "encoding/json" "fmt" "os" @@ -148,15 +149,27 @@ func (sc *subcommandContext) readTunnelCredentials(credFinder CredFinder) (conne return credentials, nil } -func (sc *subcommandContext) create(name string, credentialsFilePath string) (*tunnelstore.Tunnel, error) { +func (sc *subcommandContext) create(name string, credentialsFilePath string, secret string) (*tunnelstore.Tunnel, error) { client, err := sc.client() if err != nil { return nil, errors.Wrap(err, "couldn't create client to talk to Cloudflare Tunnel backend") } - tunnelSecret, err := generateTunnelSecret() - if err != nil { - return nil, errors.Wrap(err, "couldn't generate the secret for your new tunnel") + var tunnelSecret []byte + if secret == "" { + tunnelSecret, err = generateTunnelSecret() + if err != nil { + return nil, errors.Wrap(err, "couldn't generate the secret for your new tunnel") + } + } else { + decodedSecret, err := base64.StdEncoding.DecodeString(secret) + if err != nil { + return nil, errors.Wrap(err, "Couldn't decode tunnel secret from base64") + } + tunnelSecret = []byte(decodedSecret) + if len(tunnelSecret) < 32 { + return nil, errors.New("Decoded tunnel secret must be at least 32 bytes long") + } } tunnel, err := client.CreateTunnel(name, tunnelSecret) diff --git a/cmd/cloudflared/tunnel/subcommands.go b/cmd/cloudflared/tunnel/subcommands.go index af44d182..7f415872 100644 --- a/cmd/cloudflared/tunnel/subcommands.go +++ b/cmd/cloudflared/tunnel/subcommands.go @@ -156,6 +156,12 @@ var ( Usage: `Overwrites existing DNS records with this hostname`, EnvVars: []string{"TUNNEL_FORCE_PROVISIONING_DNS"}, } + createSecretFlag = &cli.StringFlag{ + Name: "secret", + Aliases: []string{"s"}, + Usage: "Base64 encoded secret to set for the tunnel. The decoded secret must be at least 32 bytes long. If not specified, a random 32-byte secret will be generated.", + EnvVars: []string{"TUNNEL_CREATE_SECRET"}, + } ) func buildCreateCommand() *cli.Command { @@ -170,7 +176,7 @@ func buildCreateCommand() *cli.Command { For example, to create a tunnel named 'my-tunnel' run: $ cloudflared tunnel create my-tunnel`, - Flags: []cli.Flag{outputFormatFlag, credentialsFileFlagCLIOnly}, + Flags: []cli.Flag{outputFormatFlag, credentialsFileFlagCLIOnly, createSecretFlag}, CustomHelpTemplate: commandHelpTemplate(), } } @@ -196,7 +202,7 @@ func createCommand(c *cli.Context) error { warningChecker := updater.StartWarningCheck(c) defer warningChecker.LogWarningIfAny(sc.log) - _, err = sc.create(name, c.String(CredFileFlag)) + _, err = sc.create(name, c.String(CredFileFlag), c.String(createSecretFlag.Name)) return errors.Wrap(err, "failed to create tunnel") }