Release Argo Tunnel Client 2018.5.0
This commit is contained in:
		
							parent
							
								
									a1a45b0f63
								
							
						
					
					
						commit
						9135a4837c
					
				| 
						 | 
				
			
			@ -0,0 +1,297 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cloudflare/cloudflared/origin"
 | 
			
		||||
	"github.com/cloudflare/cloudflared/tlsconfig"
 | 
			
		||||
	tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
 | 
			
		||||
	"github.com/cloudflare/cloudflared/validation"
 | 
			
		||||
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"gopkg.in/urfave/cli.v2"
 | 
			
		||||
	"gopkg.in/urfave/cli.v2/altsrc"
 | 
			
		||||
 | 
			
		||||
	"github.com/mitchellh/go-homedir"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	defaultConfigFiles = []string{"config.yml", "config.yaml"}
 | 
			
		||||
 | 
			
		||||
	// Launchd doesn't set root env variables, so there is default
 | 
			
		||||
	// Windows default config dir was ~/cloudflare-warp in documentation; let's keep it compatible
 | 
			
		||||
	defaultConfigDirs = []string{"~/.cloudflared", "~/.cloudflare-warp", "~/cloudflare-warp", "/usr/local/etc/cloudflared", "/etc/cloudflared"}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const defaultCredentialFile = "cert.pem"
 | 
			
		||||
 | 
			
		||||
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, defaultCredentialFile))
 | 
			
		||||
		if ok, _ := fileExists(originCertPath); ok {
 | 
			
		||||
			return originCertPath
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// returns the first 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 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			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 enoughOptionsSet(c *cli.Context) bool {
 | 
			
		||||
	// For cloudflared to work, the user needs to at least provide a hostname,
 | 
			
		||||
	// or runs as stand alone DNS proxy .
 | 
			
		||||
	// When using sudo, use -E flag to preserve env vars
 | 
			
		||||
	if c.NumFlags() == 0 && c.NArg() == 0 && os.Getenv("TUNNEL_HOSTNAME") == "" && os.Getenv("TUNNEL_DNS") == "" {
 | 
			
		||||
		if isRunningFromTerminal() {
 | 
			
		||||
			logger.Errorf("No arguments were provided. You need to at least specify the hostname for this tunnel. See %s", quickStartUrl)
 | 
			
		||||
			logger.Infof("If you want to run Argo Tunnel client as a stand alone DNS proxy, run with --proxy-dns option or set TUNNEL_DNS environment variable.")
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.Errorf("You need to specify all the options in a configuration file, or use environment variables. See %s and %s", serviceUrl, argumentsUrl)
 | 
			
		||||
			logger.Infof("If you want to run Argo Tunnel client as a stand alone DNS proxy, specify proxy-dns option in the configuration file, or set TUNNEL_DNS environment variable.")
 | 
			
		||||
		}
 | 
			
		||||
		cli.ShowAppHelp(c)
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func handleDeprecatedOptions(c *cli.Context) {
 | 
			
		||||
	// Fail if the user provided an old authentication method
 | 
			
		||||
	if c.IsSet("api-key") || c.IsSet("api-email") || c.IsSet("api-ca-key") {
 | 
			
		||||
		logger.Fatal("You don't need to give us your api-key anymore. Please use the new login method. Just run cloudflared login")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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 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_
 | 
			
		||||
		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"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getOriginCert(c *cli.Context) []byte {
 | 
			
		||||
	if c.String("origincert") == "" {
 | 
			
		||||
		logger.Warnf("Cannot determine default origin certificate path. No file %s in %v", defaultCredentialFile, defaultConfigDirs)
 | 
			
		||||
		if isRunningFromTerminal() {
 | 
			
		||||
			logger.Fatalf("You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", argumentsUrl)
 | 
			
		||||
		} else {
 | 
			
		||||
			logger.Fatalf("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)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	// Check that the user has acquired a certificate using the login command
 | 
			
		||||
	originCertPath, err := homedir.Expand(c.String("origincert"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatalf("Cannot resolve path %s", c.String("origincert"))
 | 
			
		||||
	}
 | 
			
		||||
	ok, err := fileExists(originCertPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Fatalf("Cannot check if origin cert exists at path %s", c.String("origincert"))
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		logger.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 {
 | 
			
		||||
		logger.WithError(err).Fatalf("Cannot read %s to load origin certificate", originCertPath)
 | 
			
		||||
	}
 | 
			
		||||
	return originCert
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, logger, protoLogger *logrus.Logger) *origin.TunnelConfig {
 | 
			
		||||
	hostname, err := validation.ValidateHostname(c.String("hostname"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Invalid hostname")
 | 
			
		||||
	}
 | 
			
		||||
	clientID := c.String("id")
 | 
			
		||||
	if !c.IsSet("id") {
 | 
			
		||||
		clientID = generateRandomClientID()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Tag parse failure")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID})
 | 
			
		||||
 | 
			
		||||
	url, err := validateUrl(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Error validating url")
 | 
			
		||||
	}
 | 
			
		||||
	logger.Infof("Proxying tunnel requests to %s", url)
 | 
			
		||||
 | 
			
		||||
	originCert := getOriginCert(c)
 | 
			
		||||
 | 
			
		||||
	originCertPool, err := loadCertPool(c)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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: originCertPool},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !c.IsSet("hello-world") && c.IsSet("origin-server-name") {
 | 
			
		||||
		httpTransport.TLSClientConfig.ServerName = c.String("origin-server-name")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &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,
 | 
			
		||||
		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(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadCertPool(c *cli.Context) (*x509.CertPool, error) {
 | 
			
		||||
	const originCAPoolFlag = "origin-ca-pool"
 | 
			
		||||
	originCAPoolFilename := c.String(originCAPoolFlag)
 | 
			
		||||
	var originCustomCAPool []byte
 | 
			
		||||
 | 
			
		||||
	if originCAPoolFilename != "" {
 | 
			
		||||
		var err error
 | 
			
		||||
		originCustomCAPool, err = ioutil.ReadFile(originCAPoolFilename)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, errors.Wrap(err, fmt.Sprintf("unable to read the file %s for --%s", originCAPoolFilename, originCAPoolFlag))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	originCertPool, err := tlsconfig.LoadOriginCertPool(originCustomCAPool)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, errors.Wrap(err, "error loading the certificate pool")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return originCertPool, nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,6 +8,6 @@ import (
 | 
			
		|||
	cli "gopkg.in/urfave/cli.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runApp(app *cli.App) {
 | 
			
		||||
func runApp(app *cli.App, shutdownC chan struct{}) {
 | 
			
		||||
	app.Run(os.Args)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,204 +1,21 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"gopkg.in/urfave/cli.v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/cloudflare/cloudflared/tlsconfig"
 | 
			
		||||
	"github.com/cloudflare/cloudflared/hello"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type templateData struct {
 | 
			
		||||
	ServerName string
 | 
			
		||||
	Request    *http.Request
 | 
			
		||||
	Body       string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type OriginUpTime struct {
 | 
			
		||||
	StartTime time.Time `json:"startTime"`
 | 
			
		||||
	UpTime    string    `json:"uptime"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultServerName = "the Argo Tunnel test server"
 | 
			
		||||
const indexTemplate = `
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
 | 
			
		||||
    <title>
 | 
			
		||||
      Argo Tunnel Connection
 | 
			
		||||
    </title>
 | 
			
		||||
    <meta name="author" content="">
 | 
			
		||||
    <meta name="description" content="Argo Tunnel Connection">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
    <style>
 | 
			
		||||
      html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}section{display:block}h1{font-size:2em;margin:.67em 0}a{background-color:transparent;-webkit-text-decoration-skip:objects}/* 1 */::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}/* 1 */a,body,dd,div,dl,dt,h1,h4,html,p,section{box-sizing:border-box}.bt{border-top-style:solid;border-top-width:1px}.bl{border-left-style:solid;border-left-width:1px}.b--orange{border-color:#f38020}.br1{border-radius:.125rem}.bw2{border-width:.25rem}.dib{display:inline-block}.sans-serif{font-family:open sans,-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.code{font-family:Consolas,monaco,monospace}.b{font-weight:700}.fw3{font-weight:300}.fw4{font-weight:400}.fw5{font-weight:500}.fw6{font-weight:600}.lh-copy{line-height:1.5}.link{text-decoration:none}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{transition:color .15s ease-in}.link:focus{outline:1px dotted currentColor}.mw-100{max-width:100%}.mw4{max-width:8rem}.mw7{max-width:48rem}.bg-light-gray{background-color:#f7f7f7}.link-hover:hover{background-color:#1f679e}.white{color:#fff}.bg-white{background-color:#fff}.bg-blue{background-color:#408bc9}.pb2{padding-bottom:.5rem}.pb6{padding-bottom:8rem}.pt3{padding-top:1rem}.pt5{padding-top:4rem}.pv2{padding-top:.5rem;padding-bottom:.5rem}.ph3{padding-left:1rem;padding-right:1rem}.ph4{padding-left:2rem;padding-right:2rem}.ml0{margin-left:0}.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:1rem}.mt5{margin-top:4rem}.ttu{text-transform:uppercase}.f4{font-size:1.25rem}.f5{font-size:1rem}.f6{font-size:.875rem}.f7{font-size:.75rem}.measure{max-width:30em}.center{margin-left:auto}.center{margin-right:auto}@media screen and (min-width:30em){.f2-ns{font-size:2.25rem}}@media screen and (min-width:30em) and (max-width:60em){.f5-m{font-size:1rem}}@media screen and (min-width:60em){.f4-l{font-size:1.25rem}}
 | 
			
		||||
    .st0{fill:#FFF}.st1{fill:#f48120}.st2{fill:#faad3f}.st3{fill:#404041}
 | 
			
		||||
    </style>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body class="sans-serif black">
 | 
			
		||||
    <div class="bt bw2 b--orange bg-white pb6">
 | 
			
		||||
      <div class="mw7 center ph4 pt3">
 | 
			
		||||
        <svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109 40.5" class="mw4">
 | 
			
		||||
          <path class="st0" d="M98.6 14.2L93 12.9l-1-.4-25.7.2v12.4l32.3.1z"/>
 | 
			
		||||
          <path class="st1" d="M88.1 24c.3-1 .2-2-.3-2.6-.5-.6-1.2-1-2.1-1.1l-17.4-.2c-.1 0-.2-.1-.3-.1-.1-.1-.1-.2 0-.3.1-.2.2-.3.4-.3l17.5-.2c2.1-.1 4.3-1.8 5.1-3.8l1-2.6c0-.1.1-.2 0-.3-1.1-5.1-5.7-8.9-11.1-8.9-5 0-9.3 3.2-10.8 7.7-1-.7-2.2-1.1-3.6-1-2.4.2-4.3 2.2-4.6 4.6-.1.6 0 1.2.1 1.8-3.9.1-7.1 3.3-7.1 7.3 0 .4 0 .7.1 1.1 0 .2.2.3.3.3h32.1c.2 0 .4-.1.4-.3l.3-1.1z"/>
 | 
			
		||||
          <path class="st2" d="M93.6 12.8h-.5c-.1 0-.2.1-.3.2l-.7 2.4c-.3 1-.2 2 .3 2.6.5.6 1.2 1 2.1 1.1l3.7.2c.1 0 .2.1.3.1.1.1.1.2 0 .3-.1.2-.2.3-.4.3l-3.8.2c-2.1.1-4.3 1.8-5.1 3.8l-.2.9c-.1.1 0 .3.2.3h13.2c.2 0 .3-.1.3-.3.2-.8.4-1.7.4-2.6 0-5.2-4.3-9.5-9.5-9.5"/>
 | 
			
		||||
          <path class="st3" d="M104.4 30.8c-.5 0-.9-.4-.9-.9s.4-.9.9-.9.9.4.9.9-.4.9-.9.9m0-1.6c-.4 0-.7.3-.7.7 0 .4.3.7.7.7.4 0 .7-.3.7-.7 0-.4-.3-.7-.7-.7m.4 1.2h-.2l-.2-.3h-.2v.3h-.2v-.9h.5c.2 0 .3.1.3.3 0 .1-.1.2-.2.3l.2.3zm-.3-.5c.1 0 .1 0 .1-.1s-.1-.1-.1-.1h-.3v.3h.3zM14.8 29H17v6h3.8v1.9h-6zM23.1 32.9c0-2.3 1.8-4.1 4.3-4.1s4.2 1.8 4.2 4.1-1.8 4.1-4.3 4.1c-2.4 0-4.2-1.8-4.2-4.1m6.3 0c0-1.2-.8-2.2-2-2.2s-2 1-2 2.1.8 2.1 2 2.1c1.2.2 2-.8 2-2M34.3 33.4V29h2.2v4.4c0 1.1.6 1.7 1.5 1.7s1.5-.5 1.5-1.6V29h2.2v4.4c0 2.6-1.5 3.7-3.7 3.7-2.3-.1-3.7-1.2-3.7-3.7M45 29h3.1c2.8 0 4.5 1.6 4.5 3.9s-1.7 4-4.5 4h-3V29zm3.1 5.9c1.3 0 2.2-.7 2.2-2s-.9-2-2.2-2h-.9v4h.9zM55.7 29H62v1.9h-4.1v1.3h3.7V34h-3.7v2.9h-2.2zM65.1 29h2.2v6h3.8v1.9h-6zM76.8 28.9H79l3.4 8H80l-.6-1.4h-3.1l-.6 1.4h-2.3l3.4-8zm2 4.9l-.9-2.2-.9 2.2h1.8zM85.2 29h3.7c1.2 0 2 .3 2.6.9.5.5.7 1.1.7 1.8 0 1.2-.6 2-1.6 2.4l1.9 2.8H90l-1.6-2.4h-1v2.4h-2.2V29zm3.6 3.8c.7 0 1.2-.4 1.2-.9 0-.6-.5-.9-1.2-.9h-1.4v1.9h1.4zM95.3 29h6.4v1.8h-4.2V32h3.8v1.8h-3.8V35h4.3v1.9h-6.5zM10 33.9c-.3.7-1 1.2-1.8 1.2-1.2 0-2-1-2-2.1s.8-2.1 2-2.1c.9 0 1.6.6 1.9 1.3h2.3c-.4-1.9-2-3.3-4.2-3.3-2.4 0-4.3 1.8-4.3 4.1s1.8 4.1 4.2 4.1c2.1 0 3.7-1.4 4.2-3.2H10z"/>
 | 
			
		||||
        </svg>
 | 
			
		||||
        <h1 class="f4 f2-ns mt5 fw5">Congrats! You created your first tunnel!</h1>
 | 
			
		||||
        <p class="f6 f5-m f4-l measure lh-copy fw3">
 | 
			
		||||
          Argo Tunnel exposes locally running applications to the internet by
 | 
			
		||||
          running an encrypted, virtual tunnel from your laptop or server to
 | 
			
		||||
          Cloudflare's edge network.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p class="b f5 mt5 fw6">Ready for the next step?</p>
 | 
			
		||||
        <a
 | 
			
		||||
          class="fw6 link white bg-blue ph4 pv2 br1 dib f5 link-hover"
 | 
			
		||||
          style="border-bottom: 1px solid #1f679e"
 | 
			
		||||
          href="https://developers.cloudflare.com/argo-tunnel/">
 | 
			
		||||
          Get started here
 | 
			
		||||
        </a>
 | 
			
		||||
       <section>
 | 
			
		||||
          <h4 class="f6 fw4 pt5 mb2">Request</h4>
 | 
			
		||||
          <dl class="bl bw2 b--orange ph3 pt3 pb2 bg-light-gray f7 code overflow-x-auto mw-100">
 | 
			
		||||
						<dd class="ml0 mb3 f5">Method: {{.Request.Method}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Protocol: {{.Request.Proto}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Request URL: {{.Request.URL}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Transfer encoding: {{.Request.TransferEncoding}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Host: {{.Request.Host}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Remote address: {{.Request.RemoteAddr}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Request URI: {{.Request.RequestURI}}</dd>
 | 
			
		||||
{{range $key, $value := .Request.Header}}
 | 
			
		||||
						<dd class="ml0 mb3 f5">Header: {{$key}}, Value: {{$value}}</dd>
 | 
			
		||||
{{end}}
 | 
			
		||||
						<dd class="ml0 mb3 f5">Body: {{.Body}}</dd>
 | 
			
		||||
					</dl>
 | 
			
		||||
        </section>
 | 
			
		||||
     </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
func hello(c *cli.Context) error {
 | 
			
		||||
func helloWorld(c *cli.Context) error {
 | 
			
		||||
	address := fmt.Sprintf(":%d", c.Int("port"))
 | 
			
		||||
	listener, err := createListener(address)
 | 
			
		||||
	listener, err := hello.CreateTLSListener(address)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer listener.Close()
 | 
			
		||||
	err = startHelloWorldServer(listener, nil)
 | 
			
		||||
	err = hello.StartHelloWorldServer(logger, listener, nil)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func startHelloWorldServer(listener net.Listener, shutdownC <-chan struct{}) error {
 | 
			
		||||
	logger.Infof("Starting Hello World server at %s", listener.Addr())
 | 
			
		||||
	serverName := defaultServerName
 | 
			
		||||
	if hostname, err := os.Hostname(); err == nil {
 | 
			
		||||
		serverName = hostname
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	upgrader := websocket.Upgrader{
 | 
			
		||||
		ReadBufferSize:  1024,
 | 
			
		||||
		WriteBufferSize: 1024,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	httpServer := &http.Server{Addr: listener.Addr().String(), Handler: nil}
 | 
			
		||||
	go func() {
 | 
			
		||||
		<-shutdownC
 | 
			
		||||
		httpServer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/uptime", uptimeHandler(time.Now()))
 | 
			
		||||
	http.HandleFunc("/ws", websocketHandler(upgrader))
 | 
			
		||||
	http.HandleFunc("/", rootHandler(serverName))
 | 
			
		||||
	err := httpServer.Serve(listener)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createListener(address string) (net.Listener, error) {
 | 
			
		||||
	certificate, err := tlsconfig.GetHelloCertificate()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the port in address is empty, a port number is automatically chosen
 | 
			
		||||
	listener, err := tls.Listen(
 | 
			
		||||
		"tcp",
 | 
			
		||||
		address,
 | 
			
		||||
		&tls.Config{Certificates: []tls.Certificate{certificate}})
 | 
			
		||||
 | 
			
		||||
	return listener, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func uptimeHandler(startTime time.Time) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		// Note that if autoupdate is enabled, the uptime is reset when a new client
 | 
			
		||||
		// release is available
 | 
			
		||||
		resp := &OriginUpTime{StartTime: startTime, UpTime: time.Now().Sub(startTime).String()}
 | 
			
		||||
		respJson, err := json.Marshal(resp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
		} else {
 | 
			
		||||
			w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
			w.Write(respJson)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func websocketHandler(upgrader websocket.Upgrader) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		conn, err := upgrader.Upgrade(w, r, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		defer conn.Close()
 | 
			
		||||
 | 
			
		||||
		for {
 | 
			
		||||
			mt, message, err := conn.ReadMessage()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := conn.WriteMessage(mt, message); err != nil {
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func rootHandler(serverName string) http.HandlerFunc {
 | 
			
		||||
	responseTemplate := template.Must(template.New("index").Parse(indexTemplate))
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var buffer bytes.Buffer
 | 
			
		||||
		var body string
 | 
			
		||||
		rawBody, err := ioutil.ReadAll(r.Body)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			body = string(rawBody)
 | 
			
		||||
		} else {
 | 
			
		||||
			body = ""
 | 
			
		||||
		}
 | 
			
		||||
		err = responseTemplate.Execute(&buffer, &templateData{
 | 
			
		||||
			ServerName: serverName,
 | 
			
		||||
			Request:    r,
 | 
			
		||||
			Body:       body,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			fmt.Fprintf(w, "error: %v", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			buffer.WriteTo(w)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -10,7 +10,7 @@ import (
 | 
			
		|||
	cli "gopkg.in/urfave/cli.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runApp(app *cli.App) {
 | 
			
		||||
func runApp(app *cli.App, shutdownC chan struct{}) {
 | 
			
		||||
	app.Commands = append(app.Commands, &cli.Command{
 | 
			
		||||
		Name:  "service",
 | 
			
		||||
		Usage: "Manages the Argo Tunnel system service",
 | 
			
		||||
| 
						 | 
				
			
			@ -183,9 +183,9 @@ 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 {
 | 
			
		||||
	if err = copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile, defaultCredentialFile); err != nil {
 | 
			
		||||
		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])
 | 
			
		||||
			serviceConfigDir, defaultCredentialFile, defaultConfigFiles[0])
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,65 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"os"
 | 
			
		||||
 | 
			
		||||
	"github.com/cloudflare/cloudflared/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/rifflock/lfshook"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
	"gopkg.in/urfave/cli.v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/mitchellh/go-homedir"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var logger = log.CreateLogger()
 | 
			
		||||
 | 
			
		||||
func configMainLogger(c *cli.Context) {
 | 
			
		||||
	logLevel, err := logrus.ParseLevel(c.String("loglevel"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Unknown logging level specified")
 | 
			
		||||
	}
 | 
			
		||||
	logger.SetLevel(logLevel)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func configProtoLogger(c *cli.Context) *logrus.Logger {
 | 
			
		||||
	protoLogLevel, err := logrus.ParseLevel(c.String("proto-loglevel"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Unknown protocol logging level specified")
 | 
			
		||||
	}
 | 
			
		||||
	protoLogger := logrus.New()
 | 
			
		||||
	protoLogger.Level = protoLogLevel
 | 
			
		||||
	return protoLogger
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initLogFile(c *cli.Context, loggers ...*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,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, l := range loggers {
 | 
			
		||||
		l.Hooks.Add(lfshook.NewHook(pathMap, &logrus.JSONFormatter{}))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ func login(c *cli.Context) error {
 | 
			
		|||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	path := filepath.Join(configPath, credentialFile)
 | 
			
		||||
	path := filepath.Join(configPath, defaultCredentialFile)
 | 
			
		||||
	fileInfo, err := os.Stat(path)
 | 
			
		||||
	if err == nil && fileInfo.Size() > 0 {
 | 
			
		||||
		fmt.Fprintf(os.Stderr, `You have an existing certificate at %s which login would overwrite.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@ const (
 | 
			
		|||
	launchdIdentifier = "com.cloudflare.cloudflared"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runApp(app *cli.App) {
 | 
			
		||||
func runApp(app *cli.App, shutdownC chan struct{}) {
 | 
			
		||||
	app.Commands = append(app.Commands, &cli.Command{
 | 
			
		||||
		Name:  "service",
 | 
			
		||||
		Usage: "Manages the Argo Tunnel launch agent",
 | 
			
		||||
| 
						 | 
				
			
			@ -91,12 +91,12 @@ func stderrPath() string {
 | 
			
		|||
func installLaunchd(c *cli.Context) error {
 | 
			
		||||
	if isRootUser() {
 | 
			
		||||
		logger.Infof("Installing Argo Tunnel client as a system launch daemon. " +
 | 
			
		||||
		"Argo Tunnel client will run at boot")
 | 
			
		||||
			"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/")
 | 
			
		||||
			"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 {
 | 
			
		||||
| 
						 | 
				
			
			@ -120,7 +120,6 @@ func installLaunchd(c *cli.Context) error {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
func uninstallLaunchd(c *cli.Context) error {
 | 
			
		||||
 | 
			
		||||
	if isRootUser() {
 | 
			
		||||
		logger.Infof("Uninstalling Argo Tunnel as a system launch daemon")
 | 
			
		||||
	} else {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,69 +1,47 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/cloudflare/cloudflared/log"
 | 
			
		||||
	"github.com/cloudflare/cloudflared/hello"
 | 
			
		||||
	"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"
 | 
			
		||||
	"github.com/facebookgo/grace/gracenet"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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/"
 | 
			
		||||
	sentryDSN       = "https://56a9c9fa5c364ab28f34b14f35ea0f1b:3e8827f6f9f740738eb11138f7bebb68@sentry.io/189878"
 | 
			
		||||
	developerPortal = "https://developers.cloudflare.com/argo-tunnel"
 | 
			
		||||
	quickStartUrl   = developerPortal + "/quickstart/quickstart/"
 | 
			
		||||
	serviceUrl      = developerPortal + "/reference/service/"
 | 
			
		||||
	argumentsUrl    = developerPortal + "/reference/arguments/"
 | 
			
		||||
	licenseUrl      = developerPortal + "/licence/"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var listeners = gracenet.Net{}
 | 
			
		||||
var Version = "DEV"
 | 
			
		||||
var BuildTime = "unknown"
 | 
			
		||||
var logger = log.CreateLogger()
 | 
			
		||||
var defaultConfigFiles = []string{"config.yml", "config.yaml"}
 | 
			
		||||
 | 
			
		||||
// Launchd doesn't set root env variables, so there is default
 | 
			
		||||
// Windows default config dir was ~/cloudflare-warp in documentation; let's keep it compatible
 | 
			
		||||
var defaultConfigDirs = []string{"~/.cloudflared", "~/.cloudflare-warp", "~/cloudflare-warp", "/usr/local/etc/cloudflared", "/etc/cloudflared"}
 | 
			
		||||
 | 
			
		||||
// Shutdown channel used by the app. When closed, app must terminate.
 | 
			
		||||
// May be closed by the Windows service runner.
 | 
			
		||||
var shutdownC chan struct{}
 | 
			
		||||
var (
 | 
			
		||||
	Version   = "DEV"
 | 
			
		||||
	BuildTime = "unknown"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	metrics.RegisterBuildInfo(BuildTime, Version)
 | 
			
		||||
	raven.SetDSN(sentryDSN)
 | 
			
		||||
	raven.SetRelease(Version)
 | 
			
		||||
	shutdownC = make(chan struct{})
 | 
			
		||||
 | 
			
		||||
	// Shutdown channel used by the app. When closed, app must terminate.
 | 
			
		||||
	// May be closed by the Windows service runner.
 | 
			
		||||
	shutdownC := make(chan struct{})
 | 
			
		||||
 | 
			
		||||
	app := &cli.App{}
 | 
			
		||||
	app.Name = "cloudflared"
 | 
			
		||||
| 
						 | 
				
			
			@ -119,6 +97,11 @@ func main() {
 | 
			
		|||
			EnvVars: []string{"TUNNEL_ORIGIN_CERT"},
 | 
			
		||||
			Value:   findDefaultOriginCertPath(),
 | 
			
		||||
		}),
 | 
			
		||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
			
		||||
			Name:    "origin-ca-pool",
 | 
			
		||||
			Usage:   "Path to the CA for the certificate of your origin. This option should be used only if your certificate is not signed by Cloudflare.",
 | 
			
		||||
			EnvVars: []string{"TUNNEL_ORIGIN_CA_POOL"},
 | 
			
		||||
		}),
 | 
			
		||||
		altsrc.NewStringFlag(&cli.StringFlag{
 | 
			
		||||
			Name:    "url",
 | 
			
		||||
			Value:   "https://localhost:8080",
 | 
			
		||||
| 
						 | 
				
			
			@ -293,10 +276,13 @@ func main() {
 | 
			
		|||
		}),
 | 
			
		||||
	}
 | 
			
		||||
	app.Action = func(c *cli.Context) error {
 | 
			
		||||
		raven.CapturePanic(func() { startServer(c) }, nil)
 | 
			
		||||
		raven.CapturePanic(func() { startServer(c, shutdownC) }, nil)
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	app.Before = func(context *cli.Context) error {
 | 
			
		||||
		if context.String("config") == "" {
 | 
			
		||||
			logger.Warnf("Cannot determine default configuration path. No file %v in %v", defaultConfigFiles, defaultConfigDirs)
 | 
			
		||||
		}
 | 
			
		||||
		inputSource, err := findInputSourceContext(context)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.WithError(err).Infof("Cannot load configuration from %s", context.String("config"))
 | 
			
		||||
| 
						 | 
				
			
			@ -337,7 +323,7 @@ func main() {
 | 
			
		|||
		},
 | 
			
		||||
		{
 | 
			
		||||
			Name:   "hello",
 | 
			
		||||
			Action: hello,
 | 
			
		||||
			Action: helloWorld,
 | 
			
		||||
			Usage:  "Run a simple \"Hello World\" server for testing Argo Tunnel.",
 | 
			
		||||
			Flags: []cli.Flag{
 | 
			
		||||
				&cli.IntFlag{
 | 
			
		||||
| 
						 | 
				
			
			@ -381,233 +367,130 @@ func main() {
 | 
			
		|||
			ArgsUsage: " ", // can't be the empty string or we get the default output
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	runApp(app)
 | 
			
		||||
	runApp(app, shutdownC)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func startServer(c *cli.Context) {
 | 
			
		||||
func startServer(c *cli.Context, shutdownC chan struct{}) {
 | 
			
		||||
	var wg sync.WaitGroup
 | 
			
		||||
	listeners := gracenet.Net{}
 | 
			
		||||
	errC := make(chan error)
 | 
			
		||||
	connectedSignal := make(chan struct{})
 | 
			
		||||
	dnsReadySignal := make(chan struct{})
 | 
			
		||||
	graceShutdownSignal := make(chan struct{})
 | 
			
		||||
 | 
			
		||||
	// 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") == "" {
 | 
			
		||||
		logger.Infof("No arguments were provided. You need to at least specify the hostname for this tunnel. See %s", quickStartUrl)
 | 
			
		||||
		cli.ShowAppHelp(c)
 | 
			
		||||
	// check whether client provides enough flags or env variables. If not, print help.
 | 
			
		||||
	if ok := enoughOptionsSet(c); !ok {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	logLevel, err := logrus.ParseLevel(c.String("loglevel"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Unknown logging level specified")
 | 
			
		||||
	}
 | 
			
		||||
	logger.SetLevel(logLevel)
 | 
			
		||||
 | 
			
		||||
	protoLogLevel, err := logrus.ParseLevel(c.String("proto-loglevel"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Unknown protocol logging level specified")
 | 
			
		||||
	}
 | 
			
		||||
	protoLogger := logrus.New()
 | 
			
		||||
	protoLogger.Level = protoLogLevel
 | 
			
		||||
 | 
			
		||||
	configMainLogger(c)
 | 
			
		||||
	protoLogger := configProtoLogger(c)
 | 
			
		||||
	if c.String("logfile") != "" {
 | 
			
		||||
		if err := initLogFile(c, protoLogger); err != nil {
 | 
			
		||||
		if err := initLogFile(c, logger, protoLogger); err != nil {
 | 
			
		||||
			logger.Error(err)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	handleDeprecatedOptions(c)
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
			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()
 | 
			
		||||
			logger.WithError(err).Fatal("Cannot create the DNS over HTTPS proxy server")
 | 
			
		||||
		}
 | 
			
		||||
		go func() {
 | 
			
		||||
			err := listener.Start(dnsReadySignal)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.WithError(err).Fatal("Cannot start the DNS over HTTPS proxy server")
 | 
			
		||||
			} else {
 | 
			
		||||
				<-shutdownC
 | 
			
		||||
			}
 | 
			
		||||
			listener.Stop()
 | 
			
		||||
			wg.Done()
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
			runDNSProxyServer(c, dnsReadySignal, shutdownC)
 | 
			
		||||
		}()
 | 
			
		||||
	} else {
 | 
			
		||||
		close(dnsReadySignal)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isRunningFromTerminal := isRunningFromTerminal()
 | 
			
		||||
	if isAutoupdateEnabled(c, isRunningFromTerminal) {
 | 
			
		||||
		// Wait for proxy-dns to come up (if used)
 | 
			
		||||
		<-dnsReadySignal
 | 
			
		||||
		if initUpdate() {
 | 
			
		||||
	// Wait for proxy-dns to come up (if used)
 | 
			
		||||
	<-dnsReadySignal
 | 
			
		||||
 | 
			
		||||
	// update needs to be after DNS proxy is up to resolve equinox server address
 | 
			
		||||
	if isAutoupdateEnabled(c) {
 | 
			
		||||
		if initUpdate(&listeners) {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq"))
 | 
			
		||||
		go autoupdate(c.Duration("autoupdate-freq"), shutdownC)
 | 
			
		||||
		go autoupdate(c.Duration("autoupdate-freq"), &listeners, shutdownC)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Serve DNS proxy stand-alone if no hostname or tag or app is going to run
 | 
			
		||||
	if c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world")) {
 | 
			
		||||
		go writePidFile(connectedSignal, c.String("pidfile"))
 | 
			
		||||
		close(connectedSignal)
 | 
			
		||||
		runServer(c, &wg, errC, shutdownC)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	hostname, err := validation.ValidateHostname(c.String("hostname"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Invalid hostname")
 | 
			
		||||
	}
 | 
			
		||||
	clientID := c.String("id")
 | 
			
		||||
	if !c.IsSet("id") {
 | 
			
		||||
		clientID = generateRandomClientID()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.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()
 | 
			
		||||
			logger.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 {
 | 
			
		||||
		logger.WithError(err).Fatal("Error validating 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") {
 | 
			
		||||
		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 {
 | 
			
		||||
		logger.WithError(err).Fatalf("Cannot resolve path %s", c.String("origincert"))
 | 
			
		||||
	}
 | 
			
		||||
	ok, err := fileExists(originCertPath)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Fatalf("Cannot check if origin cert exists at path %s", c.String("origincert"))
 | 
			
		||||
	}
 | 
			
		||||
	if !ok {
 | 
			
		||||
		logger.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 {
 | 
			
		||||
		logger.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{
 | 
			
		||||
		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"))
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		errC <- origin.StartTunnelDaemon(tunnelConfig, shutdownC, connectedSignal)
 | 
			
		||||
		wg.Done()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	runServer(c, &wg, errC, shutdownC)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func runServer(c *cli.Context, wg *sync.WaitGroup, errC chan error, shutdownC chan struct{}) {
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	metricsListener, err := listeners.Listen("tcp", c.String("metrics"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Error opening metrics server listener")
 | 
			
		||||
	}
 | 
			
		||||
	defer metricsListener.Close()
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		defer wg.Done()
 | 
			
		||||
		errC <- metrics.ServeMetrics(metricsListener, shutdownC, logger)
 | 
			
		||||
		wg.Done()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// Serve DNS proxy stand-alone if no hostname or tag or app is going to run
 | 
			
		||||
	if dnsProxyStandAlone(c) {
 | 
			
		||||
		if c.IsSet("pidfile") {
 | 
			
		||||
			go writePidFile(connectedSignal, c.String("pidfile"))
 | 
			
		||||
			close(connectedSignal)
 | 
			
		||||
		}
 | 
			
		||||
		// no grace period, handle SIGINT/SIGTERM immediately
 | 
			
		||||
		waitToShutdown(&wg, errC, shutdownC, graceShutdownSignal, 0)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if c.IsSet("hello-world") {
 | 
			
		||||
		helloListener, err := hello.CreateTLSListener("127.0.0.1:")
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.WithError(err).Fatal("Cannot start Hello World Server")
 | 
			
		||||
		}
 | 
			
		||||
		defer helloListener.Close()
 | 
			
		||||
		wg.Add(1)
 | 
			
		||||
		go func() {
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
			hello.StartHelloWorldServer(logger, helloListener, shutdownC)
 | 
			
		||||
		}()
 | 
			
		||||
		c.Set("url", "https://"+helloListener.Addr().String())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tunnelConfig := prepareTunnelConfig(c, buildInfo, logger, protoLogger)
 | 
			
		||||
 | 
			
		||||
	if c.IsSet("pidFile") {
 | 
			
		||||
		go writePidFile(connectedSignal, c.String("pidfile"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	wg.Add(1)
 | 
			
		||||
	go func() {
 | 
			
		||||
		defer wg.Done()
 | 
			
		||||
		errC <- origin.StartTunnelDaemon(tunnelConfig, graceShutdownSignal, connectedSignal)
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	waitToShutdown(&wg, errC, shutdownC, graceShutdownSignal, c.Duration("grace-period"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func waitToShutdown(wg *sync.WaitGroup,
 | 
			
		||||
	errC chan error,
 | 
			
		||||
	shutdownC, graceShutdownSignal chan struct{},
 | 
			
		||||
	gracePeriod time.Duration,
 | 
			
		||||
) {
 | 
			
		||||
	var err error
 | 
			
		||||
	if gracePeriod > 0 {
 | 
			
		||||
		err = waitForSignalWithGraceShutdown(errC, shutdownC, graceShutdownSignal, gracePeriod)
 | 
			
		||||
	} else {
 | 
			
		||||
		err = waitForSignal(errC, shutdownC)
 | 
			
		||||
		close(graceShutdownSignal)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var errCode int
 | 
			
		||||
	err = WaitForSignal(errC, shutdownC)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Quitting due to error")
 | 
			
		||||
		raven.CaptureErrorAndWait(err, nil)
 | 
			
		||||
		errCode = 1
 | 
			
		||||
	} else {
 | 
			
		||||
		logger.Info("Graceful shutdown...")
 | 
			
		||||
		logger.Info("Quitting...")
 | 
			
		||||
	}
 | 
			
		||||
	// Wait for clean exit, discarding all errors
 | 
			
		||||
	go func() {
 | 
			
		||||
| 
						 | 
				
			
			@ -618,126 +501,6 @@ func runServer(c *cli.Context, wg *sync.WaitGroup, errC chan error, shutdownC ch
 | 
			
		|||
	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)
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case err := <-errC:
 | 
			
		||||
		close(shutdownC)
 | 
			
		||||
		return err
 | 
			
		||||
	case <-signals:
 | 
			
		||||
		close(shutdownC)
 | 
			
		||||
	case <-shutdownC:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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 {
 | 
			
		||||
			logger.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 {
 | 
			
		||||
				logger.WithError(err).Error("Unable to restart server automatically")
 | 
			
		||||
			}
 | 
			
		||||
			close(shutdownC)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(freq)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateApplied() bool {
 | 
			
		||||
	releaseInfo := checkForUpdates()
 | 
			
		||||
	if releaseInfo.Updated {
 | 
			
		||||
		logger.Infof("Updated to version %s", releaseInfo.Version)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if releaseInfo.Error != nil {
 | 
			
		||||
		logger.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 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			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")
 | 
			
		||||
| 
						 | 
				
			
			@ -752,87 +515,6 @@ func writePidFile(waitForSignal chan struct{}, pidFile string) {
 | 
			
		|||
	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,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	logger.Hooks.Add(lfshook.NewHook(pathMap, &logrus.JSONFormatter{}))
 | 
			
		||||
	protoLogger.Hooks.Add(lfshook.NewHook(pathMap, &logrus.JSONFormatter{}))
 | 
			
		||||
 | 
			
		||||
	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_
 | 
			
		||||
		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 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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/cloudflare/cloudflared/tunneldns"
 | 
			
		||||
 | 
			
		||||
	"gopkg.in/urfave/cli.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runDNSProxyServer(c *cli.Context, dnsReadySignal, shutdownC chan struct{}) {
 | 
			
		||||
	port := c.Int("proxy-dns-port")
 | 
			
		||||
	if port <= 0 || port > 65535 {
 | 
			
		||||
		logger.Fatal("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
 | 
			
		||||
	}
 | 
			
		||||
	listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		close(dnsReadySignal)
 | 
			
		||||
		listener.Stop()
 | 
			
		||||
		logger.WithError(err).Fatal("Cannot create the DNS over HTTPS proxy server")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = listener.Start(dnsReadySignal)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.WithError(err).Fatal("Cannot start the DNS over HTTPS proxy server")
 | 
			
		||||
	}
 | 
			
		||||
	<-shutdownC
 | 
			
		||||
	listener.Stop()
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -119,7 +119,7 @@ func openFile(path string, create bool) (file *os.File, exists bool, err error)
 | 
			
		|||
	return file, false, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyCertificate(srcConfigDir, destConfigDir string) error {
 | 
			
		||||
func copyCertificate(srcConfigDir, destConfigDir, credentialFile string) error {
 | 
			
		||||
	destCredentialPath := filepath.Join(destConfigDir, credentialFile)
 | 
			
		||||
	destFile, exists, err := openFile(destCredentialPath, true)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -146,12 +146,12 @@ func copyCertificate(srcConfigDir, destConfigDir string) error {
 | 
			
		|||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile string) error {
 | 
			
		||||
func copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile, defaultCredentialFile string) error {
 | 
			
		||||
	if err := ensureConfigDirExists(serviceConfigDir); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := copyCertificate(defaultConfigDir, serviceConfigDir); err != nil {
 | 
			
		||||
	if err := copyCertificate(defaultConfigDir, serviceConfigDir, defaultCredentialFile); err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case err := <-errC:
 | 
			
		||||
		close(shutdownC)
 | 
			
		||||
		return err
 | 
			
		||||
	case <-signals:
 | 
			
		||||
		close(shutdownC)
 | 
			
		||||
	case <-shutdownC:
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func waitForSignalWithGraceShutdown(errC chan error, shutdownC, graceShutdownSignal chan struct{}, gracePeriod time.Duration) error {
 | 
			
		||||
	signals := make(chan os.Signal, 10)
 | 
			
		||||
	signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
 | 
			
		||||
	defer signal.Stop(signals)
 | 
			
		||||
 | 
			
		||||
	select {
 | 
			
		||||
	case err := <-errC:
 | 
			
		||||
		close(graceShutdownSignal)
 | 
			
		||||
		close(shutdownC)
 | 
			
		||||
		return err
 | 
			
		||||
	case <-signals:
 | 
			
		||||
		close(graceShutdownSignal)
 | 
			
		||||
		logger.Infof("Initiating graceful shutdown...")
 | 
			
		||||
		// Unregister signal handler early, so the client can send a second SIGTERM/SIGINT
 | 
			
		||||
		// to force shutdown cloudflared
 | 
			
		||||
		signal.Stop(signals)
 | 
			
		||||
		graceTimerTick := time.Tick(gracePeriod)
 | 
			
		||||
		// send close signal via shutdownC when grace period expires or when an
 | 
			
		||||
		// error is encountered.
 | 
			
		||||
		select {
 | 
			
		||||
		case <-graceTimerTick:
 | 
			
		||||
		case <-errC:
 | 
			
		||||
		}
 | 
			
		||||
		close(shutdownC)
 | 
			
		||||
	case <-shutdownC:
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,131 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const tick = 100 * time.Millisecond
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	serverErr        = fmt.Errorf("server error")
 | 
			
		||||
	shutdownErr      = fmt.Errorf("receive shutdown")
 | 
			
		||||
	graceShutdownErr = fmt.Errorf("receive grace shutdown")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testChannelClosed(t *testing.T, c chan struct{}) {
 | 
			
		||||
	select {
 | 
			
		||||
	case <-c:
 | 
			
		||||
		return
 | 
			
		||||
	default:
 | 
			
		||||
		t.Fatal("Channel should be readable")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWaitForSignal(t *testing.T) {
 | 
			
		||||
	// Test handling server error
 | 
			
		||||
	errC := make(chan error)
 | 
			
		||||
	shutdownC := make(chan struct{})
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		errC <- serverErr
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err := waitForSignal(errC, shutdownC)
 | 
			
		||||
	assert.Equal(t, serverErr, err)
 | 
			
		||||
	testChannelClosed(t, shutdownC)
 | 
			
		||||
 | 
			
		||||
	// Test handling SIGTERM & SIGINT
 | 
			
		||||
	for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
 | 
			
		||||
		errC = make(chan error)
 | 
			
		||||
		shutdownC = make(chan struct{})
 | 
			
		||||
 | 
			
		||||
		go func(shutdownC chan struct{}) {
 | 
			
		||||
			<-shutdownC
 | 
			
		||||
			errC <- shutdownErr
 | 
			
		||||
		}(shutdownC)
 | 
			
		||||
 | 
			
		||||
		go func(sig syscall.Signal) {
 | 
			
		||||
			// sleep for a tick to prevent sending signal before calling waitForSignal
 | 
			
		||||
			time.Sleep(tick)
 | 
			
		||||
			syscall.Kill(syscall.Getpid(), sig)
 | 
			
		||||
		}(sig)
 | 
			
		||||
 | 
			
		||||
		err = waitForSignal(errC, shutdownC)
 | 
			
		||||
		assert.Equal(t, nil, err)
 | 
			
		||||
		assert.Equal(t, shutdownErr, <-errC)
 | 
			
		||||
		testChannelClosed(t, shutdownC)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWaitForSignalWithGraceShutdown(t *testing.T) {
 | 
			
		||||
	// Test server returning error
 | 
			
		||||
	errC := make(chan error)
 | 
			
		||||
	shutdownC := make(chan struct{})
 | 
			
		||||
	graceshutdownC := make(chan struct{})
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		errC <- serverErr
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err := waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
 | 
			
		||||
	assert.Equal(t, serverErr, err)
 | 
			
		||||
	testChannelClosed(t, shutdownC)
 | 
			
		||||
	testChannelClosed(t, graceshutdownC)
 | 
			
		||||
 | 
			
		||||
	// Test handling SIGTERM & SIGINT
 | 
			
		||||
	for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
 | 
			
		||||
		//var wg sync.WaitGroup
 | 
			
		||||
		errC := make(chan error)
 | 
			
		||||
		shutdownC = make(chan struct{})
 | 
			
		||||
		graceshutdownC = make(chan struct{})
 | 
			
		||||
 | 
			
		||||
		go func(shutdownC, graceshutdownC chan struct{}) {
 | 
			
		||||
			<-graceshutdownC
 | 
			
		||||
			<-shutdownC
 | 
			
		||||
			errC <- graceShutdownErr
 | 
			
		||||
		}(shutdownC, graceshutdownC)
 | 
			
		||||
 | 
			
		||||
		go func(sig syscall.Signal) {
 | 
			
		||||
			// sleep for a tick to prevent sending signal before calling waitForSignalWithGraceShutdown
 | 
			
		||||
			time.Sleep(tick)
 | 
			
		||||
			syscall.Kill(syscall.Getpid(), sig)
 | 
			
		||||
		}(sig)
 | 
			
		||||
 | 
			
		||||
		err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
 | 
			
		||||
		assert.Equal(t, nil, err)
 | 
			
		||||
		assert.Equal(t, graceShutdownErr, <-errC)
 | 
			
		||||
		testChannelClosed(t, shutdownC)
 | 
			
		||||
		testChannelClosed(t, graceshutdownC)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Test handling SIGTERM & SIGINT, server send error before end of grace period
 | 
			
		||||
	for _, sig := range []syscall.Signal{syscall.SIGTERM, syscall.SIGINT} {
 | 
			
		||||
		errC := make(chan error)
 | 
			
		||||
		shutdownC = make(chan struct{})
 | 
			
		||||
		graceshutdownC = make(chan struct{})
 | 
			
		||||
 | 
			
		||||
		go func(shutdownC, graceshutdownC chan struct{}) {
 | 
			
		||||
			<-graceshutdownC
 | 
			
		||||
			errC <- graceShutdownErr
 | 
			
		||||
			<-shutdownC
 | 
			
		||||
			errC <- shutdownErr
 | 
			
		||||
		}(shutdownC, graceshutdownC)
 | 
			
		||||
 | 
			
		||||
		go func(sig syscall.Signal) {
 | 
			
		||||
			// sleep for a tick to prevent sending signal before calling waitForSignalWithGraceShutdown
 | 
			
		||||
			time.Sleep(tick)
 | 
			
		||||
			syscall.Kill(syscall.Getpid(), sig)
 | 
			
		||||
		}(sig)
 | 
			
		||||
 | 
			
		||||
		err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
 | 
			
		||||
		assert.Equal(t, nil, err)
 | 
			
		||||
		assert.Equal(t, shutdownErr, <-errC)
 | 
			
		||||
		testChannelClosed(t, shutdownC)
 | 
			
		||||
		testChannelClosed(t, graceshutdownC)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,8 +1,20 @@
 | 
			
		|||
package main
 | 
			
		||||
 | 
			
		||||
import "github.com/equinox-io/equinox"
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
const appID = "app_idCzgxYerVD"
 | 
			
		||||
	"golang.org/x/crypto/ssh/terminal"
 | 
			
		||||
	"gopkg.in/urfave/cli.v2"
 | 
			
		||||
 | 
			
		||||
	"github.com/equinox-io/equinox"
 | 
			
		||||
	"github.com/facebookgo/grace/gracenet"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	appID               = "app_idCzgxYerVD"
 | 
			
		||||
	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/"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var publicKey = []byte(`
 | 
			
		||||
-----BEGIN ECDSA PUBLIC KEY-----
 | 
			
		||||
| 
						 | 
				
			
			@ -39,3 +51,61 @@ func checkForUpdates() ReleaseInfo {
 | 
			
		|||
 | 
			
		||||
	return ReleaseInfo{Updated: true, Version: resp.ReleaseVersion}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func update(_ *cli.Context) error {
 | 
			
		||||
	if updateApplied() {
 | 
			
		||||
		os.Exit(64)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func initUpdate(listeners *gracenet.Net) bool {
 | 
			
		||||
	if updateApplied() {
 | 
			
		||||
		os.Args = append(os.Args, "--is-autoupdated=true")
 | 
			
		||||
		if _, err := listeners.StartProcess(); err != nil {
 | 
			
		||||
			logger.WithError(err).Error("Unable to restart server automatically")
 | 
			
		||||
			return false
 | 
			
		||||
		}
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func autoupdate(freq time.Duration, listeners *gracenet.Net, shutdownC chan struct{}) {
 | 
			
		||||
	for {
 | 
			
		||||
		if updateApplied() {
 | 
			
		||||
			os.Args = append(os.Args, "--is-autoupdated=true")
 | 
			
		||||
			if _, err := listeners.StartProcess(); err != nil {
 | 
			
		||||
				logger.WithError(err).Error("Unable to restart server automatically")
 | 
			
		||||
			}
 | 
			
		||||
			close(shutdownC)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(freq)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func updateApplied() bool {
 | 
			
		||||
	releaseInfo := checkForUpdates()
 | 
			
		||||
	if releaseInfo.Updated {
 | 
			
		||||
		logger.Infof("Updated to version %s", releaseInfo.Version)
 | 
			
		||||
		return true
 | 
			
		||||
	}
 | 
			
		||||
	if releaseInfo.Error != nil {
 | 
			
		||||
		logger.WithError(releaseInfo.Error).Error("Update check failed")
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isAutoupdateEnabled(c *cli.Context) 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()))
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -21,7 +21,7 @@ const (
 | 
			
		|||
	windowsServiceDescription = "Argo Tunnel agent"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func runApp(app *cli.App) {
 | 
			
		||||
func runApp(app *cli.App, shutdownC chan struct{}) {
 | 
			
		||||
	app.Commands = append(app.Commands, &cli.Command{
 | 
			
		||||
		Name:  "service",
 | 
			
		||||
		Usage: "Manages the Argo Tunnel Windows service",
 | 
			
		||||
| 
						 | 
				
			
			@ -59,7 +59,7 @@ func runApp(app *cli.App) {
 | 
			
		|||
	elog.Info(1, fmt.Sprintf("%s service starting", windowsServiceName))
 | 
			
		||||
	// Run executes service name by calling windowsService which is a Handler
 | 
			
		||||
	// interface that implements Execute method
 | 
			
		||||
	err = svc.Run(windowsServiceName, &windowsService{app: app, elog: elog})
 | 
			
		||||
	err = svc.Run(windowsServiceName, &windowsService{app: app, elog: elog, shutdownC: shutdownC})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		elog.Error(1, fmt.Sprintf("%s service failed: %v", windowsServiceName, err))
 | 
			
		||||
		return
 | 
			
		||||
| 
						 | 
				
			
			@ -68,8 +68,9 @@ func runApp(app *cli.App) {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
type windowsService struct {
 | 
			
		||||
	app  *cli.App
 | 
			
		||||
	elog *eventlog.Log
 | 
			
		||||
	app       *cli.App
 | 
			
		||||
	elog      *eventlog.Log
 | 
			
		||||
	shutdownC chan struct{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// called by the package code at the start of the service
 | 
			
		||||
| 
						 | 
				
			
			@ -98,7 +99,7 @@ loop:
 | 
			
		|||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	close(shutdownC)
 | 
			
		||||
	close(s.shutdownC)
 | 
			
		||||
	changes <- svc.Status{State: svc.StopPending}
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -135,7 +135,7 @@ func TestSingleStream(t *testing.T) {
 | 
			
		|||
			t.Fatalf("expected header value %s, got %s", "headerValue", stream.Headers[0].Value)
 | 
			
		||||
		}
 | 
			
		||||
		stream.WriteHeaders([]Header{
 | 
			
		||||
			Header{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
			{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
		})
 | 
			
		||||
		buf := []byte("Hello world")
 | 
			
		||||
		stream.Write(buf)
 | 
			
		||||
| 
						 | 
				
			
			@ -153,7 +153,7 @@ func TestSingleStream(t *testing.T) {
 | 
			
		|||
	muxPair.HandshakeAndServe(t)
 | 
			
		||||
 | 
			
		||||
	stream, err := muxPair.EdgeMux.OpenStream(
 | 
			
		||||
		[]Header{Header{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		[]Header{{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		nil,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -194,6 +194,7 @@ func TestSingleStream(t *testing.T) {
 | 
			
		|||
func TestSingleStreamLargeResponseBody(t *testing.T) {
 | 
			
		||||
	muxPair := NewDefaultMuxerPair()
 | 
			
		||||
	bodySize := 1 << 24
 | 
			
		||||
	streamReady := make(chan struct{})
 | 
			
		||||
	muxPair.OriginMuxConfig.Handler = MuxedStreamFunc(func(stream *MuxedStream) error {
 | 
			
		||||
		if len(stream.Headers) != 1 {
 | 
			
		||||
			t.Fatalf("expected %d headers, got %d", 1, len(stream.Headers))
 | 
			
		||||
| 
						 | 
				
			
			@ -205,25 +206,30 @@ func TestSingleStreamLargeResponseBody(t *testing.T) {
 | 
			
		|||
			t.Fatalf("expected header value %s, got %s", "headerValue", stream.Headers[0].Value)
 | 
			
		||||
		}
 | 
			
		||||
		stream.WriteHeaders([]Header{
 | 
			
		||||
			Header{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
			{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
		})
 | 
			
		||||
		payload := make([]byte, bodySize)
 | 
			
		||||
		for i := range payload {
 | 
			
		||||
			payload[i] = byte(i % 256)
 | 
			
		||||
		}
 | 
			
		||||
		t.Log("Writing payload...")
 | 
			
		||||
		n, err := stream.Write(payload)
 | 
			
		||||
		t.Logf("Wrote %d bytes into the stream", n)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("origin write error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
		if n != len(payload) {
 | 
			
		||||
			t.Fatalf("origin short write: %d/%d bytes", n, len(payload))
 | 
			
		||||
		}
 | 
			
		||||
		t.Log("Payload written; signaling that the stream is ready")
 | 
			
		||||
		streamReady <- struct{}{}
 | 
			
		||||
 | 
			
		||||
		return nil
 | 
			
		||||
	})
 | 
			
		||||
	muxPair.HandshakeAndServe(t)
 | 
			
		||||
 | 
			
		||||
	stream, err := muxPair.EdgeMux.OpenStream(
 | 
			
		||||
		[]Header{Header{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		[]Header{{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		nil,
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -239,6 +245,10 @@ func TestSingleStreamLargeResponseBody(t *testing.T) {
 | 
			
		|||
		t.Fatalf("expected header value %s, got %s", "responseValue", stream.Headers[0].Value)
 | 
			
		||||
	}
 | 
			
		||||
	responseBody := make([]byte, bodySize)
 | 
			
		||||
 | 
			
		||||
	<-streamReady
 | 
			
		||||
	t.Log("Received stream ready signal; resuming the test")
 | 
			
		||||
 | 
			
		||||
	n, err := io.ReadFull(stream, responseBody)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatalf("error from (*MuxedStream).Read: %s", err)
 | 
			
		||||
| 
						 | 
				
			
			@ -261,7 +271,7 @@ func TestMultipleStreams(t *testing.T) {
 | 
			
		|||
		}
 | 
			
		||||
		log.Debugf("Got request for stream %s", stream.Headers[0].Value)
 | 
			
		||||
		stream.WriteHeaders([]Header{
 | 
			
		||||
			Header{Name: "response-token", Value: stream.Headers[0].Value},
 | 
			
		||||
			{Name: "response-token", Value: stream.Headers[0].Value},
 | 
			
		||||
		})
 | 
			
		||||
		log.Debugf("Wrote headers for stream %s", stream.Headers[0].Value)
 | 
			
		||||
		stream.Write([]byte("OK"))
 | 
			
		||||
| 
						 | 
				
			
			@ -277,7 +287,7 @@ func TestMultipleStreams(t *testing.T) {
 | 
			
		|||
			defer wg.Done()
 | 
			
		||||
			tokenString := fmt.Sprintf("%d", tokenId)
 | 
			
		||||
			stream, err := muxPair.EdgeMux.OpenStream(
 | 
			
		||||
				[]Header{Header{Name: "client-token", Value: tokenString}},
 | 
			
		||||
				[]Header{{Name: "client-token", Value: tokenString}},
 | 
			
		||||
				nil,
 | 
			
		||||
			)
 | 
			
		||||
			log.Debugf("Got headers for stream %d", tokenId)
 | 
			
		||||
| 
						 | 
				
			
			@ -328,6 +338,7 @@ func TestMultipleStreams(t *testing.T) {
 | 
			
		|||
func TestMultipleStreamsFlowControl(t *testing.T) {
 | 
			
		||||
	maxStreams := 32
 | 
			
		||||
	errorsC := make(chan error, maxStreams)
 | 
			
		||||
	streamReady := make(chan struct{})
 | 
			
		||||
	responseSizes := make([]int32, maxStreams)
 | 
			
		||||
	for i := 0; i < maxStreams; i++ {
 | 
			
		||||
		responseSizes[i] = rand.Int31n(int32(defaultWindowSize << 4))
 | 
			
		||||
| 
						 | 
				
			
			@ -344,13 +355,14 @@ func TestMultipleStreamsFlowControl(t *testing.T) {
 | 
			
		|||
			t.Fatalf("expected header value %s, got %s", "headerValue", stream.Headers[0].Value)
 | 
			
		||||
		}
 | 
			
		||||
		stream.WriteHeaders([]Header{
 | 
			
		||||
			Header{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
			{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
		})
 | 
			
		||||
		payload := make([]byte, responseSizes[(stream.streamID-2)/2])
 | 
			
		||||
		for i := range payload {
 | 
			
		||||
			payload[i] = byte(i % 256)
 | 
			
		||||
		}
 | 
			
		||||
		n, err := stream.Write(payload)
 | 
			
		||||
		streamReady <- struct{}{}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			t.Fatalf("origin write error: %s", err)
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -367,7 +379,7 @@ func TestMultipleStreamsFlowControl(t *testing.T) {
 | 
			
		|||
		go func(tokenId int) {
 | 
			
		||||
			defer wg.Done()
 | 
			
		||||
			stream, err := muxPair.EdgeMux.OpenStream(
 | 
			
		||||
				[]Header{Header{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
				[]Header{{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
				nil,
 | 
			
		||||
			)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -387,6 +399,7 @@ func TestMultipleStreamsFlowControl(t *testing.T) {
 | 
			
		|||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			<-streamReady
 | 
			
		||||
			responseBody := make([]byte, responseSizes[(stream.streamID-2)/2])
 | 
			
		||||
			n, err := io.ReadFull(stream, responseBody)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
| 
						 | 
				
			
			@ -417,7 +430,7 @@ func TestGracefulShutdown(t *testing.T) {
 | 
			
		|||
	muxPair := NewDefaultMuxerPair()
 | 
			
		||||
	muxPair.OriginMuxConfig.Handler = MuxedStreamFunc(func(stream *MuxedStream) error {
 | 
			
		||||
		stream.WriteHeaders([]Header{
 | 
			
		||||
			Header{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
			{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
		})
 | 
			
		||||
		<-sendC
 | 
			
		||||
		log.Debugf("Writing %d bytes", len(responseBuf))
 | 
			
		||||
| 
						 | 
				
			
			@ -436,7 +449,7 @@ func TestGracefulShutdown(t *testing.T) {
 | 
			
		|||
	muxPair.HandshakeAndServe(t)
 | 
			
		||||
 | 
			
		||||
	stream, err := muxPair.EdgeMux.OpenStream(
 | 
			
		||||
		[]Header{Header{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		[]Header{{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		nil,
 | 
			
		||||
	)
 | 
			
		||||
	// Start graceful shutdown of the edge mux - this should also close the origin mux when done
 | 
			
		||||
| 
						 | 
				
			
			@ -469,7 +482,7 @@ func TestUnexpectedShutdown(t *testing.T) {
 | 
			
		|||
	muxPair.OriginMuxConfig.Handler = MuxedStreamFunc(func(stream *MuxedStream) error {
 | 
			
		||||
		defer close(handlerFinishC)
 | 
			
		||||
		stream.WriteHeaders([]Header{
 | 
			
		||||
			Header{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
			{Name: "response-header", Value: "responseValue"},
 | 
			
		||||
		})
 | 
			
		||||
		<-sendC
 | 
			
		||||
		n, err := stream.Read([]byte{0})
 | 
			
		||||
| 
						 | 
				
			
			@ -490,7 +503,7 @@ func TestUnexpectedShutdown(t *testing.T) {
 | 
			
		|||
	muxPair.HandshakeAndServe(t)
 | 
			
		||||
 | 
			
		||||
	stream, err := muxPair.EdgeMux.OpenStream(
 | 
			
		||||
		[]Header{Header{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		[]Header{{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
		nil,
 | 
			
		||||
	)
 | 
			
		||||
	// Close the underlying connection before telling the origin to write.
 | 
			
		||||
| 
						 | 
				
			
			@ -552,7 +565,7 @@ func TestOpenAfterDisconnect(t *testing.T) {
 | 
			
		|||
		}
 | 
			
		||||
 | 
			
		||||
		_, err := muxPair.EdgeMux.OpenStream(
 | 
			
		||||
			[]Header{Header{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
			[]Header{{Name: "test-header", Value: "headerValue"}},
 | 
			
		||||
			nil,
 | 
			
		||||
		)
 | 
			
		||||
		if err != ErrConnectionClosed {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,197 @@
 | 
			
		|||
package hello
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"html/template"
 | 
			
		||||
	"io/ioutil"
 | 
			
		||||
	"net"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/gorilla/websocket"
 | 
			
		||||
	"github.com/sirupsen/logrus"
 | 
			
		||||
 | 
			
		||||
	"github.com/cloudflare/cloudflared/tlsconfig"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type templateData struct {
 | 
			
		||||
	ServerName string
 | 
			
		||||
	Request    *http.Request
 | 
			
		||||
	Body       string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type OriginUpTime struct {
 | 
			
		||||
	StartTime time.Time `json:"startTime"`
 | 
			
		||||
	UpTime    string    `json:"uptime"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const defaultServerName = "the Argo Tunnel test server"
 | 
			
		||||
const indexTemplate = `
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
  <head>
 | 
			
		||||
    <meta charset="utf-8">
 | 
			
		||||
    <meta http-equiv="X-UA-Compatible" content="IE=Edge">
 | 
			
		||||
    <title>
 | 
			
		||||
      Argo Tunnel Connection
 | 
			
		||||
    </title>
 | 
			
		||||
    <meta name="author" content="">
 | 
			
		||||
    <meta name="description" content="Argo Tunnel Connection">
 | 
			
		||||
    <meta name="viewport" content="width=device-width, initial-scale=1">
 | 
			
		||||
    <style>
 | 
			
		||||
      html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}section{display:block}h1{font-size:2em;margin:.67em 0}a{background-color:transparent;-webkit-text-decoration-skip:objects}/* 1 */::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}/* 1 */a,body,dd,div,dl,dt,h1,h4,html,p,section{box-sizing:border-box}.bt{border-top-style:solid;border-top-width:1px}.bl{border-left-style:solid;border-left-width:1px}.b--orange{border-color:#f38020}.br1{border-radius:.125rem}.bw2{border-width:.25rem}.dib{display:inline-block}.sans-serif{font-family:open sans,-apple-system,BlinkMacSystemFont,avenir next,avenir,helvetica neue,helvetica,ubuntu,roboto,noto,segoe ui,arial,sans-serif}.code{font-family:Consolas,monaco,monospace}.b{font-weight:700}.fw3{font-weight:300}.fw4{font-weight:400}.fw5{font-weight:500}.fw6{font-weight:600}.lh-copy{line-height:1.5}.link{text-decoration:none}.link,.link:active,.link:focus,.link:hover,.link:link,.link:visited{transition:color .15s ease-in}.link:focus{outline:1px dotted currentColor}.mw-100{max-width:100%}.mw4{max-width:8rem}.mw7{max-width:48rem}.bg-light-gray{background-color:#f7f7f7}.link-hover:hover{background-color:#1f679e}.white{color:#fff}.bg-white{background-color:#fff}.bg-blue{background-color:#408bc9}.pb2{padding-bottom:.5rem}.pb6{padding-bottom:8rem}.pt3{padding-top:1rem}.pt5{padding-top:4rem}.pv2{padding-top:.5rem;padding-bottom:.5rem}.ph3{padding-left:1rem;padding-right:1rem}.ph4{padding-left:2rem;padding-right:2rem}.ml0{margin-left:0}.mb1{margin-bottom:.25rem}.mb2{margin-bottom:.5rem}.mb3{margin-bottom:1rem}.mt5{margin-top:4rem}.ttu{text-transform:uppercase}.f4{font-size:1.25rem}.f5{font-size:1rem}.f6{font-size:.875rem}.f7{font-size:.75rem}.measure{max-width:30em}.center{margin-left:auto}.center{margin-right:auto}@media screen and (min-width:30em){.f2-ns{font-size:2.25rem}}@media screen and (min-width:30em) and (max-width:60em){.f5-m{font-size:1rem}}@media screen and (min-width:60em){.f4-l{font-size:1.25rem}}
 | 
			
		||||
    .st0{fill:#FFF}.st1{fill:#f48120}.st2{fill:#faad3f}.st3{fill:#404041}
 | 
			
		||||
    </style>
 | 
			
		||||
  </head>
 | 
			
		||||
  <body class="sans-serif black">
 | 
			
		||||
    <div class="bt bw2 b--orange bg-white pb6">
 | 
			
		||||
      <div class="mw7 center ph4 pt3">
 | 
			
		||||
        <svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 109 40.5" class="mw4">
 | 
			
		||||
          <path class="st0" d="M98.6 14.2L93 12.9l-1-.4-25.7.2v12.4l32.3.1z"/>
 | 
			
		||||
          <path class="st1" d="M88.1 24c.3-1 .2-2-.3-2.6-.5-.6-1.2-1-2.1-1.1l-17.4-.2c-.1 0-.2-.1-.3-.1-.1-.1-.1-.2 0-.3.1-.2.2-.3.4-.3l17.5-.2c2.1-.1 4.3-1.8 5.1-3.8l1-2.6c0-.1.1-.2 0-.3-1.1-5.1-5.7-8.9-11.1-8.9-5 0-9.3 3.2-10.8 7.7-1-.7-2.2-1.1-3.6-1-2.4.2-4.3 2.2-4.6 4.6-.1.6 0 1.2.1 1.8-3.9.1-7.1 3.3-7.1 7.3 0 .4 0 .7.1 1.1 0 .2.2.3.3.3h32.1c.2 0 .4-.1.4-.3l.3-1.1z"/>
 | 
			
		||||
          <path class="st2" d="M93.6 12.8h-.5c-.1 0-.2.1-.3.2l-.7 2.4c-.3 1-.2 2 .3 2.6.5.6 1.2 1 2.1 1.1l3.7.2c.1 0 .2.1.3.1.1.1.1.2 0 .3-.1.2-.2.3-.4.3l-3.8.2c-2.1.1-4.3 1.8-5.1 3.8l-.2.9c-.1.1 0 .3.2.3h13.2c.2 0 .3-.1.3-.3.2-.8.4-1.7.4-2.6 0-5.2-4.3-9.5-9.5-9.5"/>
 | 
			
		||||
          <path class="st3" d="M104.4 30.8c-.5 0-.9-.4-.9-.9s.4-.9.9-.9.9.4.9.9-.4.9-.9.9m0-1.6c-.4 0-.7.3-.7.7 0 .4.3.7.7.7.4 0 .7-.3.7-.7 0-.4-.3-.7-.7-.7m.4 1.2h-.2l-.2-.3h-.2v.3h-.2v-.9h.5c.2 0 .3.1.3.3 0 .1-.1.2-.2.3l.2.3zm-.3-.5c.1 0 .1 0 .1-.1s-.1-.1-.1-.1h-.3v.3h.3zM14.8 29H17v6h3.8v1.9h-6zM23.1 32.9c0-2.3 1.8-4.1 4.3-4.1s4.2 1.8 4.2 4.1-1.8 4.1-4.3 4.1c-2.4 0-4.2-1.8-4.2-4.1m6.3 0c0-1.2-.8-2.2-2-2.2s-2 1-2 2.1.8 2.1 2 2.1c1.2.2 2-.8 2-2M34.3 33.4V29h2.2v4.4c0 1.1.6 1.7 1.5 1.7s1.5-.5 1.5-1.6V29h2.2v4.4c0 2.6-1.5 3.7-3.7 3.7-2.3-.1-3.7-1.2-3.7-3.7M45 29h3.1c2.8 0 4.5 1.6 4.5 3.9s-1.7 4-4.5 4h-3V29zm3.1 5.9c1.3 0 2.2-.7 2.2-2s-.9-2-2.2-2h-.9v4h.9zM55.7 29H62v1.9h-4.1v1.3h3.7V34h-3.7v2.9h-2.2zM65.1 29h2.2v6h3.8v1.9h-6zM76.8 28.9H79l3.4 8H80l-.6-1.4h-3.1l-.6 1.4h-2.3l3.4-8zm2 4.9l-.9-2.2-.9 2.2h1.8zM85.2 29h3.7c1.2 0 2 .3 2.6.9.5.5.7 1.1.7 1.8 0 1.2-.6 2-1.6 2.4l1.9 2.8H90l-1.6-2.4h-1v2.4h-2.2V29zm3.6 3.8c.7 0 1.2-.4 1.2-.9 0-.6-.5-.9-1.2-.9h-1.4v1.9h1.4zM95.3 29h6.4v1.8h-4.2V32h3.8v1.8h-3.8V35h4.3v1.9h-6.5zM10 33.9c-.3.7-1 1.2-1.8 1.2-1.2 0-2-1-2-2.1s.8-2.1 2-2.1c.9 0 1.6.6 1.9 1.3h2.3c-.4-1.9-2-3.3-4.2-3.3-2.4 0-4.3 1.8-4.3 4.1s1.8 4.1 4.2 4.1c2.1 0 3.7-1.4 4.2-3.2H10z"/>
 | 
			
		||||
        </svg>
 | 
			
		||||
        <h1 class="f4 f2-ns mt5 fw5">Congrats! You created your first tunnel!</h1>
 | 
			
		||||
        <p class="f6 f5-m f4-l measure lh-copy fw3">
 | 
			
		||||
          Argo Tunnel exposes locally running applications to the internet by
 | 
			
		||||
          running an encrypted, virtual tunnel from your laptop or server to
 | 
			
		||||
          Cloudflare's edge network.
 | 
			
		||||
        </p>
 | 
			
		||||
        <p class="b f5 mt5 fw6">Ready for the next step?</p>
 | 
			
		||||
        <a
 | 
			
		||||
          class="fw6 link white bg-blue ph4 pv2 br1 dib f5 link-hover"
 | 
			
		||||
          style="border-bottom: 1px solid #1f679e"
 | 
			
		||||
          href="https://developers.cloudflare.com/argo-tunnel/">
 | 
			
		||||
          Get started here
 | 
			
		||||
        </a>
 | 
			
		||||
       <section>
 | 
			
		||||
          <h4 class="f6 fw4 pt5 mb2">Request</h4>
 | 
			
		||||
          <dl class="bl bw2 b--orange ph3 pt3 pb2 bg-light-gray f7 code overflow-x-auto mw-100">
 | 
			
		||||
						<dd class="ml0 mb3 f5">Method: {{.Request.Method}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Protocol: {{.Request.Proto}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Request URL: {{.Request.URL}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Transfer encoding: {{.Request.TransferEncoding}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Host: {{.Request.Host}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Remote address: {{.Request.RemoteAddr}}</dd>
 | 
			
		||||
						<dd class="ml0 mb3 f5">Request URI: {{.Request.RequestURI}}</dd>
 | 
			
		||||
{{range $key, $value := .Request.Header}}
 | 
			
		||||
						<dd class="ml0 mb3 f5">Header: {{$key}}, Value: {{$value}}</dd>
 | 
			
		||||
{{end}}
 | 
			
		||||
						<dd class="ml0 mb3 f5">Body: {{.Body}}</dd>
 | 
			
		||||
					</dl>
 | 
			
		||||
        </section>
 | 
			
		||||
     </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </body>
 | 
			
		||||
</html>
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func StartHelloWorldServer(logger *logrus.Logger, listener net.Listener, shutdownC <-chan struct{}) error {
 | 
			
		||||
	logger.Infof("Starting Hello World server at %s", listener.Addr())
 | 
			
		||||
	serverName := defaultServerName
 | 
			
		||||
	if hostname, err := os.Hostname(); err == nil {
 | 
			
		||||
		serverName = hostname
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	upgrader := websocket.Upgrader{
 | 
			
		||||
		ReadBufferSize:  1024,
 | 
			
		||||
		WriteBufferSize: 1024,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	httpServer := &http.Server{Addr: listener.Addr().String(), Handler: nil}
 | 
			
		||||
	go func() {
 | 
			
		||||
		<-shutdownC
 | 
			
		||||
		httpServer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	http.HandleFunc("/uptime", uptimeHandler(time.Now()))
 | 
			
		||||
	http.HandleFunc("/ws", websocketHandler(logger, upgrader))
 | 
			
		||||
	http.HandleFunc("/", rootHandler(serverName))
 | 
			
		||||
	err := httpServer.Serve(listener)
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateTLSListener(address string) (net.Listener, error) {
 | 
			
		||||
	certificate, err := tlsconfig.GetHelloCertificate()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If the port in address is empty, a port number is automatically chosen
 | 
			
		||||
	listener, err := tls.Listen(
 | 
			
		||||
		"tcp",
 | 
			
		||||
		address,
 | 
			
		||||
		&tls.Config{Certificates: []tls.Certificate{certificate}})
 | 
			
		||||
 | 
			
		||||
	return listener, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func uptimeHandler(startTime time.Time) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		// Note that if autoupdate is enabled, the uptime is reset when a new client
 | 
			
		||||
		// release is available
 | 
			
		||||
		resp := &OriginUpTime{StartTime: startTime, UpTime: time.Now().Sub(startTime).String()}
 | 
			
		||||
		respJson, err := json.Marshal(resp)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
		} else {
 | 
			
		||||
			w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
			w.Write(respJson)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// This handler will echo message
 | 
			
		||||
func websocketHandler(logger *logrus.Logger, upgrader websocket.Upgrader) http.HandlerFunc {
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		conn, err := upgrader.Upgrade(w, r, nil)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		defer conn.Close()
 | 
			
		||||
		for {
 | 
			
		||||
			mt, message, err := conn.ReadMessage()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logger.WithError(err).Error("websocket read message error")
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			if err := conn.WriteMessage(mt, message); err != nil {
 | 
			
		||||
				logger.WithError(err).Error("websocket write message error")
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func rootHandler(serverName string) http.HandlerFunc {
 | 
			
		||||
	responseTemplate := template.Must(template.New("index").Parse(indexTemplate))
 | 
			
		||||
	return func(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
		var buffer bytes.Buffer
 | 
			
		||||
		var body string
 | 
			
		||||
		rawBody, err := ioutil.ReadAll(r.Body)
 | 
			
		||||
		if err == nil {
 | 
			
		||||
			body = string(rawBody)
 | 
			
		||||
		} else {
 | 
			
		||||
			body = ""
 | 
			
		||||
		}
 | 
			
		||||
		err = responseTemplate.Execute(&buffer, &templateData{
 | 
			
		||||
			ServerName: serverName,
 | 
			
		||||
			Request:    r,
 | 
			
		||||
			Body:       body,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			w.WriteHeader(http.StatusInternalServerError)
 | 
			
		||||
			fmt.Fprintf(w, "error: %v", err)
 | 
			
		||||
		} else {
 | 
			
		||||
			buffer.WriteTo(w)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
package hello
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestCreateTLSListenerHostAndPortSuccess(t *testing.T) {
 | 
			
		||||
	listener, err := CreateTLSListener("localhost:1234")
 | 
			
		||||
	defer listener.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if listener.Addr().String() == "" {
 | 
			
		||||
		t.Fatal("Fail to find available port")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateTLSListenerOnlyHostSuccess(t *testing.T) {
 | 
			
		||||
	listener, err := CreateTLSListener("localhost:")
 | 
			
		||||
	defer listener.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if listener.Addr().String() == "" {
 | 
			
		||||
		t.Fatal("Fail to find available port")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestCreateTLSListenerOnlyPortSuccess(t *testing.T) {
 | 
			
		||||
	listener, err := CreateTLSListener(":8888")
 | 
			
		||||
	defer listener.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	if listener.Addr().String() == "" {
 | 
			
		||||
		t.Fatal("Fail to find available port")
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -123,7 +123,12 @@ func StartTunnelDaemon(config *TunnelConfig, shutdownC <-chan struct{}, connecte
 | 
			
		|||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ServeTunnelLoop(ctx context.Context, config *TunnelConfig, addr *net.TCPAddr, connectionID uint8, connectedSignal chan struct{}) error {
 | 
			
		||||
func ServeTunnelLoop(ctx context.Context,
 | 
			
		||||
	config *TunnelConfig,
 | 
			
		||||
	addr *net.TCPAddr,
 | 
			
		||||
	connectionID uint8,
 | 
			
		||||
	connectedSignal chan struct{},
 | 
			
		||||
) error {
 | 
			
		||||
	config.Metrics.incrementHaConnections()
 | 
			
		||||
	defer config.Metrics.decrementHaConnections()
 | 
			
		||||
	backoff := BackoffHandler{MaxRetries: config.Retries}
 | 
			
		||||
| 
						 | 
				
			
			@ -482,6 +487,8 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
 | 
			
		|||
		} else {
 | 
			
		||||
			stream.WriteHeaders(H1ResponseToH2Response(response))
 | 
			
		||||
			defer conn.Close()
 | 
			
		||||
			// Copy to/from stream to the undelying connection. Use the underlying
 | 
			
		||||
			// connection because cloudflared doesn't operate on the message themselves
 | 
			
		||||
			websocket.Stream(conn.UnderlyingConn(), stream)
 | 
			
		||||
			h.metrics.incrementResponses(h.connectionID, "200")
 | 
			
		||||
			h.logResponse(response, cfRay)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,7 +5,7 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
// TODO: remove the Origin CA root certs when migrated to Authenticated Origin Pull certs
 | 
			
		||||
const cloudflareRootCA = `
 | 
			
		||||
var cloudflareRootCA = []byte(`
 | 
			
		||||
Issuer: C=US, ST=California, L=San Francisco, O=CloudFlare, Inc., OU=CloudFlare Origin SSL ECC Certificate Authority
 | 
			
		||||
-----BEGIN CERTIFICATE-----
 | 
			
		||||
MIICiDCCAi6gAwIBAgIUXZP3MWb8MKwBE1Qbawsp1sfA/Y4wCgYIKoZIzj0EAwIw
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +83,7 @@ Bz+1CD4D/bWrs3cC9+kk/jFmrrAymZlkFX8tDb5aXASSLJjUjcptci9SKqtI2h0J
 | 
			
		|||
wUGkD7+bQAr+7vr8/R+CBmNMe7csE8NeEX6lVMF7Dh0a1YKQa6hUN18bBuYgTMuT
 | 
			
		||||
QzMmZpRpIBB321ZBlcnlxiTJvWxvbCPHKHj20VwwAz7LONF59s84ZsOqfoBv8gKM
 | 
			
		||||
s0s5dsq5zpLeaw==
 | 
			
		||||
-----END CERTIFICATE-----`
 | 
			
		||||
-----END CERTIFICATE-----`)
 | 
			
		||||
 | 
			
		||||
func GetCloudflareRootCA() *x509.CertPool {
 | 
			
		||||
	ca := x509.NewCertPool()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,6 +9,7 @@ import (
 | 
			
		|||
	"net"
 | 
			
		||||
 | 
			
		||||
	"github.com/cloudflare/cloudflared/log"
 | 
			
		||||
	"github.com/pkg/errors"
 | 
			
		||||
	"gopkg.in/urfave/cli.v2"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -64,21 +65,27 @@ func LoadCert(certPath string) *x509.CertPool {
 | 
			
		|||
	return ca
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadOriginCertsPool() *x509.CertPool {
 | 
			
		||||
func LoadGlobalCertPool() (*x509.CertPool, error) {
 | 
			
		||||
	success := false
 | 
			
		||||
 | 
			
		||||
	// First, obtain the system certificate pool
 | 
			
		||||
	certPool, systemCertPoolErr := x509.SystemCertPool()
 | 
			
		||||
	if systemCertPoolErr != nil {
 | 
			
		||||
		logger.Warnf("error obtaining the system certificates: %s", systemCertPoolErr)
 | 
			
		||||
		certPool = x509.NewCertPool()
 | 
			
		||||
	} else {
 | 
			
		||||
		success = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Next, append the Cloudflare CA pool into the system pool
 | 
			
		||||
	if !certPool.AppendCertsFromPEM([]byte(cloudflareRootCA)) {
 | 
			
		||||
		logger.Warn("could not append the CF certificate to the system certificate pool")
 | 
			
		||||
	if !certPool.AppendCertsFromPEM(cloudflareRootCA) {
 | 
			
		||||
		logger.Warn("could not append the CF certificate to the cloudflared certificate pool")
 | 
			
		||||
	} else {
 | 
			
		||||
		success = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		if systemCertPoolErr != nil { // Obtaining both certificates failed; this is a fatal error
 | 
			
		||||
			logger.WithError(systemCertPoolErr).Fatalf("Error loading the certificate pool")
 | 
			
		||||
		}
 | 
			
		||||
	if success != true { // Obtaining any of the CAs has failed; this is a fatal error
 | 
			
		||||
		return nil, errors.New("error loading any of the CAs into the global certificate pool")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Finally, add the Hello certificate into the pool (since it's self-signed)
 | 
			
		||||
| 
						 | 
				
			
			@ -89,7 +96,34 @@ func LoadOriginCertsPool() *x509.CertPool {
 | 
			
		|||
 | 
			
		||||
	certPool.AddCert(helloCertificate)
 | 
			
		||||
 | 
			
		||||
	return certPool
 | 
			
		||||
	return certPool, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func LoadOriginCertPool(originCAPoolPEM []byte) (*x509.CertPool, error) {
 | 
			
		||||
	success := false
 | 
			
		||||
 | 
			
		||||
	// Get the global pool
 | 
			
		||||
	certPool, globalPoolErr := LoadGlobalCertPool()
 | 
			
		||||
	if globalPoolErr != nil {
 | 
			
		||||
		certPool = x509.NewCertPool()
 | 
			
		||||
	} else {
 | 
			
		||||
		success = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Then, add any custom origin CA pool the user may have passed
 | 
			
		||||
	if originCAPoolPEM != nil {
 | 
			
		||||
		if !certPool.AppendCertsFromPEM(originCAPoolPEM) {
 | 
			
		||||
			logger.Warn("could not append the provided origin CA to the cloudflared certificate pool")
 | 
			
		||||
		} else {
 | 
			
		||||
			success = true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if success != true {
 | 
			
		||||
		return nil, errors.New("error loading any of the CAs into the origin certificate pool")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return certPool, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateTunnelConfig(c *cli.Context, addrs []string) *tls.Config {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,211 @@
 | 
			
		|||
package tlsconfig
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"crypto/x509"
 | 
			
		||||
	"crypto/x509/pkix"
 | 
			
		||||
	"encoding/asn1"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Generated using `openssl req -newkey rsa:512 -nodes -x509 -days 3650`
 | 
			
		||||
var samplePEM = []byte(`
 | 
			
		||||
-----BEGIN CERTIFICATE-----
 | 
			
		||||
MIIB4DCCAYoCCQCb/H0EUrdXEjANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV
 | 
			
		||||
UzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcGA1UECgwQQ2xv
 | 
			
		||||
dWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVneTERMA8GA1UE
 | 
			
		||||
AwwIVGVzdCBPbmUwHhcNMTgwNDI2MTYxMDUxWhcNMjgwNDIzMTYxMDUxWjB3MQsw
 | 
			
		||||
CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcG
 | 
			
		||||
A1UECgwQQ2xvdWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVn
 | 
			
		||||
eTERMA8GA1UEAwwIVGVzdCBPbmUwXDANBgkqhkiG9w0BAQEFAANLADBIAkEAwVQD
 | 
			
		||||
K0SJ25UFLznm2pU3zhzMEvpDEofHVNnCjk4mlDrtVop7PkKZ8pDEmuQANltUrxC8
 | 
			
		||||
yHBE2wXMv+GlH+bDtwIDAQABMA0GCSqGSIb3DQEBCwUAA0EAjVYQzozIFPkt/HRY
 | 
			
		||||
uUoZ8zEHIDICb0syFf5VAjm9AgTwIPzUmD+c5vl6LWDnxq7L45nLCzhhQ6YmiwDz
 | 
			
		||||
X7Wcyg==
 | 
			
		||||
-----END CERTIFICATE-----
 | 
			
		||||
-----BEGIN CERTIFICATE-----
 | 
			
		||||
MIIB4DCCAYoCCQDZfCdAJ+mwzDANBgkqhkiG9w0BAQsFADB3MQswCQYDVQQGEwJV
 | 
			
		||||
UzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcGA1UECgwQQ2xv
 | 
			
		||||
dWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVneTERMA8GA1UE
 | 
			
		||||
AwwIVGVzdCBUd28wHhcNMTgwNDI2MTYxMTIwWhcNMjgwNDIzMTYxMTIwWjB3MQsw
 | 
			
		||||
CQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxDzANBgNVBAcMBkF1c3RpbjEZMBcG
 | 
			
		||||
A1UECgwQQ2xvdWRmbGFyZSwgSW5jLjEZMBcGA1UECwwQUHJvZHVjdCBTdHJhdGVn
 | 
			
		||||
eTERMA8GA1UEAwwIVGVzdCBUd28wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAoHKp
 | 
			
		||||
ROVK3zCSsH7ocYeyRAML4V7SFAbZcb4WIwDnE08oMBVRkQVcW5tqEkvG3RiClfzV
 | 
			
		||||
wZIJ3CfqKIeSNSDU9wIDAQABMA0GCSqGSIb3DQEBCwUAA0EAJw2gUbnPiq4C2p5b
 | 
			
		||||
iWzlA9Q7aKo+VQ4H7IZS7tTccr59nVjvH/TG3eWujpnocr4TOqW9M3CK1DF9mUGP
 | 
			
		||||
3pQ3Jg==
 | 
			
		||||
-----END CERTIFICATE-----
 | 
			
		||||
`)
 | 
			
		||||
 | 
			
		||||
var systemCertPoolSubjects []*pkix.Name
 | 
			
		||||
 | 
			
		||||
type certificateFixture struct {
 | 
			
		||||
	ou string
 | 
			
		||||
	cn string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMain(m *testing.M) {
 | 
			
		||||
	systemCertPool, err := x509.SystemCertPool()
 | 
			
		||||
	if isUnrecoverableError(err) {
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if systemCertPool == nil {
 | 
			
		||||
		// On Windows, let's just assume the system cert pool was empty
 | 
			
		||||
		systemCertPool = x509.NewCertPool()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	systemCertPoolSubjects, err = getCertPoolSubjects(systemCertPool)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		os.Exit(1)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	os.Exit(m.Run())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOriginCertPoolJustSystemPool(t *testing.T) {
 | 
			
		||||
	certPoolSubjects := loadCertPoolSubjects(t, nil)
 | 
			
		||||
	extraSubjects := subjectSubtract(systemCertPoolSubjects, certPoolSubjects)
 | 
			
		||||
 | 
			
		||||
	// Remove extra subjects from the cert pool
 | 
			
		||||
	var filteredSystemCertPoolSubjects []*pkix.Name
 | 
			
		||||
 | 
			
		||||
	t.Log(extraSubjects)
 | 
			
		||||
 | 
			
		||||
OUTER:
 | 
			
		||||
	for _, subject := range certPoolSubjects {
 | 
			
		||||
		for _, extraSubject := range extraSubjects {
 | 
			
		||||
			if subject == extraSubject {
 | 
			
		||||
				t.Log(extraSubject)
 | 
			
		||||
				continue OUTER
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		filteredSystemCertPoolSubjects = append(filteredSystemCertPoolSubjects, subject)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, len(filteredSystemCertPoolSubjects), len(systemCertPoolSubjects))
 | 
			
		||||
 | 
			
		||||
	difference := subjectSubtract(systemCertPoolSubjects, filteredSystemCertPoolSubjects)
 | 
			
		||||
	assert.Equal(t, 0, len(difference))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOriginCertPoolCFCertificates(t *testing.T) {
 | 
			
		||||
	certPoolSubjects := loadCertPoolSubjects(t, nil)
 | 
			
		||||
 | 
			
		||||
	extraSubjects := subjectSubtract(systemCertPoolSubjects, certPoolSubjects)
 | 
			
		||||
 | 
			
		||||
	expected := []*certificateFixture{
 | 
			
		||||
		{ou: "CloudFlare Origin SSL ECC Certificate Authority"},
 | 
			
		||||
		{ou: "CloudFlare Origin SSL Certificate Authority"},
 | 
			
		||||
		{cn: "origin-pull.cloudflare.net"},
 | 
			
		||||
		{cn: "Argo Tunnel Sample Hello Server Certificate"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assertFixturesMatchSubjects(t, expected, extraSubjects)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestLoadOriginCertPoolWithExtraPEMs(t *testing.T) {
 | 
			
		||||
	certPoolWithoutPEMSubjects := loadCertPoolSubjects(t, nil)
 | 
			
		||||
	certPoolWithPEMSubjects := loadCertPoolSubjects(t, samplePEM)
 | 
			
		||||
 | 
			
		||||
	difference := subjectSubtract(certPoolWithoutPEMSubjects, certPoolWithPEMSubjects)
 | 
			
		||||
 | 
			
		||||
	assert.Equal(t, 2, len(difference))
 | 
			
		||||
 | 
			
		||||
	expected := []*certificateFixture{
 | 
			
		||||
		{cn: "Test One"},
 | 
			
		||||
		{cn: "Test Two"},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	assertFixturesMatchSubjects(t, expected, difference)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func loadCertPoolSubjects(t *testing.T, originCAPoolPEM []byte) []*pkix.Name {
 | 
			
		||||
	certPool, err := LoadOriginCertPool(originCAPoolPEM)
 | 
			
		||||
	if isUnrecoverableError(err) {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	assert.NotEmpty(t, certPool.Subjects())
 | 
			
		||||
	certPoolSubjects, err := getCertPoolSubjects(certPool)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return certPoolSubjects
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func assertFixturesMatchSubjects(t *testing.T, fixtures []*certificateFixture, subjects []*pkix.Name) {
 | 
			
		||||
	assert.Equal(t, len(fixtures), len(subjects))
 | 
			
		||||
 | 
			
		||||
	for _, fixture := range fixtures {
 | 
			
		||||
		found := false
 | 
			
		||||
		for _, subject := range subjects {
 | 
			
		||||
			found = found || fixtureMatchesSubjectPredicate(fixture, subject)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !found {
 | 
			
		||||
			t.Fail()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func fixtureMatchesSubjectPredicate(fixture *certificateFixture, subject *pkix.Name) bool {
 | 
			
		||||
	cnMatch := true
 | 
			
		||||
	if fixture.cn != "" {
 | 
			
		||||
		cnMatch = fixture.cn == subject.CommonName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ouMatch := true
 | 
			
		||||
	if fixture.ou != "" {
 | 
			
		||||
		ouMatch = len(subject.OrganizationalUnit) > 0 && fixture.ou == subject.OrganizationalUnit[0]
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return cnMatch && ouMatch
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func subjectSubtract(left []*pkix.Name, right []*pkix.Name) []*pkix.Name {
 | 
			
		||||
	var difference []*pkix.Name
 | 
			
		||||
 | 
			
		||||
	var found bool
 | 
			
		||||
	for _, r := range right {
 | 
			
		||||
		found = false
 | 
			
		||||
		for _, l := range left {
 | 
			
		||||
			if (*l).String() == (*r).String() {
 | 
			
		||||
				found = true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if !found {
 | 
			
		||||
			difference = append(difference, r)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return difference
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getCertPoolSubjects(certPool *x509.CertPool) ([]*pkix.Name, error) {
 | 
			
		||||
	var subjects []*pkix.Name
 | 
			
		||||
 | 
			
		||||
	for _, subject := range certPool.Subjects() {
 | 
			
		||||
		var sequence pkix.RDNSequence
 | 
			
		||||
		_, err := asn1.Unmarshal(subject, &sequence)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		name := pkix.Name{}
 | 
			
		||||
		name.FillFromRDNSequence(&sequence)
 | 
			
		||||
 | 
			
		||||
		subjects = append(subjects, &name)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return subjects, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isUnrecoverableError(err error) bool {
 | 
			
		||||
	return err != nil && err.Error() != "crypto/x509: system root pool is not available on Windows"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -43,7 +43,7 @@ func NewUpstreamHTTPS(endpoint string) (Upstream, error) {
 | 
			
		|||
	http2.ConfigureTransport(transport)
 | 
			
		||||
 | 
			
		||||
	client := &http.Client{
 | 
			
		||||
		Timeout:   time.Second * defaultTimeout,
 | 
			
		||||
		Timeout:   defaultTimeout,
 | 
			
		||||
		Transport: transport,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,16 +13,28 @@ import (
 | 
			
		|||
	"github.com/gorilla/websocket"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var stripWebsocketHeaders = []string {
 | 
			
		||||
	"Upgrade",
 | 
			
		||||
	"Connection",
 | 
			
		||||
	"Sec-Websocket-Key",
 | 
			
		||||
	"Sec-Websocket-Version",
 | 
			
		||||
	"Sec-Websocket-Extensions",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsWebSocketUpgrade checks to see if the request is a WebSocket connection.
 | 
			
		||||
func IsWebSocketUpgrade(req *http.Request) bool {
 | 
			
		||||
	return websocket.IsWebSocketUpgrade(req)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ClientConnect creates a WebSocket client connection for provided request. Caller is responsible for closing.
 | 
			
		||||
// ClientConnect creates a WebSocket client connection for provided request. Caller is responsible for closing
 | 
			
		||||
// the connection. The response body may not contain the entire response and does
 | 
			
		||||
// not need to be closed by the application.
 | 
			
		||||
func ClientConnect(req *http.Request, tlsClientConfig *tls.Config) (*websocket.Conn, *http.Response, error) {
 | 
			
		||||
	req.URL.Scheme = changeRequestScheme(req)
 | 
			
		||||
	wsHeaders := websocketHeaders(req)
 | 
			
		||||
 | 
			
		||||
	d := &websocket.Dialer{TLSClientConfig: tlsClientConfig}
 | 
			
		||||
	conn, response, err := d.Dial(req.URL.String(), nil)
 | 
			
		||||
	conn, response, err := d.Dial(req.URL.String(), wsHeaders)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, nil, err
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			@ -62,6 +74,21 @@ func Stream(conn, backendConn io.ReadWriter) {
 | 
			
		|||
	<-proxyDone
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// the gorilla websocket library sets its own Upgrade, Connection, Sec-WebSocket-Key,
 | 
			
		||||
// Sec-WebSocket-Version and Sec-Websocket-Extensions headers.
 | 
			
		||||
// https://github.com/gorilla/websocket/blob/master/client.go#L189-L194.
 | 
			
		||||
func websocketHeaders(req *http.Request) http.Header {
 | 
			
		||||
	wsHeaders := make(http.Header)
 | 
			
		||||
	for key, val := range req.Header {
 | 
			
		||||
			wsHeaders[key] = val
 | 
			
		||||
	}
 | 
			
		||||
	// Assume the header keys are in canonical format.
 | 
			
		||||
	for _,  header := range stripWebsocketHeaders {
 | 
			
		||||
		wsHeaders.Del(header)
 | 
			
		||||
	}
 | 
			
		||||
	return wsHeaders
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// sha1Base64 sha1 and then base64 encodes str.
 | 
			
		||||
func sha1Base64(str string) string {
 | 
			
		||||
	hasher := sha1.New()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,100 @@
 | 
			
		|||
package websocket
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
  "crypto/tls"
 | 
			
		||||
  "io"
 | 
			
		||||
  "math/rand"
 | 
			
		||||
  "net/http"
 | 
			
		||||
  "testing"
 | 
			
		||||
 | 
			
		||||
  "github.com/sirupsen/logrus"
 | 
			
		||||
  "github.com/stretchr/testify/assert"
 | 
			
		||||
 | 
			
		||||
  "golang.org/x/net/websocket"
 | 
			
		||||
 | 
			
		||||
  "github.com/cloudflare/cloudflared/hello"
 | 
			
		||||
  "github.com/cloudflare/cloudflared/tlsconfig"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
  // example in Sec-Websocket-Key in rfc6455
 | 
			
		||||
  testSecWebsocketKey    = "dGhlIHNhbXBsZSBub25jZQ=="
 | 
			
		||||
  // example Sec-Websocket-Accept in rfc6455
 | 
			
		||||
  testSecWebsocketAccept = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func testRequest(t *testing.T, url string, stream io.ReadWriter) *http.Request {
 | 
			
		||||
  req, err := http.NewRequest("GET", url, stream)
 | 
			
		||||
  if err != nil {
 | 
			
		||||
    t.Fatalf("testRequestHeader error")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  req.Header.Add("Connection", "Upgrade")
 | 
			
		||||
  req.Header.Add("Upgrade", "WebSocket")
 | 
			
		||||
  req.Header.Add("Sec-Websocket-Key", testSecWebsocketKey)
 | 
			
		||||
  req.Header.Add("Sec-Websocket-Protocol", "tunnel-protocol")
 | 
			
		||||
  req.Header.Add("Sec-Websocket-Version", "13")
 | 
			
		||||
  req.Header.Add("User-Agent", "curl/7.59.0")
 | 
			
		||||
 | 
			
		||||
  return req
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func websocketClientTLSConfig(t *testing.T) *tls.Config {
 | 
			
		||||
  certPool, err := tlsconfig.LoadOriginCertPool(nil)
 | 
			
		||||
  assert.NoError(t, err)
 | 
			
		||||
  assert.NotNil(t, certPool)
 | 
			
		||||
  return &tls.Config{RootCAs: certPool}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestWebsocketHeaders(t *testing.T) {
 | 
			
		||||
  req := testRequest(t, "http://example.com", nil)
 | 
			
		||||
  wsHeaders := websocketHeaders(req)
 | 
			
		||||
  for _, header := range stripWebsocketHeaders {
 | 
			
		||||
    assert.Empty(t, wsHeaders[header])
 | 
			
		||||
  }
 | 
			
		||||
  assert.Equal(t, "curl/7.59.0", wsHeaders.Get("User-Agent"))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGenerateAcceptKey(t *testing.T) {
 | 
			
		||||
  req := testRequest(t, "http://example.com", nil)
 | 
			
		||||
  assert.Equal(t, testSecWebsocketAccept, generateAcceptKey(req))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServe(t *testing.T) {
 | 
			
		||||
  logger := logrus.New()
 | 
			
		||||
  shutdownC := make(chan struct{})
 | 
			
		||||
  errC := make(chan error)
 | 
			
		||||
  listener, err := hello.CreateTLSListener("localhost:1111")
 | 
			
		||||
  assert.NoError(t, err)
 | 
			
		||||
  defer listener.Close()
 | 
			
		||||
 | 
			
		||||
  go func() {
 | 
			
		||||
    errC <- hello.StartHelloWorldServer(logger, listener, shutdownC)
 | 
			
		||||
  }()
 | 
			
		||||
 | 
			
		||||
  req := testRequest(t, "https://localhost:1111/ws", nil)
 | 
			
		||||
 | 
			
		||||
  tlsConfig := websocketClientTLSConfig(t)
 | 
			
		||||
  assert.NotNil(t, tlsConfig)
 | 
			
		||||
  conn, resp, err := ClientConnect(req, tlsConfig)
 | 
			
		||||
  assert.NoError(t, err)
 | 
			
		||||
  assert.Equal(t, testSecWebsocketAccept, resp.Header.Get("Sec-WebSocket-Accept"))
 | 
			
		||||
 | 
			
		||||
  for i := 0; i < 1000; i++ {
 | 
			
		||||
    messageSize := rand.Int() % 2048 + 1
 | 
			
		||||
    clientMessage := make([]byte, messageSize)
 | 
			
		||||
    // rand.Read always returns len(clientMessage) and a nil error
 | 
			
		||||
    rand.Read(clientMessage)
 | 
			
		||||
    err = conn.WriteMessage(websocket.BinaryFrame, clientMessage)
 | 
			
		||||
    assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
    messageType, message, err := conn.ReadMessage()
 | 
			
		||||
    assert.NoError(t, err)
 | 
			
		||||
    assert.Equal(t, websocket.BinaryFrame, messageType)
 | 
			
		||||
    assert.Equal(t, clientMessage, message)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  conn.Close()
 | 
			
		||||
  close(shutdownC)
 | 
			
		||||
  <-errC
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
		Reference in New Issue