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-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-05-21 20:36:49 +00:00
"gopkg.in/yaml.v2"
"github.com/cloudflare/cloudflared/certutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2020-07-02 09:31:12 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
2020-04-29 20:51:32 +00:00
"github.com/cloudflare/cloudflared/logger"
2020-06-17 18:33:55 +00:00
"github.com/cloudflare/cloudflared/origin"
2020-06-15 18:33:41 +00:00
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
2020-05-21 20:36:49 +00:00
"github.com/cloudflare/cloudflared/tunnelstore"
)
2020-07-02 09:31:12 +00:00
const (
credFileFlagAlias = "cred-file"
)
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" } ,
Usage : "List tunnels with the given name" ,
}
listExistedAtFlag = & cli . TimestampFlag {
Name : "when" ,
Aliases : [ ] string { "w" } ,
Usage : fmt . Sprintf ( "List tunnels that are active at the given time, expect format in RFC3339 (%s)" , time . Now ( ) . Format ( tunnelstore . TimeLayout ) ) ,
Layout : tunnelstore . TimeLayout ,
}
listIDFlag = & cli . StringFlag {
Name : "id" ,
Aliases : [ ] string { "i" } ,
Usage : "List tunnel by ID" ,
}
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-05-21 20:36:49 +00:00
outputFormatFlag = & 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-05-21 20:36:49 +00:00
}
2020-06-25 18:25:39 +00:00
forceFlag = & 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-07-02 09:31:12 +00:00
credentialsFileFlag = & cli . StringFlag {
Name : "credentials-file" ,
Aliases : [ ] string { credFileFlagAlias } ,
Usage : "File path of tunnel credentials" ,
}
2020-07-10 22:11:31 +00:00
forceDeleteFlag = & cli . BoolFlag {
Name : "force" ,
Aliases : [ ] string { "f" } ,
2020-07-06 08:01:48 +00:00
Usage : "Allows you to delete a tunnel, even if it has active connections." ,
2020-07-10 22:11:31 +00:00
}
2020-05-21 20:36:49 +00:00
)
const hideSubcommands = true
func buildCreateCommand ( ) * cli . Command {
return & cli . Command {
Name : "create" ,
Action : cliutil . ErrorHandler ( createTunnel ) ,
Usage : "Create a new tunnel with given name" ,
ArgsUsage : "TUNNEL-NAME" ,
Hidden : hideSubcommands ,
Flags : [ ] cli . Flag { outputFormatFlag } ,
}
}
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-05-21 20:36:49 +00:00
func createTunnel ( c * cli . Context ) error {
if c . NArg ( ) != 1 {
return cliutil . UsageError ( ` "cloudflared tunnel create" requires exactly 1 argument, the name of tunnel to create. ` )
}
name := c . Args ( ) . First ( )
2020-07-20 22:34:02 +00:00
logger , err := createLogger ( c , false )
2020-04-29 20:51:32 +00:00
if err != nil {
return errors . Wrap ( err , "error setting up logger" )
}
2020-06-15 18:33:41 +00:00
tunnelSecret , err := generateTunnelSecret ( )
if err != nil {
return err
}
2020-07-03 08:55:11 +00:00
cert , originCertPath , err := getOriginCertFromContext ( c , logger )
2020-05-21 20:36:49 +00:00
if err != nil {
return err
}
2020-08-05 10:49:53 +00:00
client , err := newTunnelstoreClient ( c , cert , logger )
if err != nil {
return err
}
2020-05-21 20:36:49 +00:00
2020-06-15 18:33:41 +00:00
tunnel , err := client . CreateTunnel ( name , tunnelSecret )
2020-05-21 20:36:49 +00:00
if err != nil {
return errors . Wrap ( err , "Error creating a new tunnel" )
}
2020-06-15 18:33:41 +00:00
if writeFileErr := writeTunnelCredentials ( tunnel . ID , cert . AccountID , originCertPath , tunnelSecret , logger ) ; err != nil {
var errorLines [ ] string
errorLines = append ( errorLines , fmt . Sprintf ( "Your tunnel '%v' was created with ID %v. However, cloudflared couldn't write to the tunnel credentials file at %v.json." , tunnel . Name , tunnel . ID , tunnel . ID ) )
errorLines = append ( errorLines , fmt . Sprintf ( "The file-writing error is: %v" , writeFileErr ) )
if deleteErr := client . DeleteTunnel ( tunnel . ID ) ; deleteErr != nil {
errorLines = append ( errorLines , fmt . Sprintf ( "Cloudflared tried to delete the tunnel for you, but encountered an error. You should use `cloudflared tunnel delete %v` to delete the tunnel yourself, because the tunnel can't be run without the tunnelfile." , tunnel . ID ) )
errorLines = append ( errorLines , fmt . Sprintf ( "The delete tunnel error is: %v" , deleteErr ) )
} else {
errorLines = append ( errorLines , fmt . Sprintf ( "The tunnel was deleted, because the tunnel can't be run without the tunnelfile" ) )
}
errorMsg := strings . Join ( errorLines , "\n" )
return errors . New ( errorMsg )
}
2020-05-21 20:36:49 +00:00
if outputFormat := c . String ( outputFormatFlag . Name ) ; outputFormat != "" {
return renderOutput ( outputFormat , & tunnel )
}
logger . Infof ( "Created tunnel %s with id %s" , tunnel . Name , tunnel . ID )
return nil
}
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
}
2020-07-06 08:01:48 +00:00
func writeTunnelCredentials ( tunnelID uuid . UUID , accountID , originCertPath string , tunnelSecret [ ] byte , logger logger . Service ) error {
2020-07-02 09:31:12 +00:00
originCertDir := filepath . Dir ( originCertPath )
filePath , err := tunnelFilePath ( tunnelID , originCertDir )
2020-06-15 18:33:41 +00:00
if err != nil {
return err
}
body , err := json . Marshal ( pogs . TunnelAuth {
AccountTag : accountID ,
TunnelSecret : tunnelSecret ,
} )
if err != nil {
return errors . Wrap ( err , "Unable to marshal tunnel credentials to JSON" )
}
2020-06-16 22:43:22 +00:00
logger . Infof ( "Writing tunnel credentials to %v. cloudflared chose this file based on where your origin certificate was found." , filePath )
logger . Infof ( "Keep this file secret. To revoke these credentials, delete the tunnel." )
return ioutil . WriteFile ( filePath , body , 400 )
}
2020-07-06 08:01:48 +00:00
func readTunnelCredentials ( c * cli . Context , tunnelID uuid . UUID , logger logger . Service ) ( * pogs . TunnelAuth , error ) {
2020-07-02 09:31:12 +00:00
filePath , err := tunnelCredentialsPath ( c , tunnelID , logger )
2020-06-16 22:43:22 +00:00
if err != nil {
return nil , err
}
body , err := ioutil . ReadFile ( filePath )
if err != nil {
return nil , errors . Wrapf ( err , "couldn't read tunnel credentials from %v" , filePath )
}
2020-06-25 18:25:39 +00:00
var auth pogs . TunnelAuth
if err = json . Unmarshal ( body , & auth ) ; err != nil {
return nil , err
}
return & auth , nil
2020-06-15 18:33:41 +00:00
}
2020-07-06 08:01:48 +00:00
func tunnelCredentialsPath ( c * cli . Context , tunnelID uuid . UUID , logger logger . Service ) ( string , error ) {
2020-07-02 09:31:12 +00:00
if filePath := c . String ( "credentials-file" ) ; filePath != "" {
if validFilePath ( filePath ) {
return filePath , nil
}
}
// Fallback to look for tunnel credentials in the origin cert directory
if originCertPath , err := findOriginCert ( c , logger ) ; err == nil {
originCertDir := filepath . Dir ( originCertPath )
if filePath , err := tunnelFilePath ( tunnelID , originCertDir ) ; err == nil {
if validFilePath ( filePath ) {
return filePath , nil
}
}
}
// Last resort look under default config directories
for _ , configDir := range config . DefaultConfigDirs {
if filePath , err := tunnelFilePath ( tunnelID , configDir ) ; err == nil {
if validFilePath ( filePath ) {
return filePath , nil
}
}
}
return "" , fmt . Errorf ( "Tunnel credentials file not found" )
}
func validFilePath ( path string ) bool {
fileStat , err := os . Stat ( path )
if err != nil {
return false
}
return ! fileStat . IsDir ( )
}
2020-05-21 20:36:49 +00:00
func buildListCommand ( ) * cli . Command {
return & cli . Command {
Name : "list" ,
Action : cliutil . ErrorHandler ( listTunnels ) ,
Usage : "List existing tunnels" ,
ArgsUsage : " " ,
Hidden : hideSubcommands ,
2020-08-05 20:43:56 +00:00
Flags : [ ] cli . Flag { outputFormatFlag , showDeletedFlag , listNameFlag , listExistedAtFlag , listIDFlag , showRecentlyDisconnected } ,
2020-05-21 20:36:49 +00:00
}
}
func listTunnels ( c * cli . Context ) error {
2020-07-20 22:34:02 +00:00
logger , err := createLogger ( c , false )
2020-04-29 20:51:32 +00:00
if err != nil {
return errors . Wrap ( err , "error setting up logger" )
}
2020-07-03 08:55:11 +00:00
cert , _ , err := getOriginCertFromContext ( c , logger )
2020-05-21 20:36:49 +00:00
if err != nil {
return err
}
2020-08-05 10:49:53 +00:00
client , err := newTunnelstoreClient ( c , cert , logger )
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" ) {
filter . ShowDeleted ( )
}
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 )
}
tunnels , err := client . ListTunnels ( filter )
if err != nil {
return errors . Wrap ( err , "Error listing tunnels" )
2020-06-16 22:55:33 +00:00
}
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-08-05 20:43:56 +00:00
fmtAndPrintTunnelList ( 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" )
}
return nil
}
2020-08-05 20:43:56 +00:00
func fmtAndPrintTunnelList ( 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 )
// Print column headers with tabbed columns
fmt . Fprintln ( writer , "ID\tNAME\tCREATED\tCONNECTIONS\t" )
// 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-07-02 22:22:23 +00:00
fmt . Fprintln ( writer , formattedStr )
}
// Write data buffered in tabwriter to output
writer . Flush ( )
}
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 {
Name : "delete" ,
Action : cliutil . ErrorHandler ( deleteTunnel ) ,
Usage : "Delete existing tunnel with given ID" ,
ArgsUsage : "TUNNEL-ID" ,
Hidden : hideSubcommands ,
2020-07-10 22:11:31 +00:00
Flags : [ ] cli . Flag { credentialsFileFlag , forceDeleteFlag } ,
2020-05-21 20:36:49 +00:00
}
}
func deleteTunnel ( c * cli . Context ) error {
if c . NArg ( ) != 1 {
return cliutil . UsageError ( ` "cloudflared tunnel delete" requires exactly 1 argument, the ID of the tunnel to delete. ` )
}
2020-07-06 08:01:48 +00:00
tunnelID , err := uuid . Parse ( c . Args ( ) . First ( ) )
if err != nil {
return errors . Wrap ( err , "error parsing tunnel ID" )
}
2020-05-21 20:36:49 +00:00
2020-07-20 22:34:02 +00:00
logger , err := createLogger ( c , false )
2020-04-29 20:51:32 +00:00
if err != nil {
return errors . Wrap ( err , "error setting up logger" )
}
2020-07-03 08:55:11 +00:00
cert , _ , err := getOriginCertFromContext ( c , logger )
2020-05-21 20:36:49 +00:00
if err != nil {
return err
}
2020-08-05 10:49:53 +00:00
client , err := newTunnelstoreClient ( c , cert , logger )
if err != nil {
return err
}
2020-05-21 20:36:49 +00:00
2020-07-10 22:11:31 +00:00
forceFlagSet := c . Bool ( "force" )
2020-07-06 08:01:48 +00:00
tunnel , err := client . GetTunnel ( tunnelID )
2020-07-10 22:11:31 +00:00
if err != nil {
2020-07-06 08:01:48 +00:00
return errors . Wrapf ( err , "Can't get tunnel information. Please check tunnel id: %s" , tunnelID )
2020-07-10 22:11:31 +00:00
}
// Check if tunnel DeletedAt field has already been set
if ! tunnel . DeletedAt . IsZero ( ) {
return errors . New ( "This tunnel has already been deleted." )
}
// Check if tunnel has existing connections and if force flag is set, cleanup connections
if len ( tunnel . Connections ) > 0 {
if ! forceFlagSet {
return errors . New ( "You can not delete this tunnel because it has active connections. To see connections run the 'list' command. If you believe the tunnel is not active, you can use a -f / --force flag with this command." )
}
2020-07-06 08:01:48 +00:00
if err := client . CleanupConnections ( tunnelID ) ; err != nil {
return errors . Wrapf ( err , "Error cleaning up connections for tunnel %s" , tunnelID )
2020-07-10 22:11:31 +00:00
}
}
2020-07-06 08:01:48 +00:00
if err := client . DeleteTunnel ( tunnelID ) ; err != nil {
return errors . Wrapf ( err , "Error deleting tunnel %s" , tunnelID )
2020-05-21 20:36:49 +00:00
}
2020-07-06 08:01:48 +00:00
tunnelCredentialsPath , err := tunnelCredentialsPath ( c , tunnelID , logger )
2020-07-02 09:31:12 +00:00
if err != nil {
logger . Infof ( "Cannot locate tunnel credentials to delete, error: %v. Please delete the file manually" , err )
return nil
}
if err = os . Remove ( tunnelCredentialsPath ) ; err != nil {
logger . Infof ( "Cannot delete tunnel credentials, error: %v. Please delete the file manually" , err )
}
2020-05-21 20:36:49 +00:00
return nil
}
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-08-05 10:49:53 +00:00
func newTunnelstoreClient ( c * cli . Context , cert * certutil . OriginCert , logger logger . Service ) ( tunnelstore . Client , error ) {
return tunnelstore . NewRESTClient ( c . String ( "api-url" ) , cert . AccountID , cert . ZoneID , cert . ServiceKey , logger )
2020-06-15 18:33:41 +00:00
}
2020-07-03 08:55:11 +00:00
func getOriginCertFromContext ( c * cli . Context , logger logger . Service ) ( cert * certutil . OriginCert , originCertPath string , err error ) {
originCertPath , err = findOriginCert ( c , logger )
if err != nil {
return nil , "" , errors . Wrap ( err , "Error locating origin cert" )
}
2020-04-29 20:51:32 +00:00
blocks , err := readOriginCert ( originCertPath , logger )
2020-05-21 20:36:49 +00:00
if err != nil {
2020-07-03 08:55:11 +00:00
return nil , "" , errors . Wrapf ( err , "Can't read origin cert from %s" , originCertPath )
2020-05-21 20:36:49 +00:00
}
2020-07-03 08:55:11 +00:00
cert , err = certutil . DecodeOriginCert ( blocks )
2020-05-21 20:36:49 +00:00
if err != nil {
2020-07-03 08:55:11 +00:00
return nil , "" , errors . Wrap ( err , "Error decoding origin cert" )
2020-05-21 20:36:49 +00:00
}
if cert . AccountID == "" {
2020-07-03 08:55:11 +00:00
return nil , "" , errors . Errorf ( ` Origin certificate needs to be refreshed before creating new tunnels.\nDelete %s and run "cloudflared login" to obtain a new cert. ` , originCertPath )
2020-05-21 20:36:49 +00:00
}
2020-07-03 08:55:11 +00:00
return cert , originCertPath , nil
2020-05-21 20:36:49 +00:00
}
2020-06-16 22:43:22 +00:00
func buildRunCommand ( ) * cli . Command {
return & cli . Command {
Name : "run" ,
Action : cliutil . ErrorHandler ( runTunnel ) ,
Usage : "Proxy a local web server by running the given tunnel" ,
ArgsUsage : "TUNNEL-ID" ,
Hidden : hideSubcommands ,
2020-07-02 09:31:12 +00:00
Flags : [ ] cli . Flag { forceFlag , credentialsFileFlag } ,
2020-06-16 22:43:22 +00:00
}
}
func runTunnel ( c * cli . Context ) error {
if c . NArg ( ) != 1 {
return cliutil . UsageError ( ` "cloudflared tunnel run" requires exactly 1 argument, the ID of the tunnel to run. ` )
}
2020-07-06 08:01:48 +00:00
tunnelID , err := uuid . Parse ( c . Args ( ) . First ( ) )
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-07-20 22:34:02 +00:00
logger , err := createLogger ( c , false )
2020-06-16 22:43:22 +00:00
if err != nil {
return errors . Wrap ( err , "error setting up logger" )
}
2020-07-06 08:01:48 +00:00
credentials , err := readTunnelCredentials ( c , tunnelID , logger )
2020-06-16 22:43:22 +00:00
if err != nil {
return err
}
logger . Debugf ( "Read credentials for %v" , credentials . AccountTag )
2020-06-25 18:25:39 +00:00
return StartServer ( c , version , shutdownC , graceShutdownC , & origin . NamedTunnelConfig { Auth : * credentials , ID : tunnelID } )
2020-06-16 22:43:22 +00:00
}
2020-07-03 08:55:11 +00:00
func buildCleanupCommand ( ) * cli . Command {
return & cli . Command {
Name : "cleanup" ,
Action : cliutil . ErrorHandler ( cleanupConnections ) ,
Usage : "Cleanup connections for the tunnel with given IDs" ,
ArgsUsage : "TUNNEL-IDS" ,
Hidden : hideSubcommands ,
}
}
func cleanupConnections ( c * cli . Context ) error {
if c . NArg ( ) < 1 {
return cliutil . UsageError ( ` "cloudflared tunnel cleanup" requires at least 1 argument, the IDs of the tunnels to cleanup connections. ` )
}
2020-07-20 22:34:02 +00:00
logger , err := createLogger ( c , false )
2020-07-03 08:55:11 +00:00
if err != nil {
return errors . Wrap ( err , "error setting up logger" )
}
cert , _ , err := getOriginCertFromContext ( c , logger )
if err != nil {
return err
}
2020-08-05 10:49:53 +00:00
client , err := newTunnelstoreClient ( c , cert , logger )
if err != nil {
return err
}
2020-07-03 08:55:11 +00:00
for i := 0 ; i < c . NArg ( ) ; i ++ {
2020-07-06 08:01:48 +00:00
tunnelID , err := uuid . Parse ( c . Args ( ) . Get ( i ) )
if err != nil {
logger . Errorf ( "Failed to parse argument %d as tunnelID, error :%v" , i , err )
continue
}
logger . Infof ( "Cleanup connection for tunnel %s" , tunnelID )
if err := client . CleanupConnections ( tunnelID ) ; err != nil {
logger . Errorf ( "Error cleaning up connections for tunnel %v, error :%v" , tunnelID , err )
2020-07-03 08:55:11 +00:00
}
}
return nil
}
2020-07-06 08:01:48 +00:00
func buildRouteCommand ( ) * cli . Command {
return & cli . Command {
Name : "route" ,
Action : cliutil . ErrorHandler ( routeTunnel ) ,
Usage : "Define what hostname or load balancer can route to this tunnel" ,
Description : ` The route defines what hostname or load balancer can route to this tunnel .
To route a hostname : cloudflared tunnel route dns < tunnel ID > < hostname >
To route a load balancer : cloudflared tunnel route lb < tunnel ID > < load balancer name > < load balancer pool >
If you don ' t specify a load balancer pool , we will create a new pool called tunnel : < tunnel ID > ` ,
ArgsUsage : "dns|lb TUNNEL-ID HOSTNAME [LB-POOL]" ,
Hidden : hideSubcommands ,
}
}
func routeTunnel ( c * cli . Context ) error {
if c . NArg ( ) < 2 {
return cliutil . UsageError ( ` "cloudflared tunnel route" requires the first argument to be the route type(dns or lb), followed by the ID of the tunnel ` )
}
const tunnelIDIndex = 1
tunnelID , err := uuid . Parse ( c . Args ( ) . Get ( tunnelIDIndex ) )
if err != nil {
return errors . Wrap ( err , "error parsing tunnel ID" )
}
2020-07-20 22:34:02 +00:00
logger , err := createLogger ( c , false )
2020-07-06 08:01:48 +00:00
if err != nil {
return errors . Wrap ( err , "error setting up logger" )
}
routeType := c . Args ( ) . First ( )
var route tunnelstore . Route
switch routeType {
case "dns" :
route , err = dnsRouteFromArg ( c , tunnelID )
if err != nil {
return err
}
case "lb" :
route , err = lbRouteFromArg ( c , tunnelID , logger )
if err != nil {
return err
}
default :
return cliutil . UsageError ( "%s is not a recognized route type. Supported route types are dns and lb" , routeType )
}
cert , _ , err := getOriginCertFromContext ( c , logger )
if err != nil {
return err
}
2020-08-05 10:49:53 +00:00
client , err := newTunnelstoreClient ( c , cert , logger )
if err != nil {
return err
}
2020-07-20 22:34:02 +00:00
if err := client . RouteTunnel ( tunnelID , route ) ; err != nil {
return errors . Wrap ( err , "Failed to route tunnel" )
}
2020-07-30 14:33:10 +00:00
logger . Infof ( route . SuccessSummary ( ) )
2020-07-20 22:34:02 +00:00
return nil
2020-07-06 08:01:48 +00:00
}
func dnsRouteFromArg ( c * cli . Context , tunnelID uuid . UUID ) ( tunnelstore . Route , error ) {
const (
userHostnameIndex = 2
expectArgs = 3
)
if c . NArg ( ) != expectArgs {
return nil , cliutil . UsageError ( "Expect %d arguments, got %d" , expectArgs , c . NArg ( ) )
}
userHostname := c . Args ( ) . Get ( userHostnameIndex )
if userHostname == "" {
return nil , cliutil . UsageError ( "The third argument should be the hostname" )
}
return tunnelstore . NewDNSRoute ( userHostname ) , nil
}
func lbRouteFromArg ( c * cli . Context , tunnelID uuid . UUID , logger logger . Service ) ( tunnelstore . Route , error ) {
const (
lbNameIndex = 2
lbPoolIndex = 3
expectMinArgs = 3
)
if c . NArg ( ) < expectMinArgs {
return nil , cliutil . UsageError ( "Expect at least %d arguments, got %d" , expectMinArgs , c . NArg ( ) )
}
lbName := c . Args ( ) . Get ( lbNameIndex )
if lbName == "" {
return nil , cliutil . UsageError ( "The third argument should be the load balancer name" )
}
lbPool := c . Args ( ) . Get ( lbPoolIndex )
if lbPool == "" {
lbPool = fmt . Sprintf ( "tunnel:%v" , tunnelID )
logger . Infof ( "Generate pool name %s" , lbPool )
}
return tunnelstore . NewLBRoute ( lbName , lbPool ) , nil
}