From 77e7c240c0d838b3a377954a412b528d9070714f Mon Sep 17 00:00:00 2001 From: cloudflare-warp-bot Date: Thu, 26 Apr 2018 14:57:32 +0000 Subject: [PATCH] Release Argo Tunnel Client 2018.4.7 --- cmd/cloudflared/hello.go | 2 +- cmd/cloudflared/linux_service.go | 36 +++--- cmd/cloudflared/login.go | 8 +- cmd/cloudflared/macos_service.go | 72 +++++++---- cmd/cloudflared/main.go | 177 +++++++++++++++------------- cmd/cloudflared/service_template.go | 12 +- cmd/cloudflared/windows_service.go | 26 ++-- log/log.go | 85 +++++++++++++ metrics/metrics.go | 10 +- origin/build_info.go | 19 +++ origin/metrics.go | 22 ++-- origin/supervisor.go | 10 +- origin/tunnel.go | 58 ++++----- tlsconfig/tlsconfig.go | 20 ++-- tunneldns/tunnel.go | 22 ++-- tunnelrpc/pogs/tunnelrpc.go | 2 +- tunnelrpc/tunnelrpc.capnp | 2 + tunnelrpc/tunnelrpc.capnp.go | 170 +++++++++++++------------- 18 files changed, 460 insertions(+), 293 deletions(-) create mode 100644 log/log.go create mode 100644 origin/build_info.go diff --git a/cmd/cloudflared/hello.go b/cmd/cloudflared/hello.go index 9f73ade8..b2cd332d 100644 --- a/cmd/cloudflared/hello.go +++ b/cmd/cloudflared/hello.go @@ -103,7 +103,7 @@ func hello(c *cli.Context) error { } func startHelloWorldServer(listener net.Listener, shutdownC <-chan struct{}) error { - Log.Infof("Starting Hello World server at %s", listener.Addr()) + logger.Infof("Starting Hello World server at %s", listener.Addr()) serverName := defaultServerName if hostname, err := os.Hostname(); err == nil { serverName = hostname diff --git a/cmd/cloudflared/linux_service.go b/cmd/cloudflared/linux_service.go index d0b15937..4e859617 100644 --- a/cmd/cloudflared/linux_service.go +++ b/cmd/cloudflared/linux_service.go @@ -90,8 +90,8 @@ var sysvTemplate = ServiceTemplate{ # Short-Description: Argo Tunnel # Description: Argo Tunnel agent ### END INIT INFO -cmd="{{.Path}} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s" name=$(basename $(readlink -f $0)) +cmd="{{.Path}} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s" pid_file="/var/run/$name.pid" stdout_log="/var/log/$name.log" stderr_log="/var/log/$name.err" @@ -184,17 +184,17 @@ func installLinuxService(c *cli.Context) error { defaultConfigDir := filepath.Dir(c.String("config")) defaultConfigFile := filepath.Base(c.String("config")) if err = copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile); err != nil { - Log.WithError(err).Infof("Failed to copy user configuration. Before running the service, ensure that %s contains two files, %s and %s", + logger.WithError(err).Infof("Failed to copy user configuration. Before running the service, ensure that %s contains two files, %s and %s", serviceConfigDir, credentialFile, defaultConfigFiles[0]) return err } switch { case isSystemd(): - Log.Infof("Using Systemd") + logger.Infof("Using Systemd") return installSystemd(&templateArgs) default: - Log.Infof("Using Sysv") + logger.Infof("Using Sysv") return installSysv(&templateArgs) } } @@ -203,30 +203,30 @@ func installSystemd(templateArgs *ServiceTemplateArgs) error { for _, serviceTemplate := range systemdTemplates { err := serviceTemplate.Generate(templateArgs) if err != nil { - Log.WithError(err).Infof("error generating service template") + logger.WithError(err).Infof("error generating service template") return err } } if err := runCommand("systemctl", "enable", "cloudflared.service"); err != nil { - Log.WithError(err).Infof("systemctl enable cloudflared.service error") + logger.WithError(err).Infof("systemctl enable cloudflared.service error") return err } if err := runCommand("systemctl", "start", "cloudflared-update.timer"); err != nil { - Log.WithError(err).Infof("systemctl start cloudflared-update.timer error") + logger.WithError(err).Infof("systemctl start cloudflared-update.timer error") return err } - Log.Infof("systemctl daemon-reload") + logger.Infof("systemctl daemon-reload") return runCommand("systemctl", "daemon-reload") } func installSysv(templateArgs *ServiceTemplateArgs) error { confPath, err := sysvTemplate.ResolvePath() if err != nil { - Log.WithError(err).Infof("error resolving system path") + logger.WithError(err).Infof("error resolving system path") return err } if err := sysvTemplate.Generate(templateArgs); err != nil { - Log.WithError(err).Infof("error generating system template") + logger.WithError(err).Infof("error generating system template") return err } for _, i := range [...]string{"2", "3", "4", "5"} { @@ -245,36 +245,36 @@ func installSysv(templateArgs *ServiceTemplateArgs) error { func uninstallLinuxService(c *cli.Context) error { switch { case isSystemd(): - Log.Infof("Using Systemd") + logger.Infof("Using Systemd") return uninstallSystemd() default: - Log.Infof("Using Sysv") + logger.Infof("Using Sysv") return uninstallSysv() } } func uninstallSystemd() error { if err := runCommand("systemctl", "disable", "cloudflared.service"); err != nil { - Log.WithError(err).Infof("systemctl disable cloudflared.service error") + logger.WithError(err).Infof("systemctl disable cloudflared.service error") return err } if err := runCommand("systemctl", "stop", "cloudflared-update.timer"); err != nil { - Log.WithError(err).Infof("systemctl stop cloudflared-update.timer error") + logger.WithError(err).Infof("systemctl stop cloudflared-update.timer error") return err } for _, serviceTemplate := range systemdTemplates { if err := serviceTemplate.Remove(); err != nil { - Log.WithError(err).Infof("error removing service template") + logger.WithError(err).Infof("error removing service template") return err } } - Log.Infof("Successfully uninstall cloudflared service") + logger.Infof("Successfully uninstall cloudflared service") return nil } func uninstallSysv() error { if err := sysvTemplate.Remove(); err != nil { - Log.WithError(err).Infof("error removing service template") + logger.WithError(err).Infof("error removing service template") return err } for _, i := range [...]string{"2", "3", "4", "5"} { @@ -287,6 +287,6 @@ func uninstallSysv() error { continue } } - Log.Infof("Successfully uninstall cloudflared service") + logger.Infof("Successfully uninstall cloudflared service") return nil } diff --git a/cmd/cloudflared/login.go b/cmd/cloudflared/login.go index fa3c675e..1ba234ec 100644 --- a/cmd/cloudflared/login.go +++ b/cmd/cloudflared/login.go @@ -136,7 +136,7 @@ func download(certURL, filePath string) bool { return true } if err != nil { - Log.WithError(err).Error("Error fetching certificate") + logger.WithError(err).Error("Error fetching certificate") return false } } @@ -179,16 +179,16 @@ func putSuccess(client *http.Client, certURL string) { // indicate success to the relay server req, err := http.NewRequest("PUT", certURL+"/ok", nil) if err != nil { - Log.WithError(err).Error("HTTP request error") + logger.WithError(err).Error("HTTP request error") return } resp, err := client.Do(req) if err != nil { - Log.WithError(err).Error("HTTP error") + logger.WithError(err).Error("HTTP error") return } resp.Body.Close() if resp.StatusCode != 200 { - Log.Errorf("Unexpected HTTP error code %d", resp.StatusCode) + logger.Errorf("Unexpected HTTP error code %d", resp.StatusCode) } } diff --git a/cmd/cloudflared/macos_service.go b/cmd/cloudflared/macos_service.go index b0ce57b3..37737c57 100644 --- a/cmd/cloudflared/macos_service.go +++ b/cmd/cloudflared/macos_service.go @@ -9,7 +9,9 @@ import ( "gopkg.in/urfave/cli.v2" ) -const launchAgentIdentifier = "com.cloudflare.cloudflared" +const ( + launchdIdentifier = "com.cloudflare.cloudflared" +) func runApp(app *cli.App) { app.Commands = append(app.Commands, &cli.Command{ @@ -32,7 +34,7 @@ func runApp(app *cli.App) { } var launchdTemplate = ServiceTemplate{ - Path: installPath(launchAgentIdentifier), + Path: installPath(), Content: fmt.Sprintf(` @@ -46,9 +48,9 @@ var launchdTemplate = ServiceTemplate{ RunAtLoad StandardOutPath - /tmp/%s.out.log - StandardErrorPath - /tmp/%s.err.log + %s + StandardErrorPath + %s KeepAlive SuccessfulExit @@ -57,54 +59,84 @@ var launchdTemplate = ServiceTemplate{ ThrottleInterval 20 -`, launchAgentIdentifier, launchAgentIdentifier, launchAgentIdentifier), +`, launchdIdentifier, stdoutPath(), stderrPath()), } -func installPath(launchAgentIdentifier string) string { - pathPattern := "~/Library/LaunchAgents/%s.plist" +func isRootUser() bool { + return os.Geteuid() == 0 +} +func installPath() string { // User is root, use /Library/LaunchDaemons instead of home directory - if os.Geteuid() == 0 { - pathPattern = "/Library/LaunchDaemons/%s.plist" + if isRootUser() { + return fmt.Sprintf("/Library/LaunchDaemons/%s.plist", launchdIdentifier) } + return fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", userHomeDir(), launchdIdentifier) +} - return fmt.Sprintf(pathPattern, launchAgentIdentifier) +func stdoutPath() string { + if isRootUser() { + return fmt.Sprintf("/Library/Logs/%s.out.log", launchdIdentifier) + } + return fmt.Sprintf("%s/Library/Logs/%s.out.log", userHomeDir(), launchdIdentifier) +} + +func stderrPath() string { + if isRootUser() { + return fmt.Sprintf("/Library/Logs/%s.err.log", launchdIdentifier) + } + return fmt.Sprintf("%s/Library/Logs/%s.err.log", userHomeDir(), launchdIdentifier) } func installLaunchd(c *cli.Context) error { - Log.Infof("Installing Argo Tunnel as an user launch agent") + if isRootUser() { + logger.Infof("Installing Argo Tunnel client as a system launch daemon. " + + "Argo Tunnel client will run at boot") + } else { + logger.Infof("Installing Argo Tunnel client as an user launch agent. " + + "Note that Argo Tunnel client will only run when the user is logged in. " + + "If you want to run Argo Tunnel client at boot, install with root permission. " + + "For more information, visit https://developers.cloudflare.com/argo-tunnel/reference/service/") + } etPath, err := os.Executable() if err != nil { - Log.WithError(err).Infof("error determining executable path") + logger.WithError(err).Infof("error determining executable path") return fmt.Errorf("error determining executable path: %v", err) } templateArgs := ServiceTemplateArgs{Path: etPath} err = launchdTemplate.Generate(&templateArgs) if err != nil { - Log.WithError(err).Infof("error generating launchd template") + logger.WithError(err).Infof("error generating launchd template") return err } plistPath, err := launchdTemplate.ResolvePath() if err != nil { - Log.WithError(err).Infof("error resolving launchd template path") + logger.WithError(err).Infof("error resolving launchd template path") return err } - Log.Infof("Outputs are logged in %s and %s", fmt.Sprintf("/tmp/%s.out.log", launchAgentIdentifier), fmt.Sprintf("/tmp/%s.err.log", launchAgentIdentifier)) + + logger.Infof("Outputs are logged to %s and %s", stderrPath(), stdoutPath()) return runCommand("launchctl", "load", plistPath) } func uninstallLaunchd(c *cli.Context) error { - Log.Infof("Uninstalling Argo Tunnel as an user launch agent") + + if isRootUser() { + logger.Infof("Uninstalling Argo Tunnel as a system launch daemon") + } else { + logger.Infof("Uninstalling Argo Tunnel as an user launch agent") + } plistPath, err := launchdTemplate.ResolvePath() if err != nil { - Log.WithError(err).Infof("error resolving launchd template path") + logger.WithError(err).Infof("error resolving launchd template path") return err } err = runCommand("launchctl", "unload", plistPath) if err != nil { - Log.WithError(err).Infof("error unloading") + logger.WithError(err).Infof("error unloading") return err } - Log.Infof("Outputs are logged in %s and %s", fmt.Sprintf("/tmp/%s.out.log", launchAgentIdentifier), fmt.Sprintf("/tmp/%s.err.log", launchAgentIdentifier)) + + logger.Infof("Outputs are logged to %s and %s", stderrPath(), stdoutPath()) return launchdTemplate.Remove() } diff --git a/cmd/cloudflared/main.go b/cmd/cloudflared/main.go index 6d93fdc8..c0b6ebb1 100644 --- a/cmd/cloudflared/main.go +++ b/cmd/cloudflared/main.go @@ -11,12 +11,12 @@ import ( "os" "os/signal" "path/filepath" - "runtime" "strings" "sync" "syscall" "time" + "github.com/cloudflare/cloudflared/log" "github.com/cloudflare/cloudflared/metrics" "github.com/cloudflare/cloudflared/origin" "github.com/cloudflare/cloudflared/tlsconfig" @@ -48,7 +48,7 @@ const ( var listeners = gracenet.Net{} var Version = "DEV" var BuildTime = "unknown" -var Log *logrus.Logger +var logger = log.CreateLogger() var defaultConfigFiles = []string{"config.yml", "config.yaml"} // Launchd doesn't set root env variables, so there is default @@ -59,15 +59,6 @@ var defaultConfigDirs = []string{"~/.cloudflared", "~/.cloudflare-warp", "~/clou // 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) @@ -306,18 +297,17 @@ func main() { 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")) + logger.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")) + logger.WithError(err).Infof("Cannot apply configuration from %s", context.String("config")) return err } - Log.Infof("Applied configuration from %s", context.String("config")) + logger.Infof("Applied configuration from %s", context.String("config")) } return nil } @@ -404,45 +394,50 @@ func startServer(c *cli.Context) { // 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) + logger.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") + logger.WithError(err).Fatal("Unknown logging level specified") } - Log.SetLevel(logLevel) + logger.SetLevel(logLevel) protoLogLevel, err := logrus.ParseLevel(c.String("proto-loglevel")) if err != nil { - Log.WithError(err).Fatal("Unknown protocol logging level specified") + logger.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) + logger.Error(err) } } + buildInfo := origin.GetBuildInfo() + logger.Infof("Build info: %+v", *buildInfo) + logger.Infof("Version %s", Version) + logClientOptions(c) + if c.IsSet("proxy-dns") { port := c.Int("proxy-dns-port") if port <= 0 || port > 65535 { - Log.Fatal("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.") + logger.Fatal("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.") } wg.Add(1) listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream")) if err != nil { close(dnsReadySignal) listener.Stop() - Log.WithError(err).Fatal("Cannot create the DNS over HTTPS proxy server") + logger.WithError(err).Fatal("Cannot create the DNS over HTTPS proxy server") } go func() { err := listener.Start(dnsReadySignal) if err != nil { - Log.WithError(err).Fatal("Cannot start the DNS over HTTPS proxy server") + logger.WithError(err).Fatal("Cannot start the DNS over HTTPS proxy server") } else { <-shutdownC } @@ -453,13 +448,14 @@ func startServer(c *cli.Context) { close(dnsReadySignal) } - if isAutoupdateEnabled(c) { + isRunningFromTerminal := isRunningFromTerminal() + if isAutoupdateEnabled(c, isRunningFromTerminal) { // Wait for proxy-dns to come up (if used) <-dnsReadySignal if initUpdate() { return } - Log.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq")) + logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq")) go autoupdate(c.Duration("autoupdate-freq"), shutdownC) } @@ -473,7 +469,7 @@ func startServer(c *cli.Context) { hostname, err := validation.ValidateHostname(c.String("hostname")) if err != nil { - Log.WithError(err).Fatal("Invalid hostname") + logger.WithError(err).Fatal("Invalid hostname") } clientID := c.String("id") if !c.IsSet("id") { @@ -482,7 +478,7 @@ func startServer(c *cli.Context) { tags, err := NewTagSliceFromCLI(c.StringSlice("tag")) if err != nil { - Log.WithError(err).Fatal("Tag parse failure") + logger.WithError(err).Fatal("Tag parse failure") } tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID}) @@ -491,7 +487,7 @@ func startServer(c *cli.Context) { listener, err := createListener("127.0.0.1:") if err != nil { listener.Close() - Log.WithError(err).Fatal("Cannot start Hello World Server") + logger.WithError(err).Fatal("Cannot start Hello World Server") } go func() { startHelloWorldServer(listener, shutdownC) @@ -503,26 +499,26 @@ func startServer(c *cli.Context) { url, err := validateUrl(c) if err != nil { - Log.WithError(err).Fatal("Error validating url") + logger.WithError(err).Fatal("Error validating url") } - Log.Infof("Proxying tunnel requests to %s", url) + logger.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") + logger.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")) + logger.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")) + logger.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: + logger.Fatalf(`Cannot find a valid certificate for your origin at the path: %s @@ -535,7 +531,7 @@ If you don't have a certificate signed by Cloudflare, run the command: // 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) + logger.WithError(err).Fatalf("Cannot read %s to load origin certificate", originCertPath) } tunnelMetrics := origin.NewTunnelMetrics() @@ -558,27 +554,29 @@ If you don't have a certificate signed by Cloudflare, run the command: } tunnelConfig := &origin.TunnelConfig{ - 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"), + 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, + BuildInfo: buildInfo, + 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: logger, + IsAutoupdated: c.Bool("is-autoupdated"), + GracePeriod: c.Duration("grace-period"), + RunFromTerminal: isRunningFromTerminal, } go writePidFile(connectedSignal, c.String("pidfile")) @@ -595,21 +593,21 @@ func runServer(c *cli.Context, wg *sync.WaitGroup, errC chan error, shutdownC ch wg.Add(1) metricsListener, err := listeners.Listen("tcp", c.String("metrics")) if err != nil { - Log.WithError(err).Fatal("Error opening metrics server listener") + logger.WithError(err).Fatal("Error opening metrics server listener") } go func() { - errC <- metrics.ServeMetrics(metricsListener, shutdownC) + errC <- metrics.ServeMetrics(metricsListener, shutdownC, logger) wg.Done() }() var errCode int err = WaitForSignal(errC, shutdownC) if err != nil { - Log.WithError(err).Fatal("Quitting due to error") + logger.WithError(err).Fatal("Quitting due to error") raven.CaptureErrorAndWait(err, nil) errCode = 1 } else { - Log.Info("Graceful shutdown...") + logger.Info("Graceful shutdown...") } // Wait for clean exit, discarding all errors go func() { @@ -648,7 +646,7 @@ 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") + logger.WithError(err).Error("Unable to restart server automatically") return false } return true @@ -661,7 +659,7 @@ func autoupdate(freq time.Duration, shutdownC chan struct{}) { 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") + logger.WithError(err).Error("Unable to restart server automatically") } close(shutdownC) return @@ -673,11 +671,11 @@ func autoupdate(freq time.Duration, shutdownC chan struct{}) { func updateApplied() bool { releaseInfo := checkForUpdates() if releaseInfo.Updated { - Log.Infof("Updated to version %s", releaseInfo.Version) + logger.Infof("Updated to version %s", releaseInfo.Version) return true } if releaseInfo.Error != nil { - Log.WithError(releaseInfo.Error).Error("Update check failed") + logger.WithError(releaseInfo.Error).Error("Update check failed") } return false } @@ -748,7 +746,7 @@ func writePidFile(waitForSignal chan struct{}, pidFile string) { } file, err := os.Create(pidFile) if err != nil { - Log.WithError(err).Errorf("Unable to write pid to %s", pidFile) + logger.WithError(err).Errorf("Unable to write pid to %s", pidFile) } defer file.Close() fmt.Fprintf(file, "%d", os.Getpid()) @@ -790,16 +788,22 @@ func initLogFile(c *cli.Context, protoLogger *logrus.Logger) error { logrus.PanicLevel: filePath, } - Log.Hooks.Add(lfshook.NewHook(pathMap, &logrus.JSONFormatter{})) + logger.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) + return nil +} +func logClientOptions(c *cli.Context) { + flags := make(map[string]interface{}) for _, flag := range c.LocalFlagNames() { flags[flag] = c.Generic(flag) } + if len(flags) > 0 { + logger.Infof("Flags %v", flags) + } + envs := make(map[string]string) // Find env variables for Argo Tunnel for _, env := range os.Environ() { // All Argo Tunnel env variables start with TUNNEL_ @@ -810,24 +814,33 @@ func initLogFile(c *cli.Context, protoLogger *logrus.Logger) error { } } } - - 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 + if len(envs) > 0 { + logger.Infof("Environmental variables %v", envs) + } } -func isAutoupdateEnabled(c *cli.Context) bool { - if terminal.IsTerminal(int(os.Stdout.Fd())) { - Log.Info(noAutoupdateMessage) +func isAutoupdateEnabled(c *cli.Context, isRunningFromTerminal bool) bool { + if isRunningFromTerminal { + logger.Info(noAutoupdateMessage) return false } return !c.Bool("no-autoupdate") && c.Duration("autoupdate-freq") != 0 } + + +func isRunningFromTerminal() bool { + return terminal.IsTerminal(int(os.Stdout.Fd())) +} + +func userHomeDir() string { + // This returns the home dir of the executing user using OS-specific method + // for discovering the home dir. It's not recommended to call this function + // when the user has root permission as $HOME depends on what options the user + // use with sudo. + homeDir, err := homedir.Dir() + if err != nil { + logger.WithError(err).Fatal("Cannot determine home directory for the user.") + } + return homeDir +} diff --git a/cmd/cloudflared/service_template.go b/cmd/cloudflared/service_template.go index 063e26cb..afec0fd7 100644 --- a/cmd/cloudflared/service_template.go +++ b/cmd/cloudflared/service_template.go @@ -73,21 +73,21 @@ func runCommand(command string, args ...string) error { cmd := exec.Command(command, args...) stderr, err := cmd.StderrPipe() if err != nil { - Log.WithError(err).Infof("error getting stderr pipe") + logger.WithError(err).Infof("error getting stderr pipe") return fmt.Errorf("error getting stderr pipe: %v", err) } err = cmd.Start() if err != nil { - Log.WithError(err).Infof("error starting %s", command) + logger.WithError(err).Infof("error starting %s", command) return fmt.Errorf("error starting %s: %v", command, err) } commandErr, _ := ioutil.ReadAll(stderr) if len(commandErr) > 0 { - Log.Errorf("%s: %s", command, commandErr) + logger.Errorf("%s: %s", command, commandErr) } err = cmd.Wait() if err != nil { - Log.WithError(err).Infof("%s returned error", command) + logger.WithError(err).Infof("%s returned error", command) return fmt.Errorf("%s returned with error: %v", command, err) } return nil @@ -159,7 +159,7 @@ func copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile strin destConfigPath := filepath.Join(serviceConfigDir, defaultConfigFile) destFile, exists, err := openFile(destConfigPath, true) if err != nil { - Log.WithError(err).Infof("cannot open %s", destConfigPath) + logger.WithError(err).Infof("cannot open %s", destConfigPath) return err } else if exists { // config already exists, do nothing @@ -185,7 +185,7 @@ func copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile strin if err != nil { return fmt.Errorf("unable to copy %s to %s: %v", srcConfigPath, destConfigPath, err) } - Log.Infof("Copied %s to %s", srcConfigPath, destConfigPath) + logger.Infof("Copied %s to %s", srcConfigPath, destConfigPath) } return nil diff --git a/cmd/cloudflared/windows_service.go b/cmd/cloudflared/windows_service.go index b003bb50..f1442863 100644 --- a/cmd/cloudflared/windows_service.go +++ b/cmd/cloudflared/windows_service.go @@ -41,7 +41,7 @@ func runApp(app *cli.App) { isIntSess, err := svc.IsAnInteractiveSession() if err != nil { - Log.Fatalf("failed to determine if we are running in an interactive session: %v", err) + logger.Fatalf("failed to determine if we are running in an interactive session: %v", err) } if isIntSess { @@ -51,7 +51,7 @@ func runApp(app *cli.App) { elog, err := eventlog.Open(windowsServiceName) if err != nil { - Log.WithError(err).Infof("Cannot open event log for %s", windowsServiceName) + logger.WithError(err).Infof("Cannot open event log for %s", windowsServiceName) return } defer elog.Close() @@ -104,62 +104,62 @@ loop: } func installWindowsService(c *cli.Context) error { - Log.Infof("Installing Argo Tunnel Windows service") + logger.Infof("Installing Argo Tunnel Windows service") exepath, err := os.Executable() if err != nil { - Log.Infof("Cannot find path name that start the process") + logger.Infof("Cannot find path name that start the process") return err } m, err := mgr.Connect() if err != nil { - Log.WithError(err).Infof("Cannot establish a connection to the service control manager") + logger.WithError(err).Infof("Cannot establish a connection to the service control manager") return err } defer m.Disconnect() s, err := m.OpenService(windowsServiceName) if err == nil { s.Close() - Log.Errorf("service %s already exists", windowsServiceName) + logger.Errorf("service %s already exists", windowsServiceName) return fmt.Errorf("service %s already exists", windowsServiceName) } config := mgr.Config{StartType: mgr.StartAutomatic, DisplayName: windowsServiceDescription} s, err = m.CreateService(windowsServiceName, exepath, config) if err != nil { - Log.Infof("Cannot install service %s", windowsServiceName) + logger.Infof("Cannot install service %s", windowsServiceName) return err } defer s.Close() err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info) if err != nil { s.Delete() - Log.WithError(err).Infof("Cannot install event logger") + logger.WithError(err).Infof("Cannot install event logger") return fmt.Errorf("SetupEventLogSource() failed: %s", err) } return nil } func uninstallWindowsService(c *cli.Context) error { - Log.Infof("Uninstalling Argo Tunnel Windows Service") + logger.Infof("Uninstalling Argo Tunnel Windows Service") m, err := mgr.Connect() if err != nil { - Log.Infof("Cannot establish a connection to the service control manager") + logger.Infof("Cannot establish a connection to the service control manager") return err } defer m.Disconnect() s, err := m.OpenService(windowsServiceName) if err != nil { - Log.Infof("service %s is not installed", windowsServiceName) + logger.Infof("service %s is not installed", windowsServiceName) return fmt.Errorf("service %s is not installed", windowsServiceName) } defer s.Close() err = s.Delete() if err != nil { - Log.Errorf("Cannot delete service %s", windowsServiceName) + logger.Errorf("Cannot delete service %s", windowsServiceName) return err } err = eventlog.Remove(windowsServiceName) if err != nil { - Log.Infof("Cannot remove event logger") + logger.Infof("Cannot remove event logger") return fmt.Errorf("RemoveEventLogSource() failed: %s", err) } return nil diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..f1824f2e --- /dev/null +++ b/log/log.go @@ -0,0 +1,85 @@ +// this forks the logrus json formatter to rename msg -> message as that's the +// expected field. Ideally the logger should make it easier for us. +package log + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/mattn/go-colorable" + "github.com/sirupsen/logrus" +) + +var ( + DefaultTimestampFormat = time.RFC3339 +) + +type JSONFormatter struct { + // TimestampFormat sets the format used for marshaling timestamps. + TimestampFormat string +} + +func CreateLogger() *logrus.Logger { + logger := logrus.New() + logger.Out = colorable.NewColorableStderr() + logger.Formatter = &logrus.TextFormatter{ForceColors: true} + + return logger +} + +func (f *JSONFormatter) Format(entry *logrus.Entry) ([]byte, error) { + data := make(logrus.Fields, len(entry.Data)+3) + for k, v := range entry.Data { + switch v := v.(type) { + case error: + // Otherwise errors are ignored by `encoding/json` + // https://github.com/sirupsen/logrus/issues/137 + data[k] = v.Error() + default: + data[k] = v + } + } + prefixFieldClashes(data) + + timestampFormat := f.TimestampFormat + if timestampFormat == "" { + timestampFormat = DefaultTimestampFormat + } + + data["time"] = entry.Time.Format(timestampFormat) + data["message"] = entry.Message + data["level"] = entry.Level.String() + + serialized, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + } + return append(serialized, '\n'), nil +} + +// This is to not silently overwrite `time`, `msg` and `level` fields when +// dumping it. If this code wasn't there doing: +// +// logrus.WithField("level", 1).Info("hello") +// +// Would just silently drop the user provided level. Instead with this code +// it'll logged as: +// +// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."} +// +// It's not exported because it's still using Data in an opinionated way. It's to +// avoid code duplication between the two default formatters. +func prefixFieldClashes(data logrus.Fields) { + if t, ok := data["time"]; ok { + data["fields.time"] = t + } + + if m, ok := data["msg"]; ok { + data["fields.msg"] = m + } + + if l, ok := data["level"]; ok { + data["fields.level"] = l + } +} diff --git a/metrics/metrics.go b/metrics/metrics.go index 4707b4ed..4b6156ab 100644 --- a/metrics/metrics.go +++ b/metrics/metrics.go @@ -13,7 +13,7 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) const ( @@ -21,7 +21,7 @@ const ( startupTime = time.Millisecond * 500 ) -func ServeMetrics(l net.Listener, shutdownC <-chan struct{}) (err error) { +func ServeMetrics(l net.Listener, shutdownC <-chan struct{}, logger *logrus.Logger) (err error) { var wg sync.WaitGroup // Metrics port is privileged, so no need for further access control trace.AuthRequest = func(*http.Request) (bool, bool) { return true, true } @@ -39,7 +39,7 @@ func ServeMetrics(l net.Listener, shutdownC <-chan struct{}) (err error) { defer wg.Done() err = server.Serve(l) }() - log.WithField("addr", l.Addr()).Info("Starting metrics server") + logger.WithField("addr", l.Addr()).Info("Starting metrics server") // server.Serve will hang if server.Shutdown is called before the server is // fully started up. So add artificial delay. time.Sleep(startupTime) @@ -51,10 +51,10 @@ func ServeMetrics(l net.Listener, shutdownC <-chan struct{}) (err error) { wg.Wait() if err == http.ErrServerClosed { - log.Info("Metrics server stopped") + logger.Info("Metrics server stopped") return nil } - log.WithError(err).Error("Metrics server quit with error") + logger.WithError(err).Error("Metrics server quit with error") return err } diff --git a/origin/build_info.go b/origin/build_info.go new file mode 100644 index 00000000..72f0965a --- /dev/null +++ b/origin/build_info.go @@ -0,0 +1,19 @@ +package origin + +import ( + "runtime" +) + +type BuildInfo struct { + GoOS string `json:"go_os"` + GoVersion string `json:"go_version"` + GoArch string `json:"go_arch"` +} + +func GetBuildInfo() *BuildInfo { + return &BuildInfo{ + GoOS: runtime.GOOS, + GoVersion: runtime.Version(), + GoArch: runtime.GOARCH, + } +} diff --git a/origin/metrics.go b/origin/metrics.go index 9908c554..8ee560c1 100644 --- a/origin/metrics.go +++ b/origin/metrics.go @@ -27,7 +27,7 @@ type muxerMetrics struct { outBoundRateMax *prometheus.GaugeVec } -type tunnelMetrics struct { +type TunnelMetrics struct { haConnections prometheus.Gauge totalRequests prometheus.Counter requestsPerTunnel *prometheus.CounterVec @@ -229,7 +229,7 @@ func convertRTTMilliSec(t time.Duration) float64 { } // Metrics that can be collected without asking the edge -func NewTunnelMetrics() *tunnelMetrics { +func NewTunnelMetrics() *TunnelMetrics { haConnections := prometheus.NewGauge( prometheus.GaugeOpts{ Name: "ha_connections", @@ -305,7 +305,7 @@ func NewTunnelMetrics() *tunnelMetrics { ) prometheus.MustRegister(serverLocations) - return &tunnelMetrics{ + return &TunnelMetrics{ haConnections: haConnections, totalRequests: totalRequests, requestsPerTunnel: requestsPerTunnel, @@ -322,19 +322,19 @@ func NewTunnelMetrics() *tunnelMetrics { } } -func (t *tunnelMetrics) incrementHaConnections() { +func (t *TunnelMetrics) incrementHaConnections() { t.haConnections.Inc() } -func (t *tunnelMetrics) decrementHaConnections() { +func (t *TunnelMetrics) decrementHaConnections() { t.haConnections.Dec() } -func (t *tunnelMetrics) updateMuxerMetrics(connectionID string, metrics *h2mux.MuxerMetrics) { +func (t *TunnelMetrics) updateMuxerMetrics(connectionID string, metrics *h2mux.MuxerMetrics) { t.muxerMetrics.update(connectionID, metrics) } -func (t *tunnelMetrics) incrementRequests(connectionID string) { +func (t *TunnelMetrics) incrementRequests(connectionID string) { t.concurrentRequestsLock.Lock() var concurrentRequests uint64 var ok bool @@ -356,25 +356,25 @@ func (t *tunnelMetrics) incrementRequests(connectionID string) { t.concurrentRequestsPerTunnel.WithLabelValues(connectionID).Inc() } -func (t *tunnelMetrics) decrementConcurrentRequests(connectionID string) { +func (t *TunnelMetrics) decrementConcurrentRequests(connectionID string) { t.concurrentRequestsLock.Lock() if _, ok := t.concurrentRequests[connectionID]; ok { t.concurrentRequests[connectionID] -= 1 } else { - Log.Error("Concurrent requests per tunnel metrics went wrong; you can't decrement concurrent requests count without increment it first.") + logger.Error("Concurrent requests per tunnel metrics went wrong; you can't decrement concurrent requests count without increment it first.") } t.concurrentRequestsLock.Unlock() t.concurrentRequestsPerTunnel.WithLabelValues(connectionID).Dec() } -func (t *tunnelMetrics) incrementResponses(connectionID, code string) { +func (t *TunnelMetrics) incrementResponses(connectionID, code string) { t.responseByCode.WithLabelValues(code).Inc() t.responseCodePerTunnel.WithLabelValues(connectionID, code).Inc() } -func (t *tunnelMetrics) registerServerLocation(connectionID, loc string) { +func (t *TunnelMetrics) registerServerLocation(connectionID, loc string) { t.locationLock.Lock() defer t.locationLock.Unlock() if oldLoc, ok := t.oldServerLocations[connectionID]; ok && oldLoc == loc { diff --git a/origin/supervisor.go b/origin/supervisor.go index 4deb57c4..9a315ad8 100644 --- a/origin/supervisor.go +++ b/origin/supervisor.go @@ -73,7 +73,7 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal chan struct{}) err case tunnelError := <-s.tunnelErrors: tunnelsActive-- if tunnelError.err != nil { - Log.WithError(tunnelError.err).Warn("Tunnel disconnected due to error") + logger.WithError(tunnelError.err).Warn("Tunnel disconnected due to error") tunnelsWaiting = append(tunnelsWaiting, tunnelError.index) s.waitForNextTunnel(tunnelError.index) @@ -109,10 +109,10 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal chan struct{}) err s.lastResolve = time.Now() s.resolverC = nil if result.err == nil { - Log.Debug("Service discovery refresh complete") + logger.Debug("Service discovery refresh complete") s.edgeIPs = result.edgeIPs } else { - Log.WithError(result.err).Error("Service discovery error") + logger.WithError(result.err).Error("Service discovery error") } } } @@ -121,12 +121,12 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal chan struct{}) err func (s *Supervisor) initialize(ctx context.Context, connectedSignal chan struct{}) error { edgeIPs, err := ResolveEdgeIPs(s.config.EdgeAddrs) if err != nil { - Log.Infof("ResolveEdgeIPs err") + logger.Infof("ResolveEdgeIPs err") return err } s.edgeIPs = edgeIPs if s.config.HAConnections > len(edgeIPs) { - Log.Warnf("You requested %d HA connections but I can give you at most %d.", s.config.HAConnections, len(edgeIPs)) + logger.Warnf("You requested %d HA connections but I can give you at most %d.", s.config.HAConnections, len(edgeIPs)) s.config.HAConnections = len(edgeIPs) } s.lastResolve = time.Now() diff --git a/origin/tunnel.go b/origin/tunnel.go index 96d06469..8257d3d1 100644 --- a/origin/tunnel.go +++ b/origin/tunnel.go @@ -7,7 +7,6 @@ import ( "net" "net/http" "net/url" - "runtime" "strconv" "strings" "sync" @@ -28,7 +27,7 @@ import ( rpc "zombiezen.com/go/capnproto2/rpc" ) -var Log *logrus.Logger +var logger *logrus.Logger const ( dialTimeout = 15 * time.Second @@ -48,17 +47,19 @@ type TunnelConfig struct { HeartbeatInterval time.Duration MaxHeartbeats uint64 ClientID string + BuildInfo *BuildInfo ReportedVersion string LBPool string Tags []tunnelpogs.Tag HAConnections int HTTPTransport http.RoundTripper - Metrics *tunnelMetrics + Metrics *TunnelMetrics MetricsUpdateFreq time.Duration ProtocolLogger *logrus.Logger Logger *logrus.Logger IsAutoupdated bool GracePeriod time.Duration + RunFromTerminal bool } type dialError struct { @@ -92,18 +93,19 @@ func (c *TunnelConfig) RegistrationOptions(connectionID uint8, OriginLocalIP str return &tunnelpogs.RegistrationOptions{ ClientID: c.ClientID, Version: c.ReportedVersion, - OS: fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH), + OS: fmt.Sprintf("%s_%s", c.BuildInfo.GoOS, c.BuildInfo.GoArch), ExistingTunnelPolicy: policy, PoolName: c.LBPool, Tags: c.Tags, ConnectionID: connectionID, OriginLocalIP: OriginLocalIP, IsAutoupdated: c.IsAutoupdated, + RunFromTerminal: c.RunFromTerminal, } } func StartTunnelDaemon(config *TunnelConfig, shutdownC <-chan struct{}, connectedSignal chan struct{}) error { - Log = config.Logger + logger = config.Logger ctx, cancel := context.WithCancel(context.Background()) go func() { <-shutdownC @@ -138,7 +140,7 @@ func ServeTunnelLoop(ctx context.Context, config *TunnelConfig, addr *net.TCPAdd err, recoverable := ServeTunnel(ctx, config, addr, connectionID, connectedFuse, &backoff) if recoverable { if duration, ok := backoff.GetBackoffDuration(ctx); ok { - Log.Infof("Retrying in %s seconds", duration) + logger.Infof("Retrying in %s seconds", duration) backoff.Backoff(ctx) continue } @@ -171,7 +173,7 @@ func ServeTunnel( // Returns error from parsing the origin URL or handshake errors handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr.String(), connectionID) if err != nil { - errLog := Log.WithError(err) + errLog := logger.WithError(err) switch err.(type) { case dialError: errLog.Error("Unable to dial edge") @@ -218,21 +220,21 @@ func ServeTunnel( registerErr := <-registerErrC wg.Wait() if err != nil { - Log.WithError(err).Error("Tunnel error") + logger.WithError(err).Error("Tunnel error") return err, true } if registerErr != nil { // Don't retry on errors like entitlement failure or version too old if e, ok := registerErr.(printableRegisterTunnelError); ok { - Log.Error(e) + logger.Error(e) return e.cause, !e.permanent } else if e, ok := registerErr.(dupConnRegisterTunnelError); ok { - Log.Info("Already connected to this server, selecting a different one") + logger.Info("Already connected to this server, selecting a different one") return e, true } // Only log errors to Sentry that may have been caused by the client side, to reduce dupes raven.CaptureError(registerErr, nil) - Log.Error("Cannot register") + logger.Error("Cannot register") return err, true } return nil, false @@ -249,7 +251,7 @@ func IsRPCStreamResponse(headers []h2mux.Header) bool { } func RegisterTunnel(ctx context.Context, muxer *h2mux.Muxer, config *TunnelConfig, connectionID uint8, originLocalIP string) error { - logger := Log.WithField("subsystem", "rpc") + logger := logger.WithField("subsystem", "rpc") logger.Debug("initiating RPC stream to register") stream, err := muxer.OpenStream([]h2mux.Header{ {Name: ":method", Value: "RPC"}, @@ -304,7 +306,7 @@ func RegisterTunnel(ctx context.Context, muxer *h2mux.Muxer, config *TunnelConfi } func UnregisterTunnel(muxer *h2mux.Muxer, gracePeriod time.Duration) error { - logger := Log.WithField("subsystem", "rpc") + logger := logger.WithField("subsystem", "rpc") logger.Debug("initiating RPC stream to unregister") stream, err := muxer.OpenStream([]h2mux.Header{ {Name: ":method", Value: "RPC"}, @@ -335,7 +337,7 @@ func UnregisterTunnel(muxer *h2mux.Muxer, gracePeriod time.Duration) error { func LogServerInfo(logger *logrus.Entry, promise tunnelrpc.ServerInfo_Promise, connectionID uint8, - metrics *tunnelMetrics, + metrics *TunnelMetrics, ) { serverInfoMessage, err := promise.Struct() if err != nil { @@ -347,7 +349,7 @@ func LogServerInfo(logger *logrus.Entry, logger.WithError(err).Warn("Failed to retrieve server information") return } - Log.Infof("Connected to %s", serverInfo.LocationName) + logger.Infof("Connected to %s", serverInfo.LocationName) metrics.registerServerLocation(uint8ToString(connectionID), serverInfo.LocationName) } @@ -398,7 +400,7 @@ type TunnelHandler struct { httpClient http.RoundTripper tlsConfig *tls.Config tags []tunnelpogs.Tag - metrics *tunnelMetrics + metrics *TunnelMetrics // connectionID is only used by metrics, and prometheus requires labels to be string connectionID string } @@ -407,12 +409,12 @@ var dialer = net.Dialer{DualStack: true} // NewTunnelHandler returns a TunnelHandler, origin LAN IP and error func NewTunnelHandler(ctx context.Context, config *TunnelConfig, addr string, connectionID uint8) (*TunnelHandler, string, error) { - url, err := validation.ValidateUrl(config.OriginUrl) + originURL, err := validation.ValidateUrl(config.OriginUrl) if err != nil { - return nil, "", fmt.Errorf("Unable to parse origin url %#v", url) + return nil, "", fmt.Errorf("Unable to parse origin url %#v", originURL) } h := &TunnelHandler{ - originUrl: url, + originUrl: originURL, httpClient: config.HTTPTransport, tlsConfig: config.ClientTlsConfig, tags: config.Tags, @@ -464,11 +466,11 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error { h.metrics.incrementRequests(h.connectionID) req, err := http.NewRequest("GET", h.originUrl, h2mux.MuxedStreamReader{MuxedStream: stream}) if err != nil { - Log.WithError(err).Panic("Unexpected error from http.NewRequest") + logger.WithError(err).Panic("Unexpected error from http.NewRequest") } err = H2RequestHeadersToH1Request(stream.Headers, req) if err != nil { - Log.WithError(err).Error("invalid request received") + logger.WithError(err).Error("invalid request received") } h.AppendTagHeaders(req) cfRay := FindCfRayHeader(req) @@ -501,7 +503,7 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error { } func (h *TunnelHandler) logError(stream *h2mux.MuxedStream, err error) { - Log.WithError(err).Error("HTTP request error") + logger.WithError(err).Error("HTTP request error") stream.WriteHeaders([]h2mux.Header{{Name: ":status", Value: "502"}}) stream.Write([]byte("502 Bad Gateway")) h.metrics.incrementResponses(h.connectionID, "502") @@ -509,20 +511,20 @@ func (h *TunnelHandler) logError(stream *h2mux.MuxedStream, err error) { func (h *TunnelHandler) logRequest(req *http.Request, cfRay string) { if cfRay != "" { - Log.WithField("CF-RAY", cfRay).Infof("%s %s %s", req.Method, req.URL, req.Proto) + logger.WithField("CF-RAY", cfRay).Infof("%s %s %s", req.Method, req.URL, req.Proto) } else { - Log.Warnf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", req.Method, req.URL, req.Proto) + logger.Warnf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", req.Method, req.URL, req.Proto) } - Log.Debugf("Request Headers %+v", req.Header) + logger.Debugf("Request Headers %+v", req.Header) } func (h *TunnelHandler) logResponse(r *http.Response, cfRay string) { if cfRay != "" { - Log.WithField("CF-RAY", cfRay).Infof("%s", r.Status) + logger.WithField("CF-RAY", cfRay).Infof("%s", r.Status) } else { - Log.Infof("%s", r.Status) + logger.Infof("%s", r.Status) } - Log.Debugf("Response Headers %+v", r.Header) + logger.Debugf("Response Headers %+v", r.Header) } func (h *TunnelHandler) UpdateMetrics(connectionID string) { diff --git a/tlsconfig/tlsconfig.go b/tlsconfig/tlsconfig.go index d81e94f2..ba76cf97 100644 --- a/tlsconfig/tlsconfig.go +++ b/tlsconfig/tlsconfig.go @@ -8,10 +8,12 @@ import ( "io/ioutil" "net" - log "github.com/sirupsen/logrus" - cli "gopkg.in/urfave/cli.v2" + "github.com/cloudflare/cloudflared/log" + "gopkg.in/urfave/cli.v2" ) +var logger = log.CreateLogger() + // CLIFlags names the flags used to configure TLS for a command or subsystem. // The nil value for a field means the flag is ignored. type CLIFlags struct { @@ -29,7 +31,7 @@ func (f CLIFlags) GetConfig(c *cli.Context) *tls.Config { if c.IsSet(f.Cert) && c.IsSet(f.Key) { cert, err := tls.LoadX509KeyPair(c.String(f.Cert), c.String(f.Key)) if err != nil { - log.WithError(err).Fatal("Error parsing X509 key pair") + logger.WithError(err).Fatal("Error parsing X509 key pair") } config.Certificates = []tls.Certificate{cert} config.BuildNameToCertificate() @@ -53,11 +55,11 @@ func (f CLIFlags) GetConfig(c *cli.Context) *tls.Config { func LoadCert(certPath string) *x509.CertPool { caCert, err := ioutil.ReadFile(certPath) if err != nil { - log.WithError(err).Fatalf("Error reading certificate %s", certPath) + logger.WithError(err).Fatalf("Error reading certificate %s", certPath) } ca := x509.NewCertPool() if !ca.AppendCertsFromPEM(caCert) { - log.WithError(err).Fatalf("Error parsing certificate %s", certPath) + logger.WithError(err).Fatalf("Error parsing certificate %s", certPath) } return ca } @@ -66,23 +68,23 @@ func LoadOriginCertsPool() *x509.CertPool { // First, obtain the system certificate pool certPool, systemCertPoolErr := x509.SystemCertPool() if systemCertPoolErr != nil { - log.Warn("error obtaining the system certificates: %s", systemCertPoolErr) + logger.Warnf("error obtaining the system certificates: %s", systemCertPoolErr) certPool = x509.NewCertPool() } // Next, append the Cloudflare CA pool into the system pool if !certPool.AppendCertsFromPEM([]byte(cloudflareRootCA)) { - log.Warn("could not append the CF certificate to the system certificate pool") + logger.Warn("could not append the CF certificate to the system certificate pool") if systemCertPoolErr != nil { // Obtaining both certificates failed; this is a fatal error - log.WithError(systemCertPoolErr).Fatalf("Error loading the certificate pool") + logger.WithError(systemCertPoolErr).Fatalf("Error loading the certificate pool") } } // Finally, add the Hello certificate into the pool (since it's self-signed) helloCertificate, err := GetHelloCertificateX509() if err != nil { - log.Warn("error obtaining the Hello server certificate") + logger.Warn("error obtaining the Hello server certificate") } certPool.AddCert(helloCertificate) diff --git a/tunneldns/tunnel.go b/tunneldns/tunnel.go index efd1229d..13db1fb6 100644 --- a/tunneldns/tunnel.go +++ b/tunneldns/tunnel.go @@ -8,16 +8,18 @@ import ( "sync" "syscall" - "gopkg.in/urfave/cli.v2" - + "github.com/cloudflare/cloudflared/log" "github.com/cloudflare/cloudflared/metrics" + "github.com/coredns/coredns/core/dnsserver" "github.com/coredns/coredns/plugin" "github.com/coredns/coredns/plugin/cache" "github.com/pkg/errors" - log "github.com/sirupsen/logrus" + "gopkg.in/urfave/cli.v2" ) +var logger = log.CreateLogger() + // Listener is an adapter between CoreDNS server and Warp runnable type Listener struct { server *dnsserver.Server @@ -28,14 +30,14 @@ type Listener struct { func Run(c *cli.Context) error { metricsListener, err := net.Listen("tcp", c.String("metrics")) if err != nil { - log.WithError(err).Fatal("Failed to open the metrics listener") + logger.WithError(err).Fatal("Failed to open the metrics listener") } - go metrics.ServeMetrics(metricsListener, nil) + go metrics.ServeMetrics(metricsListener, nil, logger) listener, err := CreateListener(c.String("address"), uint16(c.Uint("port")), c.StringSlice("upstream")) if err != nil { - log.WithError(err).Errorf("Failed to create the listeners") + logger.WithError(err).Errorf("Failed to create the listeners") return err } @@ -43,7 +45,7 @@ func Run(c *cli.Context) error { readySignal := make(chan struct{}) err = listener.Start(readySignal) if err != nil { - log.WithError(err).Errorf("Failed to start the listeners") + logger.WithError(err).Errorf("Failed to start the listeners") return listener.Stop() } <-readySignal @@ -57,7 +59,7 @@ func Run(c *cli.Context) error { // Shut down server err = listener.Stop() if err != nil { - log.WithError(err).Errorf("failed to stop") + logger.WithError(err).Errorf("failed to stop") } return err } @@ -78,7 +80,7 @@ func createConfig(address string, port uint16, p plugin.Handler) *dnsserver.Conf // Start blocks for serving requests func (l *Listener) Start(readySignal chan struct{}) error { defer close(readySignal) - log.WithField("addr", l.server.Address()).Infof("Starting DNS over HTTPS proxy server") + logger.WithField("addr", l.server.Address()).Infof("Starting DNS over HTTPS proxy server") // Start UDP listener if udp, err := l.server.ListenPacket(); err == nil { @@ -119,7 +121,7 @@ func CreateListener(address string, port uint16, upstreams []string) (*Listener, // Build the list of upstreams upstreamList := make([]Upstream, 0) for _, url := range upstreams { - log.WithField("url", url).Infof("Adding DNS upstream") + logger.WithField("url", url).Infof("Adding DNS upstream") upstream, err := NewUpstreamHTTPS(url) if err != nil { return nil, errors.Wrap(err, "failed to create HTTPS upstream") diff --git a/tunnelrpc/pogs/tunnelrpc.go b/tunnelrpc/pogs/tunnelrpc.go index ab14060a..8d297a2f 100644 --- a/tunnelrpc/pogs/tunnelrpc.go +++ b/tunnelrpc/pogs/tunnelrpc.go @@ -52,7 +52,7 @@ type RegistrationOptions struct { ConnectionID uint8 `capnp:"connectionId"` OriginLocalIP string `capnp:"originLocalIp"` IsAutoupdated bool `capnp:"isAutoupdated"` - gracePeriodNanoSec int64 `capnp:"gracePeriodNanoSec"` + RunFromTerminal bool `capnp:"runFromTerminal"` } func MarshalRegistrationOptions(s tunnelrpc.RegistrationOptions, p *RegistrationOptions) error { diff --git a/tunnelrpc/tunnelrpc.capnp b/tunnelrpc/tunnelrpc.capnp index d90ef200..cbc25755 100644 --- a/tunnelrpc/tunnelrpc.capnp +++ b/tunnelrpc/tunnelrpc.capnp @@ -37,6 +37,8 @@ struct RegistrationOptions { originLocalIp @7 :Text; # whether Argo Tunnel client has been autoupdated isAutoupdated @8 :Bool; + # whether Argo Tunnel client is run from a terminal + runFromTerminal @9 :Bool; } struct Tag { diff --git a/tunnelrpc/tunnelrpc.capnp.go b/tunnelrpc/tunnelrpc.capnp.go index 0b54a66b..112b59d1 100644 --- a/tunnelrpc/tunnelrpc.capnp.go +++ b/tunnelrpc/tunnelrpc.capnp.go @@ -403,6 +403,14 @@ func (s RegistrationOptions) SetIsAutoupdated(v bool) { s.Struct.SetBit(24, v) } +func (s RegistrationOptions) RunFromTerminal() bool { + return s.Struct.Bit(25) +} + +func (s RegistrationOptions) SetRunFromTerminal(v bool) { + s.Struct.SetBit(25, v) +} + // RegistrationOptions_List is a list of RegistrationOptions. type RegistrationOptions_List struct{ capnp.List } @@ -1218,86 +1226,88 @@ func (p TunnelServer_unregisterTunnel_Results_Promise) Struct() (TunnelServer_un return TunnelServer_unregisterTunnel_Results{s}, err } -const schema_db8274f9144abc7e = "x\xda\x9cU_\x88Te\x1b\x7f~\xef;3g\x84" + - "]g\x0fg\x04\xbf\xe1\x93\xe5\x93\x15\xff\xe0\xfa\xe9\xa7" + - "\xfb\xa1\xdb\x9f\xd9\xdd\xd4\x98m]\xe7\xdd\xd1\x10\xf5\xc2" + - "\xe3\xcc\xeb\xec\xd9f\xce\x19\xce9c)\xa4%B " + - "$\x96uc\x14\xe4e\x17\x15t\x11\x84\x81\xdd$\xe1" + - "\x85\x08\x19EB\x94l\xa1(\xd6\xa2\x90\x91\x9cx\xcf" + - "\xec\x999\xae\xa5\xd6\xdd9\xcf\xfb>\xcf\xf3{\xfe\xfc" + - "~\xef\xea\x1f\xd9\x10[\x93\xfc.I$6&S\xc1" + - "\xe3\xf5\x0b\xa7\xff\xff\xe6\xf9\xa3\xa4\xe7Xp\xe8\xcch" + - "\xf6\x8e\x7f\xe4[\"\xac\xbd\xc4\x0e\xc2\xb8\xca4\"c" + - "\x9am%\x04\x17V\x9e\xf9\xe4\xc4G\xaf\xbcEb)" + - "@\x94\xd0\x88\xd6\xdee\xbf\x81`\xe8n\x9d$\xb9:\xea\xe7\xd7" + - "\x94\xeb0\xff\x80\x10,\xba1\xd2m\xdf\xba\x89\xa1\x9b" + - "\x10L:\x9eo\x9buID\xe8\"\x86.\xc2a\xa7" + - "\xe1[\x8e\xed\xa1\xa7C\x07\x02zb-\xf8\x93\x01\x0f" + - "7\xfdIi\xfbV\xd9T\xceD\xe1l;\x90\x17\x13" + - "\x89!\x0e1\x16\x83\\\xf8_\xac\x8e\x08\xf2\x96\xbd\x9d" + - ":\xb4\xe7\xe4\x81\x08U\xaf\xac\x9bV-\xfa\x8b\x8a\x19" + - "&\xed\x99\xce\x9d\x07\xe1\x9b\x08\xbb\xea\x86\xe8\xb66z" + - "\xc3\x0a\x15\xc6\xbe6\xc6\x1b\xaa\x83\xd79\xc4\xaf1\x8c" + - "\xb7U\x07\x7f\xe6\x10\xbf\xc70\xde\xc9\x11\x89[\x1c\x13" + - "`\x00\xcf\x82\x13\xe9w\xdf#\x9a\x00G\xa9\x0b\x0cz" + - "\x82g\x91 2\xe6a\x94\xa8\x94V\xf6\xac\xb2'\x13" + - "Y$\x15\xb7\xb0\x82\xa8\xd4\xa5\xec\xcb\x94=\xc5\xb2H" + - "\x11\x19K0ET\xeaS\xf6\xd5\xca\xae%\xb3\x8a\x96" + - "F?\\\xa2\xd2Je_\xaf\xec\xe9\x85Y\xa4\x89\x8c" + - "\x81\xd0\xbeN\xd9\x87\xc0\x10\x94k\x96\xb4\xfdB%>" + - "\xce\xfd\xd2\xf5,\xc7\x8e\xfe\xb9\xe3\xb5\xfb%gY\x89" + - "\xd6\xae\x15\x9d\x8c\xa2%2\x1d\xe9& C\x08\x1a\x8e" + - "S\x1b\xbfwM2\xbeY\xf50\x9fP\xe4@OG" + - "\x0a\x09\xca\x18\x84\xa4-\xfb\x16e\x1c\xbbPA\x8a\x18" + - "R\xed\xb9\x8d9\xd4[6k\x85F\x1b\x89\xe5\x0d7" + - "}\xa7\xd9\xa0\xde\x8a\xe9\xcb\x0a@\x0c\x88M\x94\xcd\x9d" + - "hocp\x9bYU\x13L\xb7'\xb8|\x05\x91\xe8" + - "\xe3\x10\xabc\x13\xecW[\xb6\x8cC\xacc\xc8\xa8U" + - "oo\xd4~\xb3\xd6\x94\xf7\xed\xce\xc34\xa5*\xfd\xd6" + - "W\xc1\xde\xe7\xf4\x15MW3\xeb\xde?\xf4\x9e\x90^" + - "FIC\\\x8f\x06\x89D\x9aCd\x19\xf2n\xa8\x1c" + - "\xe8\xe9H\xf6\x1c\"\xf2\xbfJ\x97oei\xb10I" + - "\xd4~'\x11=\x0f\xba8HL/h\xe8\xa0\x81\xb5\x9ffDO\xb0\xbe" + - "\xfc\x181}\x89\x16D:E\xf9V\xca!\x04Qu" + - "\xd4\x1b\xd67\x84 \x12CDzF4\x84\"\x1e\xbd" + - "\xdd\xf7ii\xaf\xf7(\x1d\x8b\xde\xad\x87\xf7\xab\x95'" + - "\xa3\xf0\xaan\xc5\xe2N\x11\x89.\x0e\xb1\x90!\xa89" + - "\xb3\xc2\x96\x19\x8f\xad\xd0\x83\x04\xa7\x058\x92\x9d\x8cr" + - "V\xf1{\xda\xf1M\xa5\x89\xbb9\xc4dl[\xa52" + - "\xee\xe1\x10\xb5\x98\xdeXJ\x99&9\xc4\xd1\x8e\xde\xbc" + - "|\x8cH\x1c\xe5\x10'\x184\xe9\xba\x11$\xad\xe9v" + - "T\xb2\xe6T\xc7,[z\x8a\xb6\xb3LUG\x8a\x9f" + - "\x0d\xe9\xd6M[\xda\xf07\x9bV\xad\xe9J\xb5Z-" + - "\xd2\xfd\x11\x00\x00\xff\xffy%\x9bh" +const schema_db8274f9144abc7e = "x\xda\x9cU_l\x14U\x17\xff\x9d{w;\xdb\xa4" + + "e;\x99m\x02\x1bH\x13\xd2\x86B\xbe\xf2\xc1\xc7\x87" + + "B\xfd\xd3?\x02\xba\xb5\x94\xbdl1\x08}`\xd8\xbd" + + "l\xa7\xce\xcelffQH\x04%\x18#\x89\x04E" + + "^0\x9a@\xe2\x8b\x0fj\xe2\x83\x89\xc1\x04_\xe4\x81" + + "\x07^\xd4h$1J\x88\x91h\x88\x8d\x9ahb\xc6" + + "\xdc\xd9\xce\xecR\x14\xd0\xb7\x99\xdf=\xf7\x9c\xdf9\xf7" + + "\x9c\xdfY\xf7+\x1be\xeb\xd3_\xa7\x01\xb1%\xdd\x11" + + ">X\xbbr\xfe\xbe3\x97\x8fC\xcf\xb3\xf0\xc8\x85\x89" + + "\xdco\xc1\xb1\xaf\x00\xda\xf0\x19;L\xc6\xf7L\x03\x8c" + + "\xebl\x07(\xbc\xf2\x9f\x0b\x1f\x9ez\xff\xc5\xd7!V" + + "\x11\x01)\x0d\xd8\xf0\x07\xfb\x9d@\x86\xceG@\xe1k" + + "_|4U{\xe5\xecy\xe8\xab\xe2\xf3\xcd\x9c1\xa4" + + "\xc2\xde\x02]\xbd\xb8>\xf5A\xf3$\xcd\xd5\xd1\x10\xbf" + + "\xa1\xae\x8e\xf1wA\xe1\x8a\x1f\xc7\xbb\x9d\x9b\xc7.B" + + "\xcfS\x8bE\xd3\xf0\x1b>A\xc6/\xea\xd3\xf8)2" + + "\x9e\xd8{\xfa\xd5\xf4\xf5\xd3\x97 \xf2\xd4n\xdd\xa1\xac" + + "_Nyd\x9c\x8b\x82\xbf\x91\xba\x9f\x81\xc2\xfc{\x0f" + + "\xbc3^\xf9\xf2\xf2\"\xdfQf\x9d\xda\xbc\xd1\xab\xa9" + + "/]{\x1a\x14\xb2\xeb\xe6\xb2\xe7>\x7f\xf8j[\x0a" + + "5\xed[B*\x9czb\xef\\\xe7\xb3\xd7\xae-\xa4" + + "@\xea\xc8\xd4\xa2\x14\x1a\x9a\xca~\xe3\xbe19\xb3i" + + "\xf7\x0d\xe8y~K!\xcfh\xc3d\xbc\x15\x059\xa7" + + "]2Vg4 \xa0\x85\xb1\xc8a\xa4\x19r" + + "\x94\xc28;\xf4E\xf9\x8dR\x18+)\xc5b\x08\x8c" + + "R\x91\xee\xbd\xdc\xb7\x09q\x9f\x7f/\x15\x8b\x97\xde\xdd" + + "\xeb\xd5\x8c\x93U|U\xb5\xda\xfc\xce\x01\xa2\x8b\x93X" + + "\xca(\xb4\xdd\x05U\xccN\xb5\xb5\xd0\x9d\xd4\xaaI8" + + "\xd6\xac\xac\xba\xac\xfc\xf7$\xfeM%\xa83\x9c\xc4l" + + "[\xb7J\x05\xee\xe3$\xec6A\xb5\xd4b\x98\xe5$" + + "\x8e'\x1a\xa5?\x7f\x02\x10\xc79\x89S\x8c4\xe9y" + + "1%\xad\xe1\xb5$\xd6v\xab\x93\x96#}5\xd0\x0b" + + "3\xac\x8e\xd4\xe4\xd6\xa5W3\x1d\xe9P\xb0\xcd\xb4\xec" + + "\x86'\x91\x0c\xdd\x9f\x01\x00\x00\xff\xffR3\x9d#" func init() { schemas.Register(schema_db8274f9144abc7e,