2018-03-22 15:24:52 +00:00
package main
import (
"crypto/tls"
"encoding/hex"
"fmt"
"io/ioutil"
"math/rand"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tlsconfig"
"github.com/cloudflare/cloudflared/tunneldns"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/validation"
"github.com/facebookgo/grace/gracenet"
"github.com/getsentry/raven-go"
"github.com/mitchellh/go-homedir"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh/terminal"
"gopkg.in/urfave/cli.v2"
"gopkg.in/urfave/cli.v2/altsrc"
"github.com/coreos/go-systemd/daemon"
"github.com/pkg/errors"
)
const (
sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b:3e8827f6f9f740738eb11138f7bebb68@sentry.io/189878"
credentialFile = "cert.pem"
quickStartUrl = "https://developers.cloudflare.com/argo-tunnel/quickstart/quickstart/"
noAutoupdateMessage = "cloudflared will not automatically update when run from the shell. To enable auto-updates, run cloudflared as a service: https://developers.cloudflare.com/argo-tunnel/reference/service/"
licenseUrl = "https://developers.cloudflare.com/argo-tunnel/licence/"
2018-04-02 20:43:11 +00:00
minDNSInitWait = time . Second * 15
minPingFreq = time . Second * 2
2018-03-22 15:24:52 +00:00
)
var listeners = gracenet . Net { }
var Version = "DEV"
var BuildTime = "unknown"
var Log * logrus . Logger
var defaultConfigFiles = [ ] string { "config.yml" , "config.yaml" }
2018-04-04 15:47:36 +00:00
// Launchd doesn't set root env variables, so there is default
2018-03-22 15:24:52 +00:00
// Windows default config dir was ~/cloudflare-warp in documentation; let's keep it compatible
2018-04-04 15:47:36 +00:00
var defaultConfigDirs = [ ] string { "~/.cloudflared" , "~/.cloudflare-warp" , "~/cloudflare-warp" , "/usr/local/etc/cloudflared" , "/etc/cloudflared" }
2018-03-22 15:24:52 +00:00
// Shutdown channel used by the app. When closed, app must terminate.
// May be closed by the Windows service runner.
var shutdownC chan struct { }
type BuildAndRuntimeInfo struct {
GoOS string ` json:"go_os" `
GoVersion string ` json:"go_version" `
GoArch string ` json:"go_arch" `
WarpVersion string ` json:"warp_version" `
WarpFlags map [ string ] interface { } ` json:"warp_flags" `
WarpEnvs map [ string ] string ` json:"warp_envs" `
}
func main ( ) {
metrics . RegisterBuildInfo ( BuildTime , Version )
raven . SetDSN ( sentryDSN )
raven . SetRelease ( Version )
shutdownC = make ( chan struct { } )
2018-04-04 15:47:36 +00:00
2018-03-22 15:24:52 +00:00
app := & cli . App { }
app . Name = "cloudflared"
app . Copyright = fmt . Sprintf ( ` ( c ) % d Cloudflare Inc .
Use is subject to the license agreement at % s ` , time . Now ( ) . Year ( ) , licenseUrl )
app . Usage = "Cloudflare reverse tunnelling proxy agent"
app . ArgsUsage = "origin-url"
app . Version = fmt . Sprintf ( "%s (built %s)" , Version , BuildTime )
app . Description = ` A reverse tunnel proxy agent that connects to Cloudflare ' s infrastructure .
Upon connecting , you are assigned a unique subdomain on cftunnel . com .
You need to specify a hostname on a zone you control .
A DNS record will be created to CNAME your hostname to the unique subdomain on cftunnel . com .
Requests made to Cloudflare ' s servers for your hostname will be proxied
through the tunnel to your local webserver . `
app . Flags = [ ] cli . Flag {
& cli . StringFlag {
Name : "config" ,
Usage : "Specifies a config file in YAML format." ,
Value : findDefaultConfigPath ( ) ,
} ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "autoupdate-freq" ,
Usage : "Autoupdate frequency. Default is 24h." ,
Value : time . Hour * 24 ,
} ) ,
altsrc . NewBoolFlag ( & cli . BoolFlag {
Name : "no-autoupdate" ,
Usage : "Disable periodic check for updates, restarting the server with the new version." ,
Value : false ,
} ) ,
altsrc . NewBoolFlag ( & cli . BoolFlag {
Name : "is-autoupdated" ,
Usage : "Signal the new process that Argo Tunnel client has been autoupdated" ,
Value : false ,
Hidden : true ,
} ) ,
altsrc . NewStringSliceFlag ( & cli . StringSliceFlag {
Name : "edge" ,
Usage : "Address of the Cloudflare tunnel server." ,
EnvVars : [ ] string { "TUNNEL_EDGE" } ,
Hidden : true ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "cacert" ,
Usage : "Certificate Authority authenticating the Cloudflare tunnel connection." ,
EnvVars : [ ] string { "TUNNEL_CACERT" } ,
Hidden : true ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "origincert" ,
Usage : "Path to the certificate generated for your origin when you run cloudflared login." ,
EnvVars : [ ] string { "TUNNEL_ORIGIN_CERT" } ,
Value : findDefaultOriginCertPath ( ) ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "url" ,
Value : "https://localhost:8080" ,
Usage : "Connect to the local webserver at `URL`." ,
EnvVars : [ ] string { "TUNNEL_URL" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "hostname" ,
Usage : "Set a hostname on a Cloudflare zone to route traffic through this tunnel." ,
EnvVars : [ ] string { "TUNNEL_HOSTNAME" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "origin-server-name" ,
Usage : "Hostname on the origin server certificate." ,
EnvVars : [ ] string { "TUNNEL_ORIGIN_SERVER_NAME" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "id" ,
Usage : "A unique identifier used to tie connections to this tunnel instance." ,
EnvVars : [ ] string { "TUNNEL_ID" } ,
Hidden : true ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "lb-pool" ,
Usage : "The name of a (new/existing) load balancing pool to add this origin to." ,
EnvVars : [ ] string { "TUNNEL_LB_POOL" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "api-key" ,
Usage : "This parameter has been deprecated since version 2017.10.1." ,
EnvVars : [ ] string { "TUNNEL_API_KEY" } ,
Hidden : true ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "api-email" ,
Usage : "This parameter has been deprecated since version 2017.10.1." ,
EnvVars : [ ] string { "TUNNEL_API_EMAIL" } ,
Hidden : true ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "api-ca-key" ,
Usage : "This parameter has been deprecated since version 2017.10.1." ,
EnvVars : [ ] string { "TUNNEL_API_CA_KEY" } ,
Hidden : true ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "metrics" ,
Value : "localhost:" ,
Usage : "Listen address for metrics reporting." ,
EnvVars : [ ] string { "TUNNEL_METRICS" } ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "metrics-update-freq" ,
Usage : "Frequency to update tunnel metrics" ,
Value : time . Second * 5 ,
EnvVars : [ ] string { "TUNNEL_METRICS_UPDATE_FREQ" } ,
} ) ,
altsrc . NewStringSliceFlag ( & cli . StringSliceFlag {
Name : "tag" ,
Usage : "Custom tags used to identify this tunnel, in format `KEY=VALUE`. Multiple tags may be specified" ,
EnvVars : [ ] string { "TUNNEL_TAG" } ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "heartbeat-interval" ,
Usage : "Minimum idle time before sending a heartbeat." ,
Value : time . Second * 5 ,
Hidden : true ,
} ) ,
altsrc . NewUint64Flag ( & cli . Uint64Flag {
Name : "heartbeat-count" ,
Usage : "Minimum number of unacked heartbeats to send before closing the connection." ,
Value : 5 ,
Hidden : true ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "loglevel" ,
Value : "info" ,
Usage : "Application logging level {panic, fatal, error, warn, info, debug}" ,
EnvVars : [ ] string { "TUNNEL_LOGLEVEL" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "proto-loglevel" ,
Value : "warn" ,
Usage : "Protocol logging level {panic, fatal, error, warn, info, debug}" ,
EnvVars : [ ] string { "TUNNEL_PROTO_LOGLEVEL" } ,
} ) ,
altsrc . NewUintFlag ( & cli . UintFlag {
Name : "retries" ,
Value : 5 ,
Usage : "Maximum number of retries for connection/protocol errors." ,
EnvVars : [ ] string { "TUNNEL_RETRIES" } ,
} ) ,
altsrc . NewBoolFlag ( & cli . BoolFlag {
Name : "hello-world" ,
Value : false ,
Usage : "Run Hello World Server" ,
EnvVars : [ ] string { "TUNNEL_HELLO_WORLD" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "pidfile" ,
Usage : "Write the application's PID to this file after first successful connection." ,
EnvVars : [ ] string { "TUNNEL_PIDFILE" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "logfile" ,
Usage : "Save application log to this file for reporting issues." ,
EnvVars : [ ] string { "TUNNEL_LOGFILE" } ,
} ) ,
altsrc . NewIntFlag ( & cli . IntFlag {
Name : "ha-connections" ,
Value : 4 ,
Hidden : true ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "proxy-connect-timeout" ,
Usage : "HTTP proxy timeout for establishing a new connection" ,
Value : time . Second * 30 ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "proxy-tls-timeout" ,
Usage : "HTTP proxy timeout for completing a TLS handshake" ,
Value : time . Second * 10 ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "proxy-tcp-keepalive" ,
Usage : "HTTP proxy TCP keepalive duration" ,
Value : time . Second * 30 ,
} ) ,
altsrc . NewBoolFlag ( & cli . BoolFlag {
Name : "proxy-no-happy-eyeballs" ,
Usage : "HTTP proxy should disable \"happy eyeballs\" for IPv4/v6 fallback" ,
} ) ,
altsrc . NewIntFlag ( & cli . IntFlag {
Name : "proxy-keepalive-connections" ,
Usage : "HTTP proxy maximum keepalive connection pool size" ,
Value : 100 ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "proxy-keepalive-timeout" ,
Usage : "HTTP proxy timeout for closing an idle connection" ,
Value : time . Second * 90 ,
} ) ,
altsrc . NewBoolFlag ( & cli . BoolFlag {
Name : "proxy-dns" ,
Usage : "Run a DNS over HTTPS proxy server." ,
EnvVars : [ ] string { "TUNNEL_DNS" } ,
} ) ,
altsrc . NewUintFlag ( & cli . UintFlag {
Name : "proxy-dns-port" ,
Value : 53 ,
Usage : "Listen on given port for the DNS over HTTPS proxy server." ,
EnvVars : [ ] string { "TUNNEL_DNS_PORT" } ,
} ) ,
altsrc . NewStringFlag ( & cli . StringFlag {
Name : "proxy-dns-address" ,
Usage : "Listen address for the DNS over HTTPS proxy server." ,
Value : "localhost" ,
EnvVars : [ ] string { "TUNNEL_DNS_ADDRESS" } ,
} ) ,
altsrc . NewStringSliceFlag ( & cli . StringSliceFlag {
Name : "proxy-dns-upstream" ,
Usage : "Upstream endpoint URL, you can specify multiple endpoints for redundancy." ,
2018-04-02 20:43:11 +00:00
Value : cli . NewStringSlice ( "https://cloudflare-dns.com/dns-query" ) ,
2018-03-22 15:24:52 +00:00
EnvVars : [ ] string { "TUNNEL_DNS_UPSTREAM" } ,
} ) ,
2018-04-02 20:43:11 +00:00
altsrc . NewBoolFlag ( & cli . BoolFlag {
Name : "skip-hostname-propagation-check" ,
Usage : "Flag to instruct cloudflared to skip checking whether DNS record for the hostname has been propagated." ,
EnvVars : [ ] string { "TUNNEL_SKIP_HOSTNAME_PROPAGATION_CHECK" } ,
} ) ,
altsrc . NewUintFlag ( & cli . UintFlag {
2018-04-04 15:47:36 +00:00
Name : "hostname-propagated-retries" ,
Usage : "How many pings to test whether send DNS record has been propagated before reregistering tunnel" ,
Value : 25 ,
2018-04-02 20:43:11 +00:00
EnvVars : [ ] string { "TUNNEL_HOSTNAME_PROPAGATED_RETRIES" } ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
2018-04-04 15:47:36 +00:00
Name : "init-wait-time" ,
Usage : "Initial waiting time to checking whether DNS record has propagated" ,
Value : minDNSInitWait ,
2018-04-02 20:43:11 +00:00
EnvVars : [ ] string { "TUNNEL_INIT_WAIT_TIME" } ,
2018-04-04 15:47:36 +00:00
Hidden : true ,
2018-04-02 20:43:11 +00:00
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
2018-04-04 15:47:36 +00:00
Name : "ping-freq" ,
Usage : "Ping frequency for checking DNS record has propagated" ,
Value : minPingFreq ,
2018-04-02 20:43:11 +00:00
EnvVars : [ ] string { "TUNNEL_PING_FREQ" } ,
2018-04-04 15:47:36 +00:00
Hidden : true ,
} ) ,
altsrc . NewDurationFlag ( & cli . DurationFlag {
Name : "grace-period" ,
Usage : "Duration to accpet new requests after cloudflared receives first SIGINT/SIGTERM. A second SIGINT/SIGTERM will force cloudflared to shutdown immediately." ,
Value : time . Second * 30 ,
EnvVars : [ ] string { "TUNNEL_GRACE_PERIOD" } ,
Hidden : true ,
2018-04-02 20:43:11 +00:00
} ) ,
2018-03-22 15:24:52 +00:00
}
app . Action = func ( c * cli . Context ) error {
raven . CapturePanic ( func ( ) { startServer ( c ) } , nil )
return nil
}
app . Before = func ( context * cli . Context ) error {
Log = logrus . New ( )
inputSource , err := findInputSourceContext ( context )
if err != nil {
Log . WithError ( err ) . Infof ( "Cannot load configuration from %s" , context . String ( "config" ) )
return err
} else if inputSource != nil {
err := altsrc . ApplyInputSourceValues ( context , inputSource , app . Flags )
if err != nil {
Log . WithError ( err ) . Infof ( "Cannot apply configuration from %s" , context . String ( "config" ) )
return err
}
Log . Infof ( "Applied configuration from %s" , context . String ( "config" ) )
}
return nil
}
app . Commands = [ ] * cli . Command {
{
Name : "update" ,
Action : update ,
Usage : "Update the agent if a new version exists" ,
ArgsUsage : " " ,
Description : ` Looks for a new version on the offical download server .
If a new version exists , updates the agent binary and quits .
Otherwise , does nothing .
To determine if an update happened in a script , check for error code 64. ` ,
} ,
{
Name : "login" ,
Action : login ,
Usage : "Generate a configuration file with your login details" ,
ArgsUsage : " " ,
Flags : [ ] cli . Flag {
& cli . StringFlag {
Name : "url" ,
Hidden : true ,
} ,
} ,
} ,
{
Name : "hello" ,
Action : hello ,
Usage : "Run a simple \"Hello World\" server for testing Argo Tunnel." ,
Flags : [ ] cli . Flag {
& cli . IntFlag {
Name : "port" ,
Usage : "Listen on the selected port." ,
Value : 8080 ,
} ,
} ,
ArgsUsage : " " , // can't be the empty string or we get the default output
} ,
{
Name : "proxy-dns" ,
Action : tunneldns . Run ,
Usage : "Run a DNS over HTTPS proxy server." ,
Flags : [ ] cli . Flag {
& cli . StringFlag {
Name : "metrics" ,
Value : "localhost:" ,
Usage : "Listen address for metrics reporting." ,
EnvVars : [ ] string { "TUNNEL_METRICS" } ,
} ,
& cli . StringFlag {
Name : "address" ,
Usage : "Listen address for the DNS over HTTPS proxy server." ,
Value : "localhost" ,
EnvVars : [ ] string { "TUNNEL_DNS_ADDRESS" } ,
} ,
& cli . IntFlag {
Name : "port" ,
Usage : "Listen on given port for the DNS over HTTPS proxy server." ,
Value : 53 ,
EnvVars : [ ] string { "TUNNEL_DNS_PORT" } ,
} ,
& cli . StringSliceFlag {
Name : "upstream" ,
Usage : "Upstream endpoint URL, you can specify multiple endpoints for redundancy." ,
2018-04-02 20:43:11 +00:00
Value : cli . NewStringSlice ( "https://cloudflare-dns.com/dns-query" ) ,
2018-03-22 15:24:52 +00:00
EnvVars : [ ] string { "TUNNEL_DNS_UPSTREAM" } ,
} ,
} ,
ArgsUsage : " " , // can't be the empty string or we get the default output
} ,
}
runApp ( app )
}
func startServer ( c * cli . Context ) {
var wg sync . WaitGroup
errC := make ( chan error )
2018-04-04 15:47:36 +00:00
connectedSignal := make ( chan struct { } )
2018-03-22 15:24:52 +00:00
wg . Add ( 2 )
// If the user choose to supply all options through env variables,
// c.NumFlags() == 0 && c.NArg() == 0. For cloudflared to work, the user needs to at
// least provide a hostname.
if c . NumFlags ( ) == 0 && c . NArg ( ) == 0 && os . Getenv ( "TUNNEL_HOSTNAME" ) == "" {
Log . Infof ( "No arguments were provided. You need to at least specify the hostname for this tunnel. See %s" , quickStartUrl )
cli . ShowAppHelp ( c )
return
}
logLevel , err := logrus . ParseLevel ( c . String ( "loglevel" ) )
if err != nil {
Log . WithError ( err ) . Fatal ( "Unknown logging level specified" )
}
Log . SetLevel ( logLevel )
protoLogLevel , err := logrus . ParseLevel ( c . String ( "proto-loglevel" ) )
if err != nil {
Log . WithError ( err ) . Fatal ( "Unknown protocol logging level specified" )
}
protoLogger := logrus . New ( )
protoLogger . Level = protoLogLevel
if c . String ( "logfile" ) != "" {
if err := initLogFile ( c , protoLogger ) ; err != nil {
Log . Error ( err )
}
}
if isAutoupdateEnabled ( c ) {
if initUpdate ( ) {
return
}
Log . Infof ( "Autoupdate frequency is set to %v" , c . Duration ( "autoupdate-freq" ) )
go autoupdate ( c . Duration ( "autoupdate-freq" ) , shutdownC )
}
2018-04-04 15:47:36 +00:00
if c . IsSet ( "proxy-dns" ) {
wg . Add ( 1 )
listener , err := tunneldns . CreateListener ( c . String ( "proxy-dns-address" ) , uint16 ( c . Uint ( "proxy-dns-port" ) ) , c . StringSlice ( "proxy-dns-upstream" ) )
if err != nil {
listener . Stop ( )
Log . WithError ( err ) . Fatal ( "Cannot create the DNS over HTTPS proxy server" )
}
go func ( ) {
err := listener . Start ( )
if err != nil {
Log . WithError ( err ) . Fatal ( "Cannot start the DNS over HTTPS proxy server" )
} else {
<- shutdownC
}
listener . Stop ( )
wg . Done ( )
} ( )
// Serve DNS proxy stand-alone if no hostname or tag or app is going to run
if ! c . IsSet ( "hostname" ) && ! c . IsSet ( "tag" ) && ! c . IsSet ( "hello-world" ) {
go writePidFile ( connectedSignal , c . String ( "pidfile" ) )
close ( connectedSignal )
runServer ( c , & wg , errC , shutdownC )
return
}
}
2018-03-22 15:24:52 +00:00
hostname , err := validation . ValidateHostname ( c . String ( "hostname" ) )
if err != nil {
Log . WithError ( err ) . Fatal ( "Invalid hostname" )
}
clientID := c . String ( "id" )
if ! c . IsSet ( "id" ) {
clientID = generateRandomClientID ( )
}
tags , err := NewTagSliceFromCLI ( c . StringSlice ( "tag" ) )
if err != nil {
Log . WithError ( err ) . Fatal ( "Tag parse failure" )
}
tags = append ( tags , tunnelpogs . Tag { Name : "ID" , Value : clientID } )
if c . IsSet ( "hello-world" ) {
wg . Add ( 1 )
listener , err := createListener ( "127.0.0.1:" )
if err != nil {
listener . Close ( )
Log . WithError ( err ) . Fatal ( "Cannot start Hello World Server" )
}
go func ( ) {
startHelloWorldServer ( listener , shutdownC )
wg . Done ( )
listener . Close ( )
} ( )
c . Set ( "url" , "https://" + listener . Addr ( ) . String ( ) )
}
url , err := validateUrl ( c )
if err != nil {
Log . WithError ( err ) . Fatal ( "Error validating url" )
}
Log . Infof ( "Proxying tunnel requests to %s" , url )
// Fail if the user provided an old authentication method
if c . IsSet ( "api-key" ) || c . IsSet ( "api-email" ) || c . IsSet ( "api-ca-key" ) {
Log . Fatal ( "You don't need to give us your api-key anymore. Please use the new log in method. Just run cloudflared login" )
}
// Check that the user has acquired a certificate using the log in command
originCertPath , err := homedir . Expand ( c . String ( "origincert" ) )
if err != nil {
Log . WithError ( err ) . Fatalf ( "Cannot resolve path %s" , c . String ( "origincert" ) )
}
ok , err := fileExists ( originCertPath )
if err != nil {
Log . Fatalf ( "Cannot check if origin cert exists at path %s" , c . String ( "origincert" ) )
}
if ! ok {
Log . Fatalf ( ` 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 ] )
}
// Easier to send the certificate as []byte via RPC than decoding it at this point
originCert , err := ioutil . ReadFile ( originCertPath )
if err != nil {
Log . WithError ( err ) . Fatalf ( "Cannot read %s to load origin certificate" , originCertPath )
}
tunnelMetrics := origin . NewTunnelMetrics ( )
httpTransport := & http . Transport {
Proxy : http . ProxyFromEnvironment ,
DialContext : ( & net . Dialer {
Timeout : c . Duration ( "proxy-connect-timeout" ) ,
KeepAlive : c . Duration ( "proxy-tcp-keepalive" ) ,
DualStack : ! c . Bool ( "proxy-no-happy-eyeballs" ) ,
} ) . DialContext ,
MaxIdleConns : c . Int ( "proxy-keepalive-connections" ) ,
IdleConnTimeout : c . Duration ( "proxy-keepalive-timeout" ) ,
TLSHandshakeTimeout : c . Duration ( "proxy-tls-timeout" ) ,
ExpectContinueTimeout : 1 * time . Second ,
TLSClientConfig : & tls . Config { RootCAs : tlsconfig . LoadOriginCertsPool ( ) } ,
}
if ! c . IsSet ( "hello-world" ) && c . IsSet ( "origin-server-name" ) {
httpTransport . TLSClientConfig . ServerName = c . String ( "origin-server-name" )
}
tunnelConfig := & origin . TunnelConfig {
2018-04-04 15:47:36 +00:00
EdgeAddrs : c . StringSlice ( "edge" ) ,
OriginUrl : url ,
Hostname : hostname ,
OriginCert : originCert ,
TlsConfig : tlsconfig . CreateTunnelConfig ( c , c . StringSlice ( "edge" ) ) ,
ClientTlsConfig : httpTransport . TLSClientConfig ,
Retries : c . Uint ( "retries" ) ,
HeartbeatInterval : c . Duration ( "heartbeat-interval" ) ,
MaxHeartbeats : c . Uint64 ( "heartbeat-count" ) ,
ClientID : clientID ,
ReportedVersion : Version ,
LBPool : c . String ( "lb-pool" ) ,
Tags : tags ,
HAConnections : c . Int ( "ha-connections" ) ,
HTTPTransport : httpTransport ,
Metrics : tunnelMetrics ,
MetricsUpdateFreq : c . Duration ( "metrics-update-freq" ) ,
ProtocolLogger : protoLogger ,
Logger : Log ,
IsAutoupdated : c . Bool ( "is-autoupdated" ) ,
GracePeriod : c . Duration ( "grace-period" ) ,
2018-04-02 20:43:11 +00:00
DNSValidationConfig : getDNSValidationConfig ( c ) ,
2018-03-22 15:24:52 +00:00
}
go writePidFile ( connectedSignal , c . String ( "pidfile" ) )
go func ( ) {
errC <- origin . StartTunnelDaemon ( tunnelConfig , shutdownC , connectedSignal )
wg . Done ( )
} ( )
2018-04-04 15:47:36 +00:00
runServer ( c , & wg , errC , shutdownC )
}
func runServer ( c * cli . Context , wg * sync . WaitGroup , errC chan error , shutdownC chan struct { } ) {
2018-03-22 15:24:52 +00:00
metricsListener , err := listeners . Listen ( "tcp" , c . String ( "metrics" ) )
if err != nil {
Log . WithError ( err ) . Fatal ( "Error opening metrics server listener" )
}
go func ( ) {
errC <- metrics . ServeMetrics ( metricsListener , shutdownC )
wg . Done ( )
} ( )
var errCode int
err = WaitForSignal ( errC , shutdownC )
if err != nil {
2018-03-29 00:32:01 +00:00
Log . WithError ( err ) . Fatal ( "Quitting due to error" )
2018-03-22 15:24:52 +00:00
raven . CaptureErrorAndWait ( err , nil )
errCode = 1
} else {
Log . Info ( "Quitting..." )
}
// Wait for clean exit, discarding all errors
go func ( ) {
for range errC {
}
} ( )
wg . Wait ( )
os . Exit ( errCode )
}
func WaitForSignal ( errC chan error , shutdownC chan struct { } ) error {
signals := make ( chan os . Signal , 10 )
signal . Notify ( signals , syscall . SIGTERM , syscall . SIGINT )
defer signal . Stop ( signals )
2018-04-04 15:47:36 +00:00
2018-03-22 15:24:52 +00:00
select {
case err := <- errC :
close ( shutdownC )
return err
case <- signals :
close ( shutdownC )
case <- shutdownC :
}
2018-04-04 15:47:36 +00:00
2018-03-22 15:24:52 +00:00
return nil
}
func update ( _ * cli . Context ) error {
if updateApplied ( ) {
os . Exit ( 64 )
}
return nil
}
func initUpdate ( ) bool {
if updateApplied ( ) {
os . Args = append ( os . Args , "--is-autoupdated=true" )
if _ , err := listeners . StartProcess ( ) ; err != nil {
Log . WithError ( err ) . Error ( "Unable to restart server automatically" )
return false
}
return true
}
return false
}
func autoupdate ( freq time . Duration , shutdownC chan struct { } ) {
for {
if updateApplied ( ) {
os . Args = append ( os . Args , "--is-autoupdated=true" )
if _ , err := listeners . StartProcess ( ) ; err != nil {
Log . WithError ( err ) . Error ( "Unable to restart server automatically" )
}
close ( shutdownC )
return
}
time . Sleep ( freq )
}
}
func updateApplied ( ) bool {
releaseInfo := checkForUpdates ( )
if releaseInfo . Updated {
Log . Infof ( "Updated to version %s" , releaseInfo . Version )
return true
}
if releaseInfo . Error != nil {
Log . WithError ( releaseInfo . Error ) . Error ( "Update check failed" )
}
return false
}
func fileExists ( path string ) ( bool , error ) {
f , err := os . Open ( path )
if err != nil {
if os . IsNotExist ( err ) {
// ignore missing files
return false , nil
}
return false , err
}
f . Close ( )
return true , nil
}
// returns the first path that contains a cert.pem file. If none of the defaultConfigDirs
// (differs by OS for legacy reasons) contains a cert.pem file, return empty string
func findDefaultOriginCertPath ( ) string {
for _ , defaultConfigDir := range defaultConfigDirs {
originCertPath , _ := homedir . Expand ( filepath . Join ( defaultConfigDir , credentialFile ) )
if ok , _ := fileExists ( originCertPath ) ; ok {
return originCertPath
}
}
return ""
}
// returns the firt path that contains a config file. If none of the combination of
// defaultConfigDirs (differs by OS for legacy reasons) and defaultConfigFiles
// contains a config file, return empty string
func findDefaultConfigPath ( ) string {
for _ , configDir := range defaultConfigDirs {
for _ , configFile := range defaultConfigFiles {
dirPath , err := homedir . Expand ( configDir )
if err != nil {
2018-04-04 15:47:36 +00:00
continue
2018-03-22 15:24:52 +00:00
}
path := filepath . Join ( dirPath , configFile )
if ok , _ := fileExists ( path ) ; ok {
return path
}
}
}
return ""
}
func findInputSourceContext ( context * cli . Context ) ( altsrc . InputSourceContext , error ) {
if context . String ( "config" ) != "" {
return altsrc . NewYamlSourceFromFile ( context . String ( "config" ) )
}
return nil , nil
}
func generateRandomClientID ( ) string {
r := rand . New ( rand . NewSource ( time . Now ( ) . UnixNano ( ) ) )
id := make ( [ ] byte , 32 )
r . Read ( id )
return hex . EncodeToString ( id )
}
func writePidFile ( waitForSignal chan struct { } , pidFile string ) {
<- waitForSignal
daemon . SdNotify ( false , "READY=1" )
if pidFile == "" {
return
}
file , err := os . Create ( pidFile )
if err != nil {
Log . WithError ( err ) . Errorf ( "Unable to write pid to %s" , pidFile )
}
defer file . Close ( )
fmt . Fprintf ( file , "%d" , os . Getpid ( ) )
}
// validate url. It can be either from --url or argument
func validateUrl ( c * cli . Context ) ( string , error ) {
var url = c . String ( "url" )
if c . NArg ( ) > 0 {
if c . IsSet ( "url" ) {
return "" , errors . New ( "Specified origin urls using both --url and argument. Decide which one you want, I can only support one." )
}
url = c . Args ( ) . Get ( 0 )
}
validUrl , err := validation . ValidateUrl ( url )
return validUrl , err
}
func initLogFile ( c * cli . Context , protoLogger * logrus . Logger ) error {
filePath , err := homedir . Expand ( c . String ( "logfile" ) )
if err != nil {
return errors . Wrap ( err , "Cannot resolve logfile path" )
}
fileMode := os . O_WRONLY | os . O_APPEND | os . O_CREATE | os . O_TRUNC
// do not truncate log file if the client has been autoupdated
if c . Bool ( "is-autoupdated" ) {
fileMode = os . O_WRONLY | os . O_APPEND | os . O_CREATE
}
f , err := os . OpenFile ( filePath , fileMode , 0664 )
if err != nil {
errors . Wrap ( err , fmt . Sprintf ( "Cannot open file %s" , filePath ) )
}
defer f . Close ( )
pathMap := lfshook . PathMap {
logrus . InfoLevel : filePath ,
logrus . ErrorLevel : filePath ,
logrus . FatalLevel : filePath ,
logrus . PanicLevel : filePath ,
}
Log . Hooks . Add ( lfshook . NewHook ( pathMap , & logrus . JSONFormatter { } ) )
protoLogger . Hooks . Add ( lfshook . NewHook ( pathMap , & logrus . JSONFormatter { } ) )
flags := make ( map [ string ] interface { } )
envs := make ( map [ string ] string )
for _ , flag := range c . LocalFlagNames ( ) {
flags [ flag ] = c . Generic ( flag )
}
// 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 ]
}
}
}
Log . Infof ( "Argo Tunnel build and runtime configuration: %+v" , BuildAndRuntimeInfo {
GoOS : runtime . GOOS ,
GoVersion : runtime . Version ( ) ,
GoArch : runtime . GOARCH ,
WarpVersion : Version ,
WarpFlags : flags ,
WarpEnvs : envs ,
} )
return nil
}
func isAutoupdateEnabled ( c * cli . Context ) bool {
if terminal . IsTerminal ( int ( os . Stdout . Fd ( ) ) ) {
Log . Info ( noAutoupdateMessage )
return false
}
return ! c . Bool ( "no-autoupdate" ) && c . Duration ( "autoupdate-freq" ) != 0
}
2018-04-02 20:43:11 +00:00
func getDNSValidationConfig ( c * cli . Context ) * origin . DNSValidationConfig {
dnsValidationConfig := & origin . DNSValidationConfig {
VerifyDNSPropagated : ! c . Bool ( "skip-hostname-propagation-check" ) ,
2018-04-04 15:47:36 +00:00
DNSPingRetries : c . Uint ( "hostname-propagated-retries" ) ,
DNSInitWaitTime : c . Duration ( "init-wait-time" ) ,
PingFreq : c . Duration ( "ping-freq" ) ,
2018-04-02 20:43:11 +00:00
}
if dnsValidationConfig . DNSInitWaitTime < minDNSInitWait {
dnsValidationConfig . DNSInitWaitTime = minDNSInitWait
}
if dnsValidationConfig . PingFreq < minPingFreq {
dnsValidationConfig . PingFreq = minPingFreq
}
return dnsValidationConfig
}