2020-05-21 20:36:49 +00:00
package tunnel
import (
2020-06-15 18:33:41 +00:00
"crypto/rand"
2020-05-21 20:36:49 +00:00
"encoding/json"
"fmt"
2020-06-16 22:43:22 +00:00
"io/ioutil"
2020-05-21 20:36:49 +00:00
"os"
2020-06-15 18:33:41 +00:00
"path/filepath"
2020-09-14 21:41:02 +00:00
"regexp"
2020-06-11 23:44:39 +00:00
"sort"
"strings"
2020-07-02 22:22:23 +00:00
"text/tabwriter"
2020-05-21 20:36:49 +00:00
"time"
2020-06-25 18:25:39 +00:00
"github.com/google/uuid"
2020-07-02 09:31:12 +00:00
"github.com/mitchellh/go-homedir"
2020-05-21 20:36:49 +00:00
"github.com/pkg/errors"
2020-08-05 10:49:53 +00:00
"github.com/urfave/cli/v2"
2020-10-09 17:07:08 +00:00
"github.com/urfave/cli/v2/altsrc"
2020-09-15 13:36:36 +00:00
"golang.org/x/net/idna"
2020-05-21 20:36:49 +00:00
"gopkg.in/yaml.v2"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2021-03-08 16:46:23 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
"github.com/cloudflare/cloudflared/config"
2020-10-14 13:42:00 +00:00
"github.com/cloudflare/cloudflared/connection"
2020-05-21 20:36:49 +00:00
"github.com/cloudflare/cloudflared/tunnelstore"
)
2020-07-02 09:31:12 +00:00
const (
2021-02-03 18:22:57 +00:00
allSortByOptions = "name, id, createdAt, deletedAt, numConnections"
2020-11-23 21:36:16 +00:00
CredFileFlagAlias = "cred-file"
CredFileFlag = "credentials-file"
2020-12-28 18:10:01 +00:00
LogFieldTunnelID = "tunnelID"
2020-07-02 09:31:12 +00:00
)
2020-05-21 20:36:49 +00:00
var (
2020-06-16 22:55:33 +00:00
showDeletedFlag = & cli . BoolFlag {
Name : "show-deleted" ,
Aliases : [ ] string { "d" } ,
Usage : "Include deleted tunnels in the list" ,
}
2020-08-05 10:49:53 +00:00
listNameFlag = & cli . StringFlag {
Name : "name" ,
Aliases : [ ] string { "n" } ,
2020-09-15 13:36:36 +00:00
Usage : "List tunnels with the given `NAME`" ,
2020-08-05 10:49:53 +00:00
}
listExistedAtFlag = & cli . TimestampFlag {
2020-09-16 12:15:49 +00:00
Name : "when" ,
Aliases : [ ] string { "w" } ,
Usage : "List tunnels that are active at the given `TIME` in RFC3339 format" ,
Layout : tunnelstore . TimeLayout ,
2020-09-15 13:36:36 +00:00
DefaultText : fmt . Sprintf ( "current time, %s" , time . Now ( ) . Format ( tunnelstore . TimeLayout ) ) ,
2020-08-05 10:49:53 +00:00
}
listIDFlag = & cli . StringFlag {
Name : "id" ,
Aliases : [ ] string { "i" } ,
2020-09-15 13:36:36 +00:00
Usage : "List tunnel by `ID`" ,
2020-08-05 10:49:53 +00:00
}
2020-08-05 20:43:56 +00:00
showRecentlyDisconnected = & cli . BoolFlag {
Name : "show-recently-disconnected" ,
Aliases : [ ] string { "rd" } ,
Usage : "Include connections that have recently disconnected in the list" ,
}
2020-10-09 17:07:08 +00:00
outputFormatFlag = altsrc . NewStringFlag ( & cli . StringFlag {
2020-04-29 20:51:32 +00:00
Name : "output" ,
2020-05-21 20:36:49 +00:00
Aliases : [ ] string { "o" } ,
2020-04-29 20:51:32 +00:00
Usage : "Render output using given `FORMAT`. Valid options are 'json' or 'yaml'" ,
2020-10-09 17:07:08 +00:00
} )
2021-02-03 18:22:57 +00:00
sortByFlag = & cli . StringFlag {
Name : "sort-by" ,
Value : "name" ,
Usage : fmt . Sprintf ( "Sorts the list of tunnels by the given field. Valid options are {%s}" , allSortByOptions ) ,
EnvVars : [ ] string { "TUNNEL_LIST_SORT_BY" } ,
}
invertSortFlag = & cli . BoolFlag {
Name : "invert-sort" ,
Usage : "Inverts the sort order of the tunnel list." ,
EnvVars : [ ] string { "TUNNEL_LIST_INVERT_SORT" } ,
}
2020-10-09 17:07:08 +00:00
forceFlag = altsrc . NewBoolFlag ( & cli . BoolFlag {
2020-06-16 22:43:22 +00:00
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." ,
2020-10-09 17:07:08 +00:00
} )
2021-03-02 22:16:38 +00:00
featuresFlag = altsrc . NewStringSliceFlag ( & cli . StringSliceFlag {
Name : "features" ,
Aliases : [ ] string { "F" } ,
Usage : "Opt into various features that are still being developed or tested." ,
} )
2020-10-09 17:07:08 +00:00
credentialsFileFlag = altsrc . NewStringFlag ( & cli . StringFlag {
2020-11-23 21:36:16 +00:00
Name : CredFileFlag ,
Aliases : [ ] string { CredFileFlagAlias } ,
2021-02-04 17:57:50 +00:00
Usage : "Filepath at which to read/write the tunnel credentials" ,
2020-10-08 10:12:26 +00:00
EnvVars : [ ] string { "TUNNEL_CRED_FILE" } ,
2020-10-09 17:07:08 +00:00
} )
2020-07-10 22:11:31 +00:00
forceDeleteFlag = & cli . BoolFlag {
Name : "force" ,
Aliases : [ ] string { "f" } ,
2021-02-11 21:22:51 +00:00
Usage : "Cleans up any stale connections before the tunnel is deleted. cloudflared will not " +
"delete a tunnel with connections without this flag." ,
2020-10-08 10:12:26 +00:00
EnvVars : [ ] string { "TUNNEL_RUN_FORCE_OVERWRITE" } ,
2020-07-10 22:11:31 +00:00
}
2020-10-14 10:28:07 +00:00
selectProtocolFlag = altsrc . NewStringFlag ( & cli . StringFlag {
2020-10-08 09:48:10 +00:00
Name : "protocol" ,
Value : "h2mux" ,
Aliases : [ ] string { "p" } ,
2020-10-14 13:42:00 +00:00
Usage : fmt . Sprintf ( "Protocol implementation to connect with Cloudflare's edge network. %s" , connection . AvailableProtocolFlagMessage ) ,
2020-10-08 10:12:26 +00:00
EnvVars : [ ] string { "TUNNEL_TRANSPORT_PROTOCOL" } ,
2020-10-08 09:48:10 +00:00
Hidden : true ,
2020-10-14 10:28:07 +00:00
} )
2020-05-21 20:36:49 +00:00
)
func buildCreateCommand ( ) * cli . Command {
return & cli . Command {
2020-10-08 10:12:26 +00:00
Name : "create" ,
Action : cliutil . ErrorHandler ( createCommand ) ,
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 .
2020-11-20 22:22:32 +00:00
Use "cloudflared tunnel route" subcommand to map a DNS name to this tunnel and "cloudflared tunnel run" to start the connection .
2020-10-09 17:07:08 +00:00
For example , to create a tunnel named ' my - tunnel ' run :
$ cloudflared tunnel create my - tunnel ` ,
2021-02-04 17:57:50 +00:00
Flags : [ ] cli . Flag { outputFormatFlag , credentialsFileFlag } ,
2020-09-29 09:47:50 +00:00
CustomHelpTemplate : commandHelpTemplate ( ) ,
2020-05-21 20:36:49 +00:00
}
}
2020-06-15 18:33:41 +00:00
// generateTunnelSecret as an array of 32 bytes using secure random number generator
func generateTunnelSecret ( ) ( [ ] byte , error ) {
randomBytes := make ( [ ] byte , 32 )
_ , err := rand . Read ( randomBytes )
return randomBytes , err
}
2020-08-07 12:29:53 +00:00
func createCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
2020-04-29 20:51:32 +00:00
if err != nil {
return errors . Wrap ( err , "error setting up logger" )
}
2020-08-07 12:29:53 +00:00
if c . NArg ( ) != 1 {
return cliutil . UsageError ( ` "cloudflared tunnel create" requires exactly 1 argument, the name of tunnel to create. ` )
2020-05-21 20:36:49 +00:00
}
2020-08-07 12:29:53 +00:00
name := c . Args ( ) . First ( )
2020-05-21 20:36:49 +00:00
2021-02-28 23:24:38 +00:00
warningChecker := updater . StartWarningCheck ( c )
defer warningChecker . LogWarningIfAny ( sc . log )
2021-02-04 17:57:50 +00:00
_ , err = sc . create ( name , c . String ( CredFileFlag ) )
2020-08-07 12:29:53 +00:00
return errors . Wrap ( err , "failed to create tunnel" )
2020-05-21 20:36:49 +00:00
}
2020-07-06 08:01:48 +00:00
func tunnelFilePath ( tunnelID uuid . UUID , directory string ) ( string , error ) {
2020-06-15 18:33:41 +00:00
fileName := fmt . Sprintf ( "%v.json" , tunnelID )
2020-07-02 09:31:12 +00:00
filePath := filepath . Clean ( fmt . Sprintf ( "%s/%s" , directory , fileName ) )
return homedir . Expand ( filePath )
2020-06-15 18:33:41 +00:00
}
2021-02-04 17:57:50 +00:00
// If an `outputFile` is given, write the credentials there.
// Otherwise, write it to the same directory as the originCert,
// with the filename `<tunnel id>.json`.
2020-11-23 21:36:16 +00:00
func writeTunnelCredentials (
2021-02-04 17:57:50 +00:00
originCertPath , outputFile string ,
2020-11-23 21:36:16 +00:00
credentials * connection . Credentials ,
) ( filePath string , err error ) {
2021-02-04 17:57:50 +00:00
filePath = outputFile
if outputFile == "" {
originCertDir := filepath . Dir ( originCertPath )
filePath , err = tunnelFilePath ( credentials . TunnelID , originCertDir )
}
2020-06-15 18:33:41 +00:00
if err != nil {
2020-11-23 21:36:16 +00:00
return "" , err
2020-06-15 18:33:41 +00:00
}
2020-11-23 21:36:16 +00:00
// Write the name and ID to the file too
body , err := json . Marshal ( credentials )
2020-07-02 09:31:12 +00:00
if err != nil {
2020-11-23 21:36:16 +00:00
return "" , errors . Wrap ( err , "Unable to marshal tunnel credentials to JSON" )
2020-07-02 09:31:12 +00:00
}
2020-11-23 21:36:16 +00:00
return filePath , ioutil . WriteFile ( filePath , body , 400 )
2020-07-02 09:31:12 +00:00
}
2020-05-21 20:36:49 +00:00
func buildListCommand ( ) * cli . Command {
return & cli . Command {
2021-02-03 18:22:57 +00:00
Name : "list" ,
Action : cliutil . ErrorHandler ( listCommand ) ,
Usage : "List existing tunnels" ,
UsageText : "cloudflared tunnel [tunnel command options] list [subcommand options]" ,
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 ,
sortByFlag ,
invertSortFlag ,
} ,
2020-09-29 09:47:50 +00:00
CustomHelpTemplate : commandHelpTemplate ( ) ,
2020-05-21 20:36:49 +00:00
}
}
2020-08-07 12:29:53 +00:00
func listCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
2020-05-21 20:36:49 +00:00
if err != nil {
2020-08-05 10:49:53 +00:00
return err
2020-05-21 20:36:49 +00:00
}
2020-08-05 10:49:53 +00:00
filter := tunnelstore . NewFilter ( )
if ! c . Bool ( "show-deleted" ) {
2020-08-07 12:29:53 +00:00
filter . NoDeleted ( )
2020-08-05 10:49:53 +00:00
}
if name := c . String ( "name" ) ; name != "" {
filter . ByName ( name )
}
if existedAt := c . Timestamp ( "time" ) ; existedAt != nil {
filter . ByExistedAt ( * existedAt )
}
if id := c . String ( "id" ) ; id != "" {
tunnelID , err := uuid . Parse ( id )
if err != nil {
return errors . Wrapf ( err , "%s is not a valid tunnel ID" , id )
2020-06-16 22:55:33 +00:00
}
2020-08-05 10:49:53 +00:00
filter . ByTunnelID ( tunnelID )
}
2021-02-28 23:24:38 +00:00
warningChecker := updater . StartWarningCheck ( c )
defer warningChecker . LogWarningIfAny ( sc . log )
2020-08-07 12:29:53 +00:00
tunnels , err := sc . list ( filter )
2020-08-05 10:49:53 +00:00
if err != nil {
2020-08-07 12:29:53 +00:00
return err
2020-06-16 22:55:33 +00:00
}
2021-02-03 18:22:57 +00:00
// Sort the tunnels
sortBy := c . String ( "sort-by" )
invalidSortField := false
sort . Slice ( tunnels , func ( i , j int ) bool {
cmp := func ( ) bool {
switch sortBy {
case "name" :
return tunnels [ i ] . Name < tunnels [ j ] . Name
case "id" :
return tunnels [ i ] . ID . String ( ) < tunnels [ j ] . ID . String ( )
case "createdAt" :
return tunnels [ i ] . CreatedAt . Unix ( ) < tunnels [ j ] . CreatedAt . Unix ( )
case "deletedAt" :
return tunnels [ i ] . DeletedAt . Unix ( ) < tunnels [ j ] . DeletedAt . Unix ( )
case "numConnections" :
return len ( tunnels [ i ] . Connections ) < len ( tunnels [ j ] . Connections )
default :
invalidSortField = true
return tunnels [ i ] . Name < tunnels [ j ] . Name
}
} ( )
if c . Bool ( "invert-sort" ) {
return ! cmp
}
return cmp
} )
if invalidSortField {
sc . log . Error ( ) . Msgf ( "%s is not a valid sort field. Valid sort fields are %s. Defaulting to 'name'." , sortBy , allSortByOptions )
}
2020-05-21 20:36:49 +00:00
if outputFormat := c . String ( outputFormatFlag . Name ) ; outputFormat != "" {
return renderOutput ( outputFormat , tunnels )
}
2020-07-02 22:22:23 +00:00
2020-05-21 20:36:49 +00:00
if len ( tunnels ) > 0 {
2020-12-22 02:06:46 +00:00
formatAndPrintTunnelList ( tunnels , c . Bool ( "show-recently-disconnected" ) )
2020-05-21 20:36:49 +00:00
} else {
fmt . Println ( "You have no tunnels, use 'cloudflared tunnel create' to define a new tunnel" )
}
2021-02-28 23:24:38 +00:00
2020-05-21 20:36:49 +00:00
return nil
}
2020-12-22 02:06:46 +00:00
func formatAndPrintTunnelList ( tunnels [ ] * tunnelstore . Tunnel , showRecentlyDisconnected bool ) {
2020-07-02 22:22:23 +00:00
const (
minWidth = 0
tabWidth = 8
padding = 1
padChar = ' '
flags = 0
)
writer := tabwriter . NewWriter ( os . Stdout , minWidth , tabWidth , padding , padChar , flags )
2020-09-14 22:23:23 +00:00
defer writer . Flush ( )
2020-07-02 22:22:23 +00:00
// Print column headers with tabbed columns
2020-11-25 06:55:13 +00:00
_ , _ = fmt . Fprintln ( writer , "ID\tNAME\tCREATED\tCONNECTIONS\t" )
2020-07-02 22:22:23 +00:00
// Loop through tunnels, create formatted string for each, and print using tabwriter
for _ , t := range tunnels {
2020-08-05 20:43:56 +00:00
formattedStr := fmt . Sprintf (
"%s\t%s\t%s\t%s\t" ,
t . ID ,
t . Name ,
t . CreatedAt . Format ( time . RFC3339 ) ,
fmtConnections ( t . Connections , showRecentlyDisconnected ) ,
)
2020-11-25 06:55:13 +00:00
_ , _ = fmt . Fprintln ( writer , formattedStr )
2020-07-02 22:22:23 +00:00
}
}
2020-08-05 20:43:56 +00:00
func fmtConnections ( connections [ ] tunnelstore . Connection , showRecentlyDisconnected bool ) string {
2020-06-11 23:44:39 +00:00
// Count connections per colo
numConnsPerColo := make ( map [ string ] uint , len ( connections ) )
for _ , connection := range connections {
2020-08-05 20:43:56 +00:00
if ! connection . IsPendingReconnect || showRecentlyDisconnected {
numConnsPerColo [ connection . ColoName ] ++
}
2020-06-11 23:44:39 +00:00
}
// Get sorted list of colos
sortedColos := [ ] string { }
for coloName := range numConnsPerColo {
sortedColos = append ( sortedColos , coloName )
}
sort . Strings ( sortedColos )
// Map each colo to its frequency, combine into output string.
var output [ ] string
for _ , coloName := range sortedColos {
output = append ( output , fmt . Sprintf ( "%dx%s" , numConnsPerColo [ coloName ] , coloName ) )
}
return strings . Join ( output , ", " )
}
2020-05-21 20:36:49 +00:00
func buildDeleteCommand ( ) * cli . Command {
return & cli . Command {
2020-09-29 09:47:50 +00:00
Name : "delete" ,
Action : cliutil . ErrorHandler ( deleteCommand ) ,
Usage : "Delete existing tunnel by UUID or name" ,
2020-10-09 17:07:08 +00:00
UsageText : "cloudflared tunnel [tunnel command options] delete [subcommand options] TUNNEL" ,
2020-09-29 09:47:50 +00:00
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." ,
Flags : [ ] cli . Flag { credentialsFileFlag , forceDeleteFlag } ,
CustomHelpTemplate : commandHelpTemplate ( ) ,
2020-05-21 20:36:49 +00:00
}
}
2020-08-07 12:29:53 +00:00
func deleteCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
2020-07-06 08:01:48 +00:00
if err != nil {
2020-08-07 12:29:53 +00:00
return err
2020-07-06 08:01:48 +00:00
}
2020-05-21 20:36:49 +00:00
2020-08-07 12:29:53 +00:00
if c . NArg ( ) < 1 {
2020-08-18 21:54:05 +00:00
return cliutil . UsageError ( ` "cloudflared tunnel delete" requires at least 1 argument, the ID or name of the tunnel to delete. ` )
2020-04-29 20:51:32 +00:00
}
2021-02-28 23:24:38 +00:00
warningChecker := updater . StartWarningCheck ( c )
defer warningChecker . LogWarningIfAny ( sc . log )
2020-08-18 21:54:05 +00:00
tunnelIDs , err := sc . findIDs ( c . Args ( ) . Slice ( ) )
2020-08-05 10:49:53 +00:00
if err != nil {
return err
}
2020-05-21 20:36:49 +00:00
2020-08-07 12:29:53 +00:00
return sc . delete ( tunnelIDs )
}
2020-07-06 08:01:48 +00:00
2020-05-21 20:36:49 +00:00
func renderOutput ( format string , v interface { } ) error {
switch format {
case "json" :
encoder := json . NewEncoder ( os . Stdout )
encoder . SetIndent ( "" , " " )
return encoder . Encode ( v )
case "yaml" :
return yaml . NewEncoder ( os . Stdout ) . Encode ( v )
default :
return errors . Errorf ( "Unknown output format '%s'" , format )
}
}
2020-06-16 22:43:22 +00:00
func buildRunCommand ( ) * cli . Command {
2020-09-16 12:15:49 +00:00
flags := [ ] cli . Flag {
forceFlag ,
credentialsFileFlag ,
2020-10-08 09:48:10 +00:00
selectProtocolFlag ,
2021-03-02 22:16:38 +00:00
featuresFlag ,
2020-09-16 12:15:49 +00:00
}
2020-09-29 09:47:50 +00:00
flags = append ( flags , configureProxyFlags ( false ) ... )
2020-06-16 22:43:22 +00:00
return & cli . Command {
Name : "run" ,
2020-08-07 12:29:53 +00:00
Action : cliutil . ErrorHandler ( runCommand ) ,
2020-10-09 17:07:08 +00:00
Before : SetFlagsFromConfigFile ,
2020-06-16 22:43:22 +00:00
Usage : "Proxy a local web server by running the given tunnel" ,
2020-10-15 20:08:57 +00:00
UsageText : "cloudflared tunnel [tunnel command options] run [subcommand options] [TUNNEL]" ,
2020-11-20 22:22:32 +00:00
Description : ` Runs the tunnel identified by name or UUUD , creating highly available connections
2020-10-15 20:08:57 +00:00
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" .
2020-09-14 22:23:23 +00:00
2020-11-20 22:22:32 +00:00
This command requires the tunnel credentials file created when "cloudflared tunnel create" was run ,
2020-10-09 17:07:08 +00:00
however it does not need access to cert . pem from "cloudflared login" if you identify the tunnel by UUID .
2020-11-20 22:22:32 +00:00
If you experience other problems running the tunnel , "cloudflared tunnel cleanup" may help by removing
2020-10-09 17:07:08 +00:00
any old connection records .
2020-09-16 12:15:49 +00:00
` ,
Flags : flags ,
2020-09-29 09:47:50 +00:00
CustomHelpTemplate : commandHelpTemplate ( ) ,
2020-06-16 22:43:22 +00:00
}
}
2020-08-07 12:29:53 +00:00
func runCommand ( c * cli . Context ) error {
sc , err := newSubcommandContext ( c )
if err != nil {
return err
}
2020-10-15 20:08:57 +00:00
if c . NArg ( ) > 1 {
return cliutil . UsageError ( ` "cloudflared tunnel run" accepts only one argument, the ID or name of the tunnel to run. ` )
}
tunnelRef := c . Args ( ) . First ( )
if tunnelRef == "" {
2020-10-19 22:33:40 +00:00
// see if tunnel id was in the config file
tunnelRef = config . GetConfiguration ( ) . TunnelID
2020-10-15 20:08:57 +00:00
if tunnelRef == "" {
return cliutil . UsageError ( ` "cloudflared tunnel run" requires the ID or name of the tunnel to run as the last command line argument or in the configuration file. ` )
}
2020-06-16 22:43:22 +00:00
}
2020-10-15 20:08:57 +00:00
return runNamedTunnel ( sc , tunnelRef )
}
func runNamedTunnel ( sc * subcommandContext , tunnelRef string ) error {
tunnelID , err := sc . findID ( tunnelRef )
2020-06-25 18:25:39 +00:00
if err != nil {
return errors . Wrap ( err , "error parsing tunnel ID" )
}
2020-06-16 22:43:22 +00:00
2020-12-28 18:10:01 +00:00
sc . log . Info ( ) . Str ( LogFieldTunnelID , tunnelID . String ( ) ) . Msg ( "Starting tunnel" )
2020-10-15 20:08:57 +00:00
2020-08-07 12:29:53 +00:00
return sc . run ( tunnelID )
2020-06-16 22:43:22 +00:00
}
2020-07-03 08:55:11 +00:00
func buildCleanupCommand ( ) * cli . Command {
return & cli . Command {
2020-09-29 09:47:50 +00:00
Name : "cleanup" ,
Action : cliutil . ErrorHandler ( cleanupCommand ) ,
Usage : "Cleanup tunnel connections" ,
2020-10-09 17:07:08 +00:00
UsageText : "cloudflared tunnel [tunnel command options] cleanup [subcommand options] TUNNEL" ,
2020-09-29 09:47:50 +00:00
Description : "Delete connections for tunnels with the given UUIDs or names." ,
CustomHelpTemplate : commandHelpTemplate ( ) ,
2020-07-03 08:55:11 +00:00
}
}
2020-08-07 12:29:53 +00:00
func cleanupCommand ( c * cli . Context ) error {
2020-07-03 08:55:11 +00:00
if c . NArg ( ) < 1 {
return cliutil . UsageError ( ` "cloudflared tunnel cleanup" requires at least 1 argument, the IDs of the tunnels to cleanup connections. ` )
}
2020-08-07 12:29:53 +00:00
sc , err := newSubcommandContext ( c )
2020-07-03 08:55:11 +00:00
if err != nil {
return err
}
2020-08-07 12:29:53 +00:00
2020-08-18 21:54:05 +00:00
tunnelIDs , err := sc . findIDs ( c . Args ( ) . Slice ( ) )
2020-08-05 10:49:53 +00:00
if err != nil {
return err
}
2020-07-03 08:55:11 +00:00
2020-08-07 12:29:53 +00:00
return sc . cleanupConnections ( tunnelIDs )
2020-07-03 08:55:11 +00:00
}
2020-07-06 08:01:48 +00:00
func buildRouteCommand ( ) * cli . Command {
return & cli . Command {
2020-09-29 09:47:50 +00:00
Name : "route" ,
Action : cliutil . ErrorHandler ( routeCommand ) ,
2021-02-26 09:50:19 +00:00
Usage : "Define which traffic routed from Cloudflare edge to this tunnel: requests to a DNS hostname, to a Cloudflare Load Balancer, or traffic originating from Cloudflare WARP clients" ,
UsageText : "cloudflared tunnel [tunnel command options] route [subcommand options] [dns TUNNEL HOSTNAME]|[lb TUNNEL HOSTNAME LB-POOL]|[ip NETWORK TUNNEL]" ,
Description : ` The route command defines how Cloudflare will proxy requests to this tunnel .
To route a hostname by creating a DNS CNAME record to a tunnel :
cloudflared tunnel route dns < tunnel ID or name > < hostname >
You can read more at : https : //developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/dns
To use this tunnel as a load balancer origin , creating pool and load balancer if necessary :
cloudflared tunnel route lb < tunnel ID or name > < hostname > < load balancer pool >
You can read more at : https : //developers.cloudflare.com/cloudflare-one/connections/connect-apps/routing-to-tunnel/lb
For Cloudflare WARP traffic to be routed to your private network , reachable from this tunnel as origins , use :
cloudflared tunnel route ip < network CIDR > < tunnel ID or name >
Further information about managing Cloudflare WARP traffic to your tunnel is available at :
cloudflared tunnel route ip -- help
` ,
2020-09-29 09:47:50 +00:00
CustomHelpTemplate : commandHelpTemplate ( ) ,
2020-12-22 02:06:46 +00:00
Subcommands : [ ] * cli . Command {
buildRouteIPSubcommand ( ) ,
} ,
2020-07-06 08:01:48 +00:00
}
}
2020-09-14 21:41:02 +00:00
func dnsRouteFromArg ( c * cli . Context ) ( tunnelstore . Route , error ) {
2020-07-06 08:01:48 +00:00
const (
userHostnameIndex = 2
2020-09-14 21:41:02 +00:00
expectedNArgs = 3
2020-07-06 08:01:48 +00:00
)
2020-09-14 21:41:02 +00:00
if c . NArg ( ) != expectedNArgs {
return nil , cliutil . UsageError ( "Expected %d arguments, got %d" , expectedNArgs , c . NArg ( ) )
2020-07-06 08:01:48 +00:00
}
userHostname := c . Args ( ) . Get ( userHostnameIndex )
if userHostname == "" {
return nil , cliutil . UsageError ( "The third argument should be the hostname" )
2020-11-20 22:22:32 +00:00
} else if ! validateHostname ( userHostname , true ) {
2020-09-14 21:41:02 +00:00
return nil , errors . Errorf ( "%s is not a valid hostname" , userHostname )
2020-07-06 08:01:48 +00:00
}
return tunnelstore . NewDNSRoute ( userHostname ) , nil
}
2020-09-14 21:41:02 +00:00
func lbRouteFromArg ( c * cli . Context ) ( tunnelstore . Route , error ) {
2020-07-06 08:01:48 +00:00
const (
lbNameIndex = 2
lbPoolIndex = 3
2020-09-14 21:41:02 +00:00
expectedNArgs = 4
2020-07-06 08:01:48 +00:00
)
2020-09-14 21:41:02 +00:00
if c . NArg ( ) != expectedNArgs {
return nil , cliutil . UsageError ( "Expected %d arguments, got %d" , expectedNArgs , c . NArg ( ) )
2020-07-06 08:01:48 +00:00
}
lbName := c . Args ( ) . Get ( lbNameIndex )
if lbName == "" {
return nil , cliutil . UsageError ( "The third argument should be the load balancer name" )
2020-11-20 22:22:32 +00:00
} else if ! validateHostname ( lbName , true ) {
2020-09-14 21:41:02 +00:00
return nil , errors . Errorf ( "%s is not a valid load balancer name" , lbName )
2020-07-06 08:01:48 +00:00
}
2020-09-14 21:41:02 +00:00
2020-07-06 08:01:48 +00:00
lbPool := c . Args ( ) . Get ( lbPoolIndex )
if lbPool == "" {
2020-09-14 21:41:02 +00:00
return nil , cliutil . UsageError ( "The fourth argument should be the pool name" )
2020-11-20 22:22:32 +00:00
} else if ! validateName ( lbPool , false ) {
2020-09-14 21:41:02 +00:00
return nil , errors . Errorf ( "%s is not a valid pool name" , lbPool )
2020-07-06 08:01:48 +00:00
}
return tunnelstore . NewLBRoute ( lbName , lbPool ) , nil
}
2020-08-07 12:29:53 +00:00
2020-09-14 21:41:02 +00:00
var nameRegex = regexp . MustCompile ( "^[_a-zA-Z0-9][-_.a-zA-Z0-9]*$" )
2020-11-20 22:22:32 +00:00
var hostNameRegex = regexp . MustCompile ( "^[*_a-zA-Z0-9][-_.a-zA-Z0-9]*$" )
2020-09-14 21:41:02 +00:00
2020-11-20 22:22:32 +00:00
func validateName ( s string , allowWildcardSubdomain bool ) bool {
if allowWildcardSubdomain {
return hostNameRegex . MatchString ( s )
}
2020-09-14 21:41:02 +00:00
return nameRegex . MatchString ( s )
}
2020-11-20 22:22:32 +00:00
func validateHostname ( s string , allowWildcardSubdomain bool ) bool {
2020-09-15 13:36:36 +00:00
// Slightly stricter than PunyCodeProfile
idnaProfile := idna . New (
idna . ValidateLabels ( true ) ,
idna . VerifyDNSLength ( true ) )
puny , err := idnaProfile . ToASCII ( s )
2020-11-20 22:22:32 +00:00
return err == nil && validateName ( puny , allowWildcardSubdomain )
2020-09-15 13:36:36 +00:00
}
2020-08-07 12:29:53 +00:00
func routeCommand ( c * cli . Context ) error {
if c . NArg ( ) < 2 {
2020-08-18 21:54:05 +00:00
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 ` )
2020-08-07 12:29:53 +00:00
}
2020-08-18 21:54:05 +00:00
sc , err := newSubcommandContext ( c )
2020-08-07 12:29:53 +00:00
if err != nil {
2020-08-18 21:54:05 +00:00
return err
2020-08-07 12:29:53 +00:00
}
2020-08-18 21:54:05 +00:00
const tunnelIDIndex = 1
2020-08-07 12:29:53 +00:00
routeType := c . Args ( ) . First ( )
2020-09-17 20:19:47 +00:00
var route tunnelstore . Route
2020-09-04 15:05:32 +00:00
var tunnelID uuid . UUID
2020-08-07 12:29:53 +00:00
switch routeType {
case "dns" :
2020-09-04 15:05:32 +00:00
tunnelID , err = sc . findID ( c . Args ( ) . Get ( tunnelIDIndex ) )
if err != nil {
return err
}
2020-09-17 20:19:47 +00:00
route , err = dnsRouteFromArg ( c )
2020-08-07 12:29:53 +00:00
if err != nil {
return err
}
case "lb" :
2020-09-04 15:05:32 +00:00
tunnelID , err = sc . findID ( c . Args ( ) . Get ( tunnelIDIndex ) )
if err != nil {
return err
}
2020-09-17 20:19:47 +00:00
route , err = lbRouteFromArg ( c )
2020-08-07 12:29:53 +00:00
if err != nil {
return err
}
default :
return cliutil . UsageError ( "%s is not a recognized route type. Supported route types are dns and lb" , routeType )
}
2020-09-17 20:19:47 +00:00
res , err := sc . route ( tunnelID , route )
if err != nil {
2020-08-07 12:29:53 +00:00
return err
}
2020-09-17 20:19:47 +00:00
2020-12-28 18:10:01 +00:00
sc . log . Info ( ) . Str ( LogFieldTunnelID , tunnelID . String ( ) ) . Msg ( res . SuccessSummary ( ) )
2020-08-07 12:29:53 +00:00
return nil
}
2020-09-29 09:47:50 +00:00
func commandHelpTemplate ( ) string {
var parentFlagsHelp string
for _ , f := range configureCloudflaredFlags ( false ) {
parentFlagsHelp += fmt . Sprintf ( " %s\n\t" , f )
}
for _ , f := range configureLoggingFlags ( false ) {
parentFlagsHelp += fmt . Sprintf ( " %s\n\t" , f )
}
const template = ` NAME :
{ { . HelpName } } - { { . Usage } }
2020-11-20 22:22:32 +00:00
2020-09-29 09:47:50 +00:00
USAGE :
{ { . UsageText } }
2020-11-20 22:22:32 +00:00
2020-09-29 09:47:50 +00:00
DESCRIPTION :
{ { . Description } }
2020-11-20 22:22:32 +00:00
2020-09-29 09:47:50 +00:00
TUNNEL COMMAND OPTIONS :
% s
2020-10-09 17:07:08 +00:00
SUBCOMMAND OPTIONS :
2020-09-29 09:47:50 +00:00
{ { range . VisibleFlags } } { { . } }
{ { end } }
`
return fmt . Sprintf ( template , parentFlagsHelp )
}