2018-10-08 19:20:28 +00:00
package tunnel
2018-05-01 23:45:06 +00:00
import (
2020-11-02 11:21:34 +00:00
"crypto/tls"
2018-05-01 23:45:06 +00:00
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
2021-09-21 10:02:59 +00:00
"time"
2018-05-01 23:45:06 +00:00
2021-03-23 14:30:43 +00:00
"github.com/google/uuid"
homedir "github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
"github.com/rs/zerolog"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/ssh/terminal"
2021-12-27 19:05:14 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
2021-03-08 16:46:23 +00:00
"github.com/cloudflare/cloudflared/config"
2020-10-08 10:12:26 +00:00
"github.com/cloudflare/cloudflared/connection"
2020-10-14 10:28:07 +00:00
"github.com/cloudflare/cloudflared/edgediscovery"
2020-10-08 10:12:26 +00:00
"github.com/cloudflare/cloudflared/h2mux"
2020-10-09 00:12:29 +00:00
"github.com/cloudflare/cloudflared/ingress"
2018-05-01 23:45:06 +00:00
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tlsconfig"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/validation"
)
2020-12-28 18:10:01 +00:00
const LogFieldOriginCertPath = "originCertPath"
2018-05-01 23:45:06 +00:00
var (
2018-10-08 19:20:28 +00:00
developerPortal = "https://developers.cloudflare.com/argo-tunnel"
quickStartUrl = developerPortal + "/quickstart/quickstart/"
serviceUrl = developerPortal + "/reference/service/"
argumentsUrl = developerPortal + "/reference/arguments/"
2020-12-28 18:10:01 +00:00
LogFieldHostname = "hostname"
2018-05-01 23:45:06 +00:00
)
2020-08-14 20:52:47 +00:00
// returns the first path that contains a cert.pem file. If none of the DefaultConfigSearchDirectories
2018-10-08 19:20:28 +00:00
// contains a cert.pem file, return empty string
2018-05-01 23:45:06 +00:00
func findDefaultOriginCertPath ( ) string {
2020-08-14 20:52:47 +00:00
for _ , defaultConfigDir := range config . DefaultConfigSearchDirectories ( ) {
2018-10-08 19:20:28 +00:00
originCertPath , _ := homedir . Expand ( filepath . Join ( defaultConfigDir , config . DefaultCredentialFile ) )
if ok , _ := config . FileExists ( originCertPath ) ; ok {
2018-05-01 23:45:06 +00:00
return originCertPath
}
}
return ""
}
2020-11-25 06:55:13 +00:00
func generateRandomClientID ( log * zerolog . Logger ) ( string , error ) {
2019-03-28 17:58:23 +00:00
u , err := uuid . NewRandom ( )
if err != nil {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( "couldn't create UUID for client ID %s" , err )
2019-03-28 17:58:23 +00:00
return "" , err
}
return u . String ( ) , nil
2018-05-01 23:45:06 +00:00
}
2020-11-25 06:55:13 +00:00
func logClientOptions ( c * cli . Context , log * zerolog . Logger ) {
2018-05-01 23:45:06 +00:00
flags := make ( map [ string ] interface { } )
2021-01-15 20:25:20 +00:00
for _ , flag := range c . FlagNames ( ) {
2018-05-01 23:45:06 +00:00
flags [ flag ] = c . Generic ( flag )
}
2019-03-08 08:36:24 +00:00
2018-05-01 23:45:06 +00:00
if len ( flags ) > 0 {
2021-01-15 20:25:20 +00:00
log . Info ( ) . Msgf ( "Settings: %v" , flags )
2018-05-01 23:45:06 +00:00
}
envs := make ( map [ string ] string )
// Find env variables for Argo Tunnel
for _ , env := range os . Environ ( ) {
// All Argo Tunnel env variables start with TUNNEL_
if strings . Contains ( env , "TUNNEL_" ) {
vars := strings . Split ( env , "=" )
if len ( vars ) == 2 {
envs [ vars [ 0 ] ] = vars [ 1 ]
}
}
}
if len ( envs ) > 0 {
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Environmental variables %v" , envs )
2018-05-01 23:45:06 +00:00
}
}
2021-04-05 10:09:38 +00:00
func dnsProxyStandAlone ( c * cli . Context , namedTunnel * connection . NamedTunnelConfig ) bool {
return c . IsSet ( "proxy-dns" ) && ( ! c . IsSet ( "hostname" ) && ! c . IsSet ( "tag" ) && ! c . IsSet ( "hello-world" ) && namedTunnel == nil )
2018-05-01 23:45:06 +00:00
}
2020-12-28 18:10:01 +00:00
func findOriginCert ( originCertPath string , log * zerolog . Logger ) ( string , error ) {
2020-05-21 20:36:49 +00:00
if originCertPath == "" {
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Cannot determine default origin certificate path. No file %s in %v" , config . DefaultCredentialFile , config . DefaultConfigSearchDirectories ( ) )
2018-05-01 23:45:06 +00:00
if isRunningFromTerminal ( ) {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( "You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information." , argumentsUrl )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "client didn't specify origincert path when running from terminal" )
2018-05-01 23:45:06 +00:00
} else {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( "You need to specify the origin certificate path by specifying the origincert option in the configuration file, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information." , serviceUrl )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "client didn't specify origincert path" )
2018-05-01 23:45:06 +00:00
}
}
2020-05-21 20:36:49 +00:00
var err error
originCertPath , err = homedir . Expand ( originCertPath )
2018-05-01 23:45:06 +00:00
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msgf ( "Cannot resolve origin certificate path" )
return "" , fmt . Errorf ( "cannot resolve path %s" , originCertPath )
2018-05-01 23:45:06 +00:00
}
2020-05-21 20:36:49 +00:00
// Check that the user has acquired a certificate using the login command
2018-10-08 19:20:28 +00:00
ok , err := config . FileExists ( originCertPath )
2018-05-01 23:45:06 +00:00
if err != nil {
2021-03-01 09:23:18 +00:00
log . Error ( ) . Err ( err ) . Msgf ( "Cannot check if origin cert exists at path %s" , originCertPath )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "cannot check if origin cert exists at path %s" , originCertPath )
2018-05-01 23:45:06 +00:00
}
if ! ok {
2020-11-25 06:55:13 +00:00
log . Error ( ) . Msgf ( ` Cannot find a valid certificate for your origin at the path :
2018-05-01 23:45:06 +00:00
% s
If the path above is wrong , specify the path with the - origincert option .
If you don ' t have a certificate signed by Cloudflare , run the command :
% s login
` , originCertPath , os . Args [ 0 ] )
2020-12-28 18:10:01 +00:00
return "" , fmt . Errorf ( "cannot find a valid certificate at the path %s" , originCertPath )
2018-05-01 23:45:06 +00:00
}
2020-05-21 20:36:49 +00:00
return originCertPath , nil
}
2020-12-28 18:10:01 +00:00
func readOriginCert ( originCertPath string ) ( [ ] byte , error ) {
2018-05-01 23:45:06 +00:00
// Easier to send the certificate as []byte via RPC than decoding it at this point
originCert , err := ioutil . ReadFile ( originCertPath )
if err != nil {
2020-12-28 18:10:01 +00:00
return nil , fmt . Errorf ( "cannot read %s to load origin certificate" , originCertPath )
2018-05-01 23:45:06 +00:00
}
return originCert , nil
}
2020-12-28 18:10:01 +00:00
func getOriginCert ( originCertPath string , log * zerolog . Logger ) ( [ ] byte , error ) {
if originCertPath , err := findOriginCert ( originCertPath , log ) ; err != nil {
2020-05-21 20:36:49 +00:00
return nil , err
} else {
2020-12-28 18:10:01 +00:00
return readOriginCert ( originCertPath )
2020-05-21 20:36:49 +00:00
}
}
2019-02-28 22:56:01 +00:00
func prepareTunnelConfig (
c * cli . Context ,
2021-12-27 19:05:14 +00:00
info * cliutil . BuildInfo ,
2021-02-03 18:32:54 +00:00
log , logTransport * zerolog . Logger ,
2021-01-14 22:33:36 +00:00
observer * connection . Observer ,
2020-10-08 10:12:26 +00:00
namedTunnel * connection . NamedTunnelConfig ,
2020-11-02 11:21:34 +00:00
) ( * origin . TunnelConfig , ingress . Ingress , error ) {
2020-09-11 22:02:34 +00:00
isNamedTunnel := namedTunnel != nil
2020-06-25 18:25:39 +00:00
2020-12-28 18:10:01 +00:00
configHostname := c . String ( "hostname" )
hostname , err := validation . ValidateHostname ( configHostname )
2018-05-01 23:45:06 +00:00
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Str ( LogFieldHostname , configHostname ) . Msg ( "Invalid hostname" )
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , errors . Wrap ( err , "Invalid hostname" )
2018-05-01 23:45:06 +00:00
}
clientID := c . String ( "id" )
if ! c . IsSet ( "id" ) {
2020-11-25 06:55:13 +00:00
clientID , err = generateRandomClientID ( log )
2019-03-28 17:58:23 +00:00
if err != nil {
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , err
2019-03-28 17:58:23 +00:00
}
2018-05-01 23:45:06 +00:00
}
tags , err := NewTagSliceFromCLI ( c . StringSlice ( "tag" ) )
if err != nil {
2020-12-28 18:10:01 +00:00
log . Err ( err ) . Msg ( "Tag parse failure" )
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , errors . Wrap ( err , "Tag parse failure" )
2018-05-01 23:45:06 +00:00
}
tags = append ( tags , tunnelpogs . Tag { Name : "ID" , Value : clientID } )
2020-10-08 10:12:26 +00:00
var (
ingressRules ingress . Ingress
classicTunnel * connection . ClassicTunnelConfig
)
2021-01-17 20:22:53 +00:00
cfg := config . GetConfiguration ( )
2020-10-08 10:12:26 +00:00
if isNamedTunnel {
2020-10-09 00:12:29 +00:00
clientUUID , err := uuid . NewRandom ( )
if err != nil {
2021-03-15 18:30:17 +00:00
return nil , ingress . Ingress { } , errors . Wrap ( err , "can't generate connector UUID" )
2020-10-09 00:12:29 +00:00
}
2021-03-15 18:30:17 +00:00
log . Info ( ) . Msgf ( "Generated Connector ID: %s" , clientUUID )
2021-03-02 22:16:38 +00:00
features := append ( c . StringSlice ( "features" ) , origin . FeatureSerializedHeaders )
2020-10-09 00:12:29 +00:00
namedTunnel . Client = tunnelpogs . ClientInfo {
ClientID : clientUUID [ : ] ,
2021-03-02 22:16:38 +00:00
Features : dedup ( features ) ,
2021-12-27 19:05:14 +00:00
Version : info . Version ( ) ,
Arch : info . OSArch ( ) ,
2020-10-09 00:12:29 +00:00
}
2021-01-17 20:22:53 +00:00
ingressRules , err = ingress . ParseIngress ( cfg )
2020-10-12 17:54:15 +00:00
if err != nil && err != ingress . ErrNoIngressRules {
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , err
2020-10-09 00:12:29 +00:00
}
2020-10-15 17:41:50 +00:00
if ! ingressRules . IsEmpty ( ) && c . IsSet ( "url" ) {
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , ingress . ErrURLIncompatibleWithIngress
2020-10-09 00:12:29 +00:00
}
2020-10-08 10:12:26 +00:00
} else {
2021-08-26 17:15:36 +00:00
originCertPath := c . String ( "origincert" )
originCertLog := log . With ( ) .
Str ( LogFieldOriginCertPath , originCertPath ) .
Logger ( )
originCert , err := getOriginCert ( originCertPath , & originCertLog )
if err != nil {
return nil , ingress . Ingress { } , errors . Wrap ( err , "Error getting origin cert" )
}
2020-10-08 10:12:26 +00:00
classicTunnel = & connection . ClassicTunnelConfig {
Hostname : hostname ,
OriginCert : originCert ,
// turn off use of reconnect token and auth refresh when using named tunnels
UseReconnectToken : ! isNamedTunnel && c . Bool ( "use-reconnect-token" ) ,
}
2020-10-09 00:12:29 +00:00
}
2020-10-15 21:41:03 +00:00
// Convert single-origin configuration into multi-origin configuration.
2020-10-15 17:41:50 +00:00
if ingressRules . IsEmpty ( ) {
2020-11-25 06:55:13 +00:00
ingressRules , err = ingress . NewSingleOrigin ( c , ! isNamedTunnel )
2020-10-09 00:12:29 +00:00
if err != nil {
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , err
2020-05-04 20:15:17 +00:00
}
2018-10-08 19:20:28 +00:00
}
2021-01-21 15:23:18 +00:00
var warpRoutingService * ingress . WarpRoutingService
warpRoutingEnabled := isWarpRoutingEnabled ( cfg . WarpRouting , isNamedTunnel )
if warpRoutingEnabled {
warpRoutingService = ingress . NewWarpRoutingService ( )
2021-02-19 10:53:43 +00:00
log . Info ( ) . Msgf ( "Warp-routing is enabled" )
2021-01-21 15:23:18 +00:00
}
2021-10-11 10:31:05 +00:00
protocolSelector , err := connection . NewProtocolSelector ( c . String ( "protocol" ) , warpRoutingEnabled , namedTunnel , edgediscovery . ProtocolPercentage , origin . ResolveTTL , log )
2020-10-14 10:28:07 +00:00
if err != nil {
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , err
2020-10-14 10:28:07 +00:00
}
2020-11-25 06:55:13 +00:00
log . Info ( ) . Msgf ( "Initial protocol %s" , protocolSelector . Current ( ) )
2020-10-14 13:42:00 +00:00
edgeTLSConfigs := make ( map [ connection . Protocol ] * tls . Config , len ( connection . ProtocolList ) )
for _ , p := range connection . ProtocolList {
2021-08-17 14:30:02 +00:00
tlsSettings := p . TLSSettings ( )
if tlsSettings == nil {
return nil , ingress . Ingress { } , fmt . Errorf ( "%s has unknown TLS settings" , p )
}
edgeTLSConfig , err := tlsconfig . CreateTunnelConfig ( c , tlsSettings . ServerName )
2020-10-14 13:42:00 +00:00
if err != nil {
2020-11-02 11:21:34 +00:00
return nil , ingress . Ingress { } , errors . Wrap ( err , "unable to create TLS config to connect with edge" )
2020-10-14 13:42:00 +00:00
}
2021-08-21 16:31:28 +00:00
if len ( tlsSettings . NextProtos ) > 0 {
2021-08-17 14:30:02 +00:00
edgeTLSConfig . NextProtos = tlsSettings . NextProtos
}
2020-10-14 13:42:00 +00:00
edgeTLSConfigs [ p ] = edgeTLSConfig
2018-11-15 15:43:50 +00:00
}
2020-10-08 10:12:26 +00:00
2021-01-17 20:22:53 +00:00
originProxy := origin . NewOriginProxy ( ingressRules , warpRoutingService , tags , log )
2021-09-21 10:02:59 +00:00
gracePeriod , err := gracePeriod ( c )
if err != nil {
return nil , ingress . Ingress { } , err
}
2020-10-14 13:42:00 +00:00
connectionConfig := & connection . Config {
2020-12-09 21:46:53 +00:00
OriginProxy : originProxy ,
2021-09-21 10:02:59 +00:00
GracePeriod : gracePeriod ,
2020-10-08 10:12:26 +00:00
ReplaceExisting : c . Bool ( "force" ) ,
}
muxerConfig := & connection . MuxerConfig {
2021-02-03 18:32:54 +00:00
HeartbeatInterval : c . Duration ( "heartbeat-interval" ) ,
2021-01-14 13:08:55 +00:00
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
2021-02-03 18:32:54 +00:00
MaxHeartbeats : uint64 ( c . Int ( "heartbeat-count" ) ) ,
2021-01-14 13:08:55 +00:00
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
CompressionSetting : h2mux . CompressionSetting ( uint64 ( c . Int ( "compression-quality" ) ) ) ,
2020-06-25 18:25:39 +00:00
MetricsUpdateFreq : c . Duration ( "metrics-update-freq" ) ,
2020-10-08 10:12:26 +00:00
}
return & origin . TunnelConfig {
2020-10-14 13:42:00 +00:00
ConnectionConfig : connectionConfig ,
2021-12-27 19:05:14 +00:00
OSArch : info . OSArch ( ) ,
2020-10-08 10:12:26 +00:00
ClientID : clientID ,
EdgeAddrs : c . StringSlice ( "edge" ) ,
2021-08-28 15:39:00 +00:00
Region : c . String ( "region" ) ,
2020-10-08 10:12:26 +00:00
HAConnections : c . Int ( "ha-connections" ) ,
IncidentLookup : origin . NewIncidentLookup ( ) ,
IsAutoupdated : c . Bool ( "is-autoupdated" ) ,
LBPool : c . String ( "lb-pool" ) ,
2020-11-02 11:21:34 +00:00
Tags : tags ,
2020-11-25 06:55:13 +00:00
Log : log ,
2021-02-03 18:32:54 +00:00
LogTransport : logTransport ,
2021-01-14 22:33:36 +00:00
Observer : observer ,
2021-12-27 19:05:14 +00:00
ReportedVersion : info . Version ( ) ,
2021-01-14 13:08:55 +00:00
// Note TUN-3758 , we use Int because UInt is not supported with altsrc
Retries : uint ( c . Int ( "retries" ) ) ,
2020-10-08 10:12:26 +00:00
RunFromTerminal : isRunningFromTerminal ( ) ,
NamedTunnel : namedTunnel ,
ClassicTunnel : classicTunnel ,
MuxerConfig : muxerConfig ,
2020-10-14 13:42:00 +00:00
ProtocolSelector : protocolSelector ,
EdgeTLSConfigs : edgeTLSConfigs ,
2020-11-02 11:21:34 +00:00
} , ingressRules , nil
2018-05-01 23:45:06 +00:00
}
2021-09-21 10:02:59 +00:00
func gracePeriod ( c * cli . Context ) ( time . Duration , error ) {
period := c . Duration ( "grace-period" )
if period > connection . MaxGracePeriod {
return time . Duration ( 0 ) , fmt . Errorf ( "grace-period must be equal or less than %v" , connection . MaxGracePeriod )
}
return period , nil
}
2021-01-21 15:23:18 +00:00
func isWarpRoutingEnabled ( warpConfig config . WarpRoutingConfig , isNamedTunnel bool ) bool {
return warpConfig . Enabled && isNamedTunnel
2021-01-17 20:22:53 +00:00
}
2018-10-08 19:20:28 +00:00
func isRunningFromTerminal ( ) bool {
return terminal . IsTerminal ( int ( os . Stdout . Fd ( ) ) )
}
2021-03-02 22:16:38 +00:00
// Remove any duplicates from the slice
func dedup ( slice [ ] string ) [ ] string {
// Convert the slice into a set
set := make ( map [ string ] bool , 0 )
for _ , str := range slice {
set [ str ] = true
}
// Convert the set back into a slice
keys := make ( [ ] string , len ( set ) )
i := 0
for str := range set {
keys [ i ] = str
i ++
}
return keys
}