2020-08-07 12:29:53 +00:00
package tunnel
import (
2021-09-29 23:56:38 +00:00
"encoding/base64"
2020-08-07 12:29:53 +00:00
"encoding/json"
"fmt"
"os"
2021-03-24 03:32:40 +00:00
"path/filepath"
2020-08-07 12:29:53 +00:00
"strings"
2020-10-15 20:08:57 +00:00
"github.com/google/uuid"
"github.com/pkg/errors"
2020-11-25 06:55:13 +00:00
"github.com/rs/zerolog"
2020-10-15 20:08:57 +00:00
"github.com/urfave/cli/v2"
2021-12-27 14:56:50 +00:00
"github.com/cloudflare/cloudflared/cfapi"
2020-10-08 10:12:26 +00:00
"github.com/cloudflare/cloudflared/connection"
2023-04-12 16:43:38 +00:00
"github.com/cloudflare/cloudflared/credentials"
2020-08-07 12:29:53 +00:00
"github.com/cloudflare/cloudflared/logger"
)
2020-11-10 18:27:52 +00:00
type errInvalidJSONCredential struct {
err error
path string
}
func ( e errInvalidJSONCredential ) Error ( ) string {
return "Invalid JSON when parsing tunnel credentials file"
}
2020-09-01 16:06:00 +00:00
// subcommandContext carries structs shared between subcommands, to reduce number of arguments needed to
// pass between subcommands, and make sure they are only initialized once
2020-08-07 12:29:53 +00:00
type subcommandContext struct {
2022-07-20 23:17:29 +00:00
c * cli . Context
log * zerolog . Logger
fs fileSystem
2020-08-07 12:29:53 +00:00
// These fields should be accessed using their respective Getter
2021-12-27 14:56:50 +00:00
tunnelstoreClient cfapi . Client
2023-04-12 16:43:38 +00:00
userCredential * credentials . User
2020-08-07 12:29:53 +00:00
}
func newSubcommandContext ( c * cli . Context ) ( * subcommandContext , error ) {
return & subcommandContext {
2022-07-20 23:17:29 +00:00
c : c ,
log : logger . CreateLoggerFromContext ( c , logger . EnableTerminalLog ) ,
fs : realFileSystem { } ,
2020-08-07 12:29:53 +00:00
} , nil
}
2020-11-23 21:36:16 +00:00
// Returns something that can find the given tunnel's credentials file.
func ( sc * subcommandContext ) credentialFinder ( tunnelID uuid . UUID ) CredFinder {
if path := sc . c . String ( CredFileFlag ) ; path != "" {
return newStaticPath ( path , sc . fs )
}
2020-11-25 06:55:13 +00:00
return newSearchByID ( tunnelID , sc . c , sc . log , sc . fs )
2020-11-23 21:36:16 +00:00
}
2021-12-27 14:56:50 +00:00
func ( sc * subcommandContext ) client ( ) ( cfapi . Client , error ) {
2020-08-07 12:29:53 +00:00
if sc . tunnelstoreClient != nil {
return sc . tunnelstoreClient , nil
}
2023-04-12 16:43:38 +00:00
cred , err := sc . credential ( )
2020-08-07 12:29:53 +00:00
if err != nil {
return nil , err
}
2023-04-12 16:43:38 +00:00
sc . tunnelstoreClient , err = cred . Client ( sc . c . String ( "api-url" ) , buildInfo . UserAgent ( ) , sc . log )
2020-08-07 12:29:53 +00:00
if err != nil {
return nil , err
}
2023-04-12 16:43:38 +00:00
return sc . tunnelstoreClient , nil
2020-08-07 12:29:53 +00:00
}
2023-04-12 16:43:38 +00:00
func ( sc * subcommandContext ) credential ( ) ( * credentials . User , error ) {
2020-08-07 12:29:53 +00:00
if sc . userCredential == nil {
2023-04-12 16:43:38 +00:00
uc , err := credentials . Read ( sc . c . String ( credentials . OriginCertFlag ) , sc . log )
2020-08-07 12:29:53 +00:00
if err != nil {
2023-04-12 16:43:38 +00:00
return nil , err
2020-08-07 12:29:53 +00:00
}
2023-04-12 16:43:38 +00:00
sc . userCredential = uc
2020-08-07 12:29:53 +00:00
}
return sc . userCredential , nil
}
2020-11-23 21:36:16 +00:00
func ( sc * subcommandContext ) readTunnelCredentials ( credFinder CredFinder ) ( connection . Credentials , error ) {
filePath , err := credFinder . Path ( )
2020-08-07 12:29:53 +00:00
if err != nil {
2020-11-23 21:36:16 +00:00
return connection . Credentials { } , err
2020-08-07 12:29:53 +00:00
}
2020-11-23 21:36:16 +00:00
body , err := sc . fs . readFile ( filePath )
2020-08-07 12:29:53 +00:00
if err != nil {
2020-11-23 21:36:16 +00:00
return connection . Credentials { } , errors . Wrapf ( err , "couldn't read tunnel credentials from %v" , filePath )
2020-08-07 12:29:53 +00:00
}
2020-11-23 21:36:16 +00:00
var credentials connection . Credentials
if err = json . Unmarshal ( body , & credentials ) ; err != nil {
2020-11-11 18:17:14 +00:00
if strings . HasSuffix ( filePath , ".pem" ) {
2020-11-23 21:36:16 +00:00
return connection . Credentials { } , fmt . Errorf ( "The tunnel credentials file should be .json but you gave a .pem. " +
"The tunnel credentials file was originally created by `cloudflared tunnel create`. " +
"You may have accidentally used the filepath to cert.pem, which is generated by `cloudflared tunnel " +
"login`." )
2020-11-11 18:17:14 +00:00
}
2020-11-23 21:36:16 +00:00
return connection . Credentials { } , errInvalidJSONCredential { path : filePath , err : err }
2020-08-07 12:29:53 +00:00
}
2020-11-23 21:36:16 +00:00
return credentials , nil
2020-08-07 12:29:53 +00:00
}
2021-12-27 14:56:50 +00:00
func ( sc * subcommandContext ) create ( name string , credentialsFilePath string , secret string ) ( * cfapi . Tunnel , error ) {
2020-08-07 12:29:53 +00:00
client , err := sc . client ( )
if err != nil {
2021-09-28 07:39:40 +00:00
return nil , errors . Wrap ( err , "couldn't create client to talk to Cloudflare Tunnel backend" )
2020-08-07 12:29:53 +00:00
}
2021-09-29 23:56:38 +00:00
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" )
}
2020-08-07 12:29:53 +00:00
}
tunnel , err := client . CreateTunnel ( name , tunnelSecret )
if err != nil {
2020-08-18 21:54:05 +00:00
return nil , errors . Wrap ( err , "Create Tunnel API call failed" )
2020-08-07 12:29:53 +00:00
}
credential , err := sc . credential ( )
if err != nil {
return nil , err
}
2020-11-23 21:36:16 +00:00
tunnelCredentials := connection . Credentials {
2023-04-12 16:43:38 +00:00
AccountTag : credential . AccountID ( ) ,
2020-11-23 21:36:16 +00:00
TunnelSecret : tunnelSecret ,
TunnelID : tunnel . ID ,
}
2021-03-24 03:32:40 +00:00
usedCertPath := false
if credentialsFilePath == "" {
2023-04-12 16:43:38 +00:00
originCertDir := filepath . Dir ( credential . CertPath ( ) )
2021-03-24 03:32:40 +00:00
credentialsFilePath , err = tunnelFilePath ( tunnelCredentials . TunnelID , originCertDir )
if err != nil {
return nil , err
}
usedCertPath = true
}
writeFileErr := writeTunnelCredentials ( credentialsFilePath , & tunnelCredentials )
2020-11-25 06:55:13 +00:00
if writeFileErr != nil {
2020-08-07 12:29:53 +00:00
var errorLines [ ] string
2021-03-24 03:32:40 +00:00
errorLines = append ( errorLines , fmt . Sprintf ( "Your tunnel '%v' was created with ID %v. However, cloudflared couldn't write tunnel credentials to %s." , tunnel . Name , tunnel . ID , credentialsFilePath ) )
2020-08-07 12:29:53 +00:00
errorLines = append ( errorLines , fmt . Sprintf ( "The file-writing error is: %v" , writeFileErr ) )
2023-09-20 10:10:50 +00:00
if deleteErr := client . DeleteTunnel ( tunnel . ID , true ) ; deleteErr != nil {
2020-08-07 12:29:53 +00:00
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 {
2021-03-24 03:32:40 +00:00
errorLines = append ( errorLines , fmt . Sprintf ( "The tunnel was deleted, because the tunnel can't be run without the credentials file" ) )
2020-08-07 12:29:53 +00:00
}
errorMsg := strings . Join ( errorLines , "\n" )
return nil , errors . New ( errorMsg )
}
if outputFormat := sc . c . String ( outputFormatFlag . Name ) ; outputFormat != "" {
return nil , renderOutput ( outputFormat , & tunnel )
}
2021-03-24 03:32:40 +00:00
fmt . Printf ( "Tunnel credentials written to %v." , credentialsFilePath )
if usedCertPath {
fmt . Print ( " cloudflared chose this file based on where your origin certificate was found." )
}
fmt . Println ( " Keep this file secret. To revoke these credentials, delete the tunnel." )
fmt . Printf ( "\nCreated tunnel %s with id %s\n" , tunnel . Name , tunnel . ID )
2022-02-21 11:49:13 +00:00
return & tunnel . Tunnel , nil
2020-08-07 12:29:53 +00:00
}
2021-12-27 14:56:50 +00:00
func ( sc * subcommandContext ) list ( filter * cfapi . TunnelFilter ) ( [ ] * cfapi . Tunnel , error ) {
2020-08-07 12:29:53 +00:00
client , err := sc . client ( )
if err != nil {
return nil , err
}
return client . ListTunnels ( filter )
}
func ( sc * subcommandContext ) delete ( tunnelIDs [ ] uuid . UUID ) error {
forceFlagSet := sc . c . Bool ( "force" )
client , err := sc . client ( )
if err != nil {
return err
}
for _ , id := range tunnelIDs {
tunnel , err := client . GetTunnel ( id )
if err != nil {
2021-10-14 13:50:07 +00:00
return errors . Wrapf ( err , "Can't get tunnel information. Please check tunnel id: %s" , id )
2020-08-07 12:29:53 +00:00
}
// Check if tunnel DeletedAt field has already been set
if ! tunnel . DeletedAt . IsZero ( ) {
return fmt . Errorf ( "Tunnel %s has already been deleted" , tunnel . ID )
}
2023-09-20 10:10:50 +00:00
if err := client . DeleteTunnel ( tunnel . ID , forceFlagSet ) ; err != nil {
2020-08-07 12:29:53 +00:00
return errors . Wrapf ( err , "Error deleting tunnel %s" , tunnel . ID )
}
2020-11-23 21:36:16 +00:00
credFinder := sc . credentialFinder ( id )
2020-12-04 11:06:13 +00:00
if tunnelCredentialsPath , err := credFinder . Path ( ) ; err == nil {
if err = os . Remove ( tunnelCredentialsPath ) ; err != nil {
2020-11-25 06:55:13 +00:00
sc . log . Info ( ) . Msgf ( "Tunnel %v was deleted, but we could not remove its credentials file %s: %s. Consider deleting this file manually." , id , tunnelCredentialsPath , err )
2020-12-04 11:06:13 +00:00
}
2020-08-07 12:29:53 +00:00
}
}
return nil
}
2020-11-23 21:36:16 +00:00
// findCredentials will choose the right way to find the credentials file, find it,
// and add the TunnelID into any old credentials (generated before TUN-3581 added the `TunnelID`
// field to credentials files)
func ( sc * subcommandContext ) findCredentials ( tunnelID uuid . UUID ) ( connection . Credentials , error ) {
2021-09-03 15:01:45 +00:00
var credentials connection . Credentials
var err error
if credentialsContents := sc . c . String ( CredContentsFlag ) ; credentialsContents != "" {
if err = json . Unmarshal ( [ ] byte ( credentialsContents ) , & credentials ) ; err != nil {
err = errInvalidJSONCredential { path : "TUNNEL_CRED_CONTENTS" , err : err }
}
} else {
credFinder := sc . credentialFinder ( tunnelID )
credentials , err = sc . readTunnelCredentials ( credFinder )
}
2020-11-23 21:36:16 +00:00
// This line ensures backwards compatibility with credentials files generated before
// TUN-3581. Those old credentials files don't have a TunnelID field, so we enrich the struct
// with the ID, which we have already resolved from the user input.
credentials . TunnelID = tunnelID
return credentials , err
}
2020-08-07 12:29:53 +00:00
func ( sc * subcommandContext ) run ( tunnelID uuid . UUID ) error {
2020-11-23 21:36:16 +00:00
credentials , err := sc . findCredentials ( tunnelID )
2020-08-07 12:29:53 +00:00
if err != nil {
2020-11-10 18:27:52 +00:00
if e , ok := err . ( errInvalidJSONCredential ) ; ok {
2020-11-25 06:55:13 +00:00
sc . log . Error ( ) . Msgf ( "The credentials file at %s contained invalid JSON. This is probably caused by passing the wrong filepath. Reminder: the credentials file is a .json file created via `cloudflared tunnel create`." , e . path )
sc . log . Error ( ) . Msgf ( "Invalid JSON when parsing credentials file: %s" , e . err . Error ( ) )
2020-11-10 18:27:52 +00:00
}
2020-08-07 12:29:53 +00:00
return err
}
2020-11-25 06:55:13 +00:00
2022-02-21 11:49:13 +00:00
return sc . runWithCredentials ( credentials )
}
func ( sc * subcommandContext ) runWithCredentials ( credentials connection . Credentials ) error {
sc . log . Info ( ) . Str ( LogFieldTunnelID , credentials . TunnelID . String ( ) ) . Msg ( "Starting tunnel" )
2020-09-01 16:06:00 +00:00
return StartServer (
sc . c ,
2021-12-27 19:05:14 +00:00
buildInfo ,
2024-08-26 06:07:39 +00:00
& connection . TunnelProperties { Credentials : credentials , QuickTunnelUrl : credentials . Hostname } ,
2020-11-25 06:55:13 +00:00
sc . log ,
2020-09-01 16:06:00 +00:00
)
2020-08-07 12:29:53 +00:00
}
func ( sc * subcommandContext ) cleanupConnections ( tunnelIDs [ ] uuid . UUID ) error {
2021-12-27 14:56:50 +00:00
params := cfapi . NewCleanupParams ( )
2021-03-19 23:26:51 +00:00
extraLog := ""
if connector := sc . c . String ( "connector-id" ) ; connector != "" {
connectorID , err := uuid . Parse ( connector )
if err != nil {
return errors . Wrapf ( err , "%s is not a valid client ID (must be a UUID)" , connector )
}
params . ForClient ( connectorID )
extraLog = fmt . Sprintf ( " for connector-id %s" , connectorID . String ( ) )
}
2020-08-07 12:29:53 +00:00
client , err := sc . client ( )
if err != nil {
return err
}
for _ , tunnelID := range tunnelIDs {
2021-03-19 23:26:51 +00:00
sc . log . Info ( ) . Msgf ( "Cleanup connection for tunnel %s%s" , tunnelID , extraLog )
if err := client . CleanupConnections ( tunnelID , params ) ; err != nil {
2020-11-25 06:55:13 +00:00
sc . log . Error ( ) . Msgf ( "Error cleaning up connections for tunnel %v, error :%v" , tunnelID , err )
2020-08-07 12:29:53 +00:00
}
}
return nil
}
2022-03-22 12:46:07 +00:00
func ( sc * subcommandContext ) getTunnelTokenCredentials ( tunnelID uuid . UUID ) ( * connection . TunnelToken , error ) {
client , err := sc . client ( )
if err != nil {
return nil , err
}
token , err := client . GetTunnelToken ( tunnelID )
if err != nil {
sc . log . Err ( err ) . Msgf ( "Could not get the Token for the given Tunnel %v" , tunnelID )
return nil , err
}
return ParseToken ( token )
}
2021-12-27 14:56:50 +00:00
func ( sc * subcommandContext ) route ( tunnelID uuid . UUID , r cfapi . HostnameRoute ) ( cfapi . HostnameRouteResult , error ) {
2020-08-07 12:29:53 +00:00
client , err := sc . client ( )
if err != nil {
2020-09-17 20:19:47 +00:00
return nil , err
2020-08-07 12:29:53 +00:00
}
2020-09-17 20:19:47 +00:00
return client . RouteTunnel ( tunnelID , r )
2020-08-07 12:29:53 +00:00
}
2020-11-23 21:36:16 +00:00
// Query Tunnelstore to find the active tunnel with the given name.
2021-12-27 14:56:50 +00:00
func ( sc * subcommandContext ) tunnelActive ( name string ) ( * cfapi . Tunnel , bool , error ) {
filter := cfapi . NewTunnelFilter ( )
2020-08-07 12:29:53 +00:00
filter . NoDeleted ( )
filter . ByName ( name )
tunnels , err := sc . list ( filter )
if err != nil {
return nil , false , err
}
if len ( tunnels ) == 0 {
return nil , false , nil
}
// There should only be 1 active tunnel for a given name
return tunnels [ 0 ] , true , nil
}
2020-08-18 21:54:05 +00:00
// findID parses the input. If it's a UUID, return the UUID.
// Otherwise, assume it's a name, and look up the ID of that tunnel.
func ( sc * subcommandContext ) findID ( input string ) ( uuid . UUID , error ) {
if u , err := uuid . Parse ( input ) ; err == nil {
return u , nil
}
2020-11-23 21:36:16 +00:00
// Look up name in the credentials file.
credFinder := newStaticPath ( sc . c . String ( CredFileFlag ) , sc . fs )
if credentials , err := sc . readTunnelCredentials ( credFinder ) ; err == nil {
2022-02-21 14:51:43 +00:00
if credentials . TunnelID != uuid . Nil {
2020-11-23 21:36:16 +00:00
return credentials . TunnelID , nil
}
}
// Fall back to querying Tunnelstore.
2020-08-18 21:54:05 +00:00
if tunnel , found , err := sc . tunnelActive ( input ) ; err != nil {
return uuid . Nil , err
} else if found {
return tunnel . ID , nil
}
return uuid . Nil , fmt . Errorf ( "%s is neither the ID nor the name of any of your tunnels" , input )
}
// findIDs is just like mapping `findID` over a slice, but it only uses
2021-10-19 14:01:23 +00:00
// one Tunnelstore API call per non-UUID input provided.
2020-08-18 21:54:05 +00:00
func ( sc * subcommandContext ) findIDs ( inputs [ ] string ) ( [ ] uuid . UUID , error ) {
2021-10-19 14:01:23 +00:00
uuids , names := splitUuids ( inputs )
2020-08-18 21:54:05 +00:00
2021-10-19 14:01:23 +00:00
for _ , name := range names {
2021-12-27 14:56:50 +00:00
filter := cfapi . NewTunnelFilter ( )
2021-10-19 14:01:23 +00:00
filter . NoDeleted ( )
filter . ByName ( name )
2021-03-15 18:30:17 +00:00
2021-10-19 14:01:23 +00:00
tunnels , err := sc . list ( filter )
if err != nil {
return nil , err
}
2021-10-18 09:32:33 +00:00
2021-10-19 14:01:23 +00:00
if len ( tunnels ) != 1 {
return nil , fmt . Errorf ( "there should only be 1 non-deleted Tunnel named %s" , name )
}
2020-08-18 21:54:05 +00:00
2021-10-19 14:01:23 +00:00
uuids = append ( uuids , tunnels [ 0 ] . ID )
2020-08-18 21:54:05 +00:00
}
2021-10-19 14:01:23 +00:00
return uuids , nil
2021-03-15 18:30:17 +00:00
}
2021-10-19 14:01:23 +00:00
func splitUuids ( inputs [ ] string ) ( [ ] uuid . UUID , [ ] string ) {
uuids := make ( [ ] uuid . UUID , 0 )
names := make ( [ ] string , 0 )
for _ , input := range inputs {
id , err := uuid . Parse ( input )
if err != nil {
names = append ( names , input )
2020-08-18 21:54:05 +00:00
} else {
2021-10-19 14:01:23 +00:00
uuids = append ( uuids , id )
2020-08-18 21:54:05 +00:00
}
}
2021-10-19 14:01:23 +00:00
return uuids , names
2020-08-18 21:54:05 +00:00
}