2018-10-08 19:20:28 +00:00
package tunnel
2018-05-01 23:45:06 +00:00
import (
2019-02-14 10:40:54 +00:00
"context"
2018-05-01 23:45:06 +00:00
"crypto/tls"
"fmt"
"io/ioutil"
"net"
"net/http"
"os"
"path/filepath"
"strings"
"time"
2019-06-17 21:18:47 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
2018-10-08 19:20:28 +00:00
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
2020-02-06 00:55:26 +00:00
"github.com/cloudflare/cloudflared/edgediscovery"
2020-04-29 20:51:32 +00:00
"github.com/cloudflare/cloudflared/logger"
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"
2019-03-28 17:58:23 +00:00
"github.com/google/uuid"
2018-05-01 23:45:06 +00:00
"github.com/mitchellh/go-homedir"
"github.com/pkg/errors"
2019-03-28 17:58:23 +00:00
"golang.org/x/crypto/ssh/terminal"
"gopkg.in/urfave/cli.v2"
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/"
2018-05-01 23:45:06 +00:00
)
2018-10-08 19:20:28 +00:00
// returns the first path that contains a cert.pem file. If none of the DefaultConfigDirs
// contains a cert.pem file, return empty string
2018-05-01 23:45:06 +00:00
func findDefaultOriginCertPath ( ) string {
2018-10-08 19:20:28 +00:00
for _ , defaultConfigDir := range config . DefaultConfigDirs {
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-04-29 20:51:32 +00:00
func generateRandomClientID ( logger logger . Service ) ( string , error ) {
2019-03-28 17:58:23 +00:00
u , err := uuid . NewRandom ( )
if err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "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-04-29 20:51:32 +00:00
func logClientOptions ( c * cli . Context , logger logger . Service ) {
2018-05-01 23:45:06 +00:00
flags := make ( map [ string ] interface { } )
for _ , flag := range c . LocalFlagNames ( ) {
flags [ flag ] = c . Generic ( flag )
}
2019-03-08 08:36:24 +00:00
sliceFlags := [ ] string { "header" , "tag" , "proxy-dns-upstream" , "upstream" , "edge" }
for _ , sliceFlag := range sliceFlags {
if len ( c . StringSlice ( sliceFlag ) ) > 0 {
flags [ sliceFlag ] = strings . Join ( c . StringSlice ( sliceFlag ) , ", " )
}
}
2018-05-01 23:45:06 +00:00
if len ( flags ) > 0 {
2020-04-29 20:51:32 +00:00
logger . Infof ( "Environment variables %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 {
logger . Infof ( "Environmental variables %v" , envs )
}
}
func dnsProxyStandAlone ( c * cli . Context ) bool {
return c . IsSet ( "proxy-dns" ) && ( ! c . IsSet ( "hostname" ) && ! c . IsSet ( "tag" ) && ! c . IsSet ( "hello-world" ) )
}
2020-04-29 20:51:32 +00:00
func findOriginCert ( c * cli . Context , logger logger . Service ) ( string , error ) {
2020-05-21 20:36:49 +00:00
originCertPath := c . String ( "origincert" )
if originCertPath == "" {
2020-04-29 20:51:32 +00:00
logger . Infof ( "Cannot determine default origin certificate path. No file %s in %v" , config . DefaultCredentialFile , config . DefaultConfigDirs )
2018-05-01 23:45:06 +00:00
if isRunningFromTerminal ( ) {
logger . Errorf ( "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-05-21 20:36:49 +00:00
return "" , fmt . Errorf ( "Client didn't specify origincert path when running from terminal" )
2018-05-01 23:45:06 +00:00
} else {
logger . Errorf ( "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-05-21 20:36:49 +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-04-29 20:51:32 +00:00
logger . Errorf ( "Cannot resolve path %s: %s" , originCertPath , err )
2020-05-21 20:36:49 +00:00
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 {
2020-05-21 20:36:49 +00:00
logger . Errorf ( "Cannot check if origin cert exists at path %s" , originCertPath )
return "" , fmt . Errorf ( "Cannot check if origin cert exists at path %s" , originCertPath )
2018-05-01 23:45:06 +00:00
}
if ! ok {
logger . Errorf ( ` Cannot find a valid certificate for your origin at the path :
% 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-05-21 20:36:49 +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-04-29 20:51:32 +00:00
func readOriginCert ( originCertPath string , logger logger . Service ) ( [ ] byte , error ) {
2020-05-21 20:36:49 +00:00
logger . Debugf ( "Reading origin cert from %s" , originCertPath )
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-04-29 20:51:32 +00:00
logger . Errorf ( "Cannot read %s to load origin certificate: %s" , originCertPath , err )
2018-05-01 23:45:06 +00:00
return nil , fmt . Errorf ( "Cannot read %s to load origin certificate" , originCertPath )
}
return originCert , nil
}
2020-04-29 20:51:32 +00:00
func getOriginCert ( c * cli . Context , logger logger . Service ) ( [ ] byte , error ) {
if originCertPath , err := findOriginCert ( c , logger ) ; err != nil {
2020-05-21 20:36:49 +00:00
return nil , err
} else {
2020-04-29 20:51:32 +00:00
return readOriginCert ( originCertPath , logger )
2020-05-21 20:36:49 +00:00
}
}
2019-02-28 22:56:01 +00:00
func prepareTunnelConfig (
c * cli . Context ,
2019-06-17 21:18:47 +00:00
buildInfo * buildinfo . BuildInfo ,
2020-04-29 20:51:32 +00:00
version string ,
logger logger . Service ,
transportLogger logger . Service ,
2019-02-28 22:56:01 +00:00
) ( * origin . TunnelConfig , error ) {
2018-05-01 23:45:06 +00:00
hostname , err := validation . ValidateHostname ( c . String ( "hostname" ) )
if err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "Invalid hostname: %s" , err )
2018-05-01 23:45:06 +00:00
return nil , errors . Wrap ( err , "Invalid hostname" )
}
2019-03-22 17:00:57 +00:00
isFreeTunnel := hostname == ""
2018-05-01 23:45:06 +00:00
clientID := c . String ( "id" )
if ! c . IsSet ( "id" ) {
2019-03-28 17:58:23 +00:00
clientID , err = generateRandomClientID ( logger )
if err != nil {
return nil , err
}
2018-05-01 23:45:06 +00:00
}
tags , err := NewTagSliceFromCLI ( c . StringSlice ( "tag" ) )
if err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "Tag parse failure: %s" , err )
2018-05-01 23:45:06 +00:00
return nil , errors . Wrap ( err , "Tag parse failure" )
}
tags = append ( tags , tunnelpogs . Tag { Name : "ID" , Value : clientID } )
2018-10-19 20:44:35 +00:00
originURL , err := config . ValidateUrl ( c )
2018-05-01 23:45:06 +00:00
if err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "Error validating origin URL: %s" , err )
2018-10-08 19:20:28 +00:00
return nil , errors . Wrap ( err , "Error validating origin URL" )
2018-05-01 23:45:06 +00:00
}
2019-03-22 17:00:57 +00:00
var originCert [ ] byte
if ! isFreeTunnel {
2020-04-29 20:51:32 +00:00
originCert , err = getOriginCert ( c , logger )
2019-03-22 17:00:57 +00:00
if err != nil {
return nil , errors . Wrap ( err , "Error getting origin cert" )
}
2018-05-01 23:45:06 +00:00
}
2019-05-28 20:53:35 +00:00
originCertPool , err := tlsconfig . LoadOriginCA ( c , logger )
2018-05-01 23:45:06 +00:00
if err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "Error loading cert pool: %s" , err )
2018-05-01 23:45:06 +00:00
return nil , errors . Wrap ( err , "Error loading cert pool" )
}
tunnelMetrics := origin . NewTunnelMetrics ( )
httpTransport := & http . Transport {
2019-02-14 10:40:54 +00:00
Proxy : http . ProxyFromEnvironment ,
2018-05-01 23:45:06 +00:00
MaxIdleConns : c . Int ( "proxy-keepalive-connections" ) ,
2019-12-17 01:02:28 +00:00
MaxIdleConnsPerHost : c . Int ( "proxy-keepalive-connections" ) ,
2018-05-01 23:45:06 +00:00
IdleConnTimeout : c . Duration ( "proxy-keepalive-timeout" ) ,
TLSHandshakeTimeout : c . Duration ( "proxy-tls-timeout" ) ,
ExpectContinueTimeout : 1 * time . Second ,
TLSClientConfig : & tls . Config { RootCAs : originCertPool , InsecureSkipVerify : c . IsSet ( "no-tls-verify" ) } ,
}
2019-11-22 18:07:14 +00:00
dialer := & net . Dialer {
2019-02-14 10:40:54 +00:00
Timeout : c . Duration ( "proxy-connect-timeout" ) ,
KeepAlive : c . Duration ( "proxy-tcp-keepalive" ) ,
2019-11-22 18:07:14 +00:00
}
if c . Bool ( "proxy-no-happy-eyeballs" ) {
dialer . FallbackDelay = - 1 // As of Golang 1.12, a negative delay disables "happy eyeballs"
}
dialContext := dialer . DialContext
2019-02-14 10:40:54 +00:00
if c . IsSet ( "unix-socket" ) {
unixSocket , err := config . ValidateUnixSocket ( c )
if err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "Error validating --unix-socket: %s" , err )
2019-02-14 10:40:54 +00:00
return nil , errors . Wrap ( err , "Error validating --unix-socket" )
}
logger . Infof ( "Proxying tunnel requests to unix:%s" , unixSocket )
httpTransport . DialContext = func ( ctx context . Context , _ , _ string ) ( net . Conn , error ) {
// if --unix-socket specified, enforce network type "unix"
return dialContext ( ctx , "unix" , unixSocket )
}
} else {
logger . Infof ( "Proxying tunnel requests to %s" , originURL )
httpTransport . DialContext = dialContext
}
2018-05-01 23:45:06 +00:00
if ! c . IsSet ( "hello-world" ) && c . IsSet ( "origin-server-name" ) {
httpTransport . TLSClientConfig . ServerName = c . String ( "origin-server-name" )
}
2020-05-04 20:15:17 +00:00
// If tunnel running in bastion mode, a connection to origin will not exist until initiated by the client.
if ! c . IsSet ( bastionFlag ) {
if err = validation . ValidateHTTPService ( originURL , hostname , httpTransport ) ; err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "unable to connect to the origin: %s" , err )
2020-05-04 20:15:17 +00:00
}
2018-10-08 19:20:28 +00:00
}
2019-05-28 20:53:35 +00:00
toEdgeTLSConfig , err := tlsconfig . CreateTunnelConfig ( c )
2018-11-15 15:43:50 +00:00
if err != nil {
2020-04-29 20:51:32 +00:00
logger . Errorf ( "unable to create TLS config to connect with edge: %s" , err )
2018-11-15 15:43:50 +00:00
return nil , errors . Wrap ( err , "unable to create TLS config to connect with edge" )
}
2018-05-01 23:45:06 +00:00
return & origin . TunnelConfig {
2019-04-04 22:04:55 +00:00
BuildInfo : buildInfo ,
ClientID : clientID ,
ClientTlsConfig : httpTransport . TLSClientConfig ,
CompressionQuality : c . Uint64 ( "compression-quality" ) ,
EdgeAddrs : c . StringSlice ( "edge" ) ,
GracePeriod : c . Duration ( "grace-period" ) ,
HAConnections : c . Int ( "ha-connections" ) ,
HTTPTransport : httpTransport ,
HeartbeatInterval : c . Duration ( "heartbeat-interval" ) ,
Hostname : hostname ,
2019-08-30 03:55:54 +00:00
HTTPHostHeader : c . String ( "http-host-header" ) ,
2019-04-04 22:04:55 +00:00
IncidentLookup : origin . NewIncidentLookup ( ) ,
IsAutoupdated : c . Bool ( "is-autoupdated" ) ,
IsFreeTunnel : isFreeTunnel ,
LBPool : c . String ( "lb-pool" ) ,
Logger : logger ,
2020-04-29 20:51:32 +00:00
TransportLogger : transportLogger ,
2019-04-04 22:04:55 +00:00
MaxHeartbeats : c . Uint64 ( "heartbeat-count" ) ,
Metrics : tunnelMetrics ,
MetricsUpdateFreq : c . Duration ( "metrics-update-freq" ) ,
NoChunkedEncoding : c . Bool ( "no-chunked-encoding" ) ,
OriginCert : originCert ,
OriginUrl : originURL ,
ReportedVersion : version ,
Retries : c . Uint ( "retries" ) ,
RunFromTerminal : isRunningFromTerminal ( ) ,
Tags : tags ,
TlsConfig : toEdgeTLSConfig ,
UseDeclarativeTunnel : c . Bool ( "use-declarative-tunnels" ) ,
2019-12-04 17:22:08 +00:00
UseReconnectToken : c . Bool ( "use-reconnect-token" ) ,
2020-03-06 20:48:16 +00:00
UseQuickReconnects : c . Bool ( "use-quick-reconnects" ) ,
2018-05-01 23:45:06 +00:00
} , nil
}
2020-04-29 20:51:32 +00:00
func serviceDiscoverer ( c * cli . Context , logger logger . Service ) ( * edgediscovery . Edge , error ) {
2019-06-18 16:47:29 +00:00
// If --edge is specfied, resolve edge server addresses
if len ( c . StringSlice ( "edge" ) ) > 0 {
2020-02-06 00:55:26 +00:00
return edgediscovery . StaticEdge ( logger , c . StringSlice ( "edge" ) )
2019-06-18 16:47:29 +00:00
}
// Otherwise lookup edge server addresses through service discovery
2020-02-06 00:55:26 +00:00
return edgediscovery . ResolveEdge ( logger )
2019-06-18 16:47:29 +00:00
}
2018-10-08 19:20:28 +00:00
func isRunningFromTerminal ( ) bool {
return terminal . IsTerminal ( int ( os . Stdout . Fd ( ) ) )
}