TUN-1093: Revert cloudflared to 2018.8.0

pull/54/head
Areg Harutyunyan 4 years ago
parent 2fc2f3c927
commit faeba02e57

36
Gopkg.lock generated

@ -56,34 +56,12 @@
]
revision = "992e7928c7c258628d2b13b769acc86781b9faea"
[[projects]]
branch = "master"
name = "github.com/coreos/go-oidc"
packages = [
"http",
"jose",
"key",
"oauth2",
"oidc"
]
revision = "a93f71fdfe73d2c0f5413c0565eea0af6523a6df"
[[projects]]
name = "github.com/coreos/go-systemd"
packages = ["daemon"]
revision = "39ca1b05acc7ad1220e09f133283b8859a8b71ab"
version = "v17"
[[projects]]
name = "github.com/coreos/pkg"
packages = [
"health",
"httputil",
"timeutil"
]
revision = "97fdf19511ea361ae1c100dd393cc47f8dcfa1e1"
version = "v4"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
@ -175,12 +153,6 @@
packages = ["go/otgrpc"]
revision = "8e809c8a86450a29b90dcc9efbf062d0fe6d9746"
[[projects]]
name = "github.com/jonboulle/clockwork"
packages = ["."]
revision = "2eee05ed794112d45db504eb05aa693efd2b8b09"
version = "v0.1.0"
[[projects]]
branch = "master"
name = "github.com/lib/pq"
@ -310,14 +282,8 @@
branch = "master"
name = "golang.org/x/crypto"
packages = [
"curve25519",
"ed25519",
"ed25519/internal/edwards25519",
"internal/subtle",
"nacl/box",
"nacl/secretbox",
"poly1305",
"salsa20/salsa",
"ssh/terminal"
]
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602"
@ -461,6 +427,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "ee681bef3527e49801c841e313f98b40116eafe8b60be21273956eeb96487486"
inputs-digest = "9aac6a0260101988582bfa55d39559e53cf8ccd7e37caad3350aeea768f5bdb2"
solver-name = "gps-cdcl"
solver-version = 1

@ -48,7 +48,6 @@
[[constraint]]
name = "zombiezen.com/go/capnproto2"
source = "https://github.com/zombiezen/go-capnproto2"
version = "2.17.1"
[[constraint]]
name = "github.com/gorilla/websocket"
@ -65,11 +64,3 @@
[[override]]
name = "github.com/mholt/caddy"
branch = "master"
[[constraint]]
branch = "master"
name = "github.com/coreos/go-oidc"
[[constraint]]
branch = "master"
name = "golang.org/x/crypto"

@ -55,7 +55,3 @@ release: bin/equinox
bin/equinox:
mkdir -p bin
curl -s https://bin.equinox.io/c/75JtLRTsJ3n/release-tool-beta-$(EQUINOX_PLATFORM).tgz | tar xz -C bin/
.PHONY: tunnel-deps
tunnel-deps:
capnp compile -ogo -I ./tunnelrpc tunnelrpc/tunnelrpc.capnp

@ -1,199 +0,0 @@
package access
import (
"errors"
"fmt"
"net/url"
"os"
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
"golang.org/x/net/idna"
"github.com/cloudflare/cloudflared/log"
raven "github.com/getsentry/raven-go"
cli "gopkg.in/urfave/cli.v2"
)
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
// Flags return the global flags for Access related commands (hopefully none)
func Flags() []cli.Flag {
return []cli.Flag{} // no flags yet.
}
// Commands returns all the Access related subcommands
func Commands() []*cli.Command {
return []*cli.Command{
{
Name: "access",
Category: "Access (BETA)",
Usage: "access <subcommand>",
Description: `(BETA) Cloudflare Access protects internal resources by securing, authenticating and monitoring access
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
applications from the command line. This feature is considered beta. Your feedback is greatly appreciated!
https://cfl.re/CLIAuthBeta`,
Subcommands: []*cli.Command{
{
Name: "login",
Action: login,
Usage: "login <url of access application>",
Description: `The login subcommand initiates an authentication flow with your identity provider.
The subcommand will launch a browser. For headless systems, a url is provided.
Once authenticated with your identity provider, the login command will generate a JSON Web Token (JWT)
scoped to your identity, the application you intend to reach, and valid for a session duration set by your
administrator. cloudflared stores the token in local storage.`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "url",
Hidden: true,
},
},
},
{
Name: "curl",
Action: curl,
Usage: "curl <args>",
Description: `The curl subcommand wraps curl and automatically injects the JWT into a cf-access-token
header when using curl to reach an application behind Access.`,
ArgsUsage: "allow-request will allow the curl request to continue even if the jwt is not present.",
SkipFlagParsing: true,
},
{
Name: "token",
Action: token,
Usage: "token -app=<url of access application>",
ArgsUsage: "url of Access application",
Description: `The token subcommand produces a JWT which can be used to authenticate requests.`,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "app",
},
},
},
},
},
}
}
// login pops up the browser window to do the actual login and JWT generation
func login(c *cli.Context) error {
raven.SetDSN(sentryDSN)
logger := log.CreateLogger()
args := c.Args()
appURL, err := url.Parse(args.First())
if args.Len() < 1 || err != nil {
logger.Errorf("Please provide the url of the Access application\n")
return err
}
if _, err := fetchToken(c, appURL); err != nil {
logger.Errorf("Failed to fetch token: %s\n", err)
return err
}
return nil
}
// curl provides a wrapper around curl, passing Access JWT along in request
func curl(c *cli.Context) error {
raven.SetDSN(sentryDSN)
logger := log.CreateLogger()
args := c.Args()
if args.Len() < 1 {
logger.Error("Please provide the access app and command you wish to run.")
return errors.New("incorrect args")
}
cmdArgs, appURL, allowRequest, err := buildCurlCmdArgs(args.Slice())
if err != nil {
return err
}
token, err := getTokenIfExists(appURL)
if err != nil || token == "" {
if allowRequest {
logger.Warn("You don't have an Access token set. Please run access token <access application> to fetch one.")
return shell.Run("curl", cmdArgs...)
}
token, err = fetchToken(c, appURL)
if err != nil {
logger.Error("Failed to refresh token: ", err)
return err
}
}
cmdArgs = append(cmdArgs, "-H")
cmdArgs = append(cmdArgs, fmt.Sprintf("cf-access-token: %s", token))
return shell.Run("curl", cmdArgs...)
}
// token dumps provided token to stdout
func token(c *cli.Context) error {
raven.SetDSN(sentryDSN)
appURL, err := url.Parse(c.String("app"))
if err != nil || c.NumFlags() < 1 {
fmt.Fprintln(os.Stderr, "Please provide a url.")
return err
}
token, err := getTokenIfExists(appURL)
if err != nil || token == "" {
fmt.Fprintln(os.Stderr, "Unable to find token for provided application. Please run token command to generate token.")
return err
}
if _, err := fmt.Fprint(os.Stdout, token); err != nil {
fmt.Fprintln(os.Stderr, "Failed to write token to stdout.")
return err
}
return nil
}
// processURL will preprocess the string (parse to a url, convert to punycode, etc).
func processURL(s string) (*url.URL, error) {
u, err := url.ParseRequestURI(s)
if err != nil {
return nil, err
}
host, err := idna.ToASCII(u.Hostname())
if err != nil { // we fail to convert to punycode, just return the url we parsed.
return u, nil
}
if u.Port() != "" {
u.Host = fmt.Sprintf("%s:%s", host, u.Port())
} else {
u.Host = host
}
return u, nil
}
// buildCurlCmdArgs will build the curl cmd args
func buildCurlCmdArgs(cmdArgs []string) ([]string, *url.URL, bool, error) {
allowRequest, iAllowRequest := false, 0
var appURL *url.URL
for i, arg := range cmdArgs {
if arg == "-allow-request" || arg == "-ar" {
iAllowRequest = i
allowRequest = true
}
u, err := processURL(arg)
if err == nil {
appURL = u
cmdArgs[i] = appURL.String()
}
}
if appURL == nil {
logger.Error("Please provide a valid URL.")
return cmdArgs, appURL, allowRequest, errors.New("invalid url")
}
if allowRequest {
// remove from cmdArgs
cmdArgs[iAllowRequest] = cmdArgs[len(cmdArgs)-1]
cmdArgs = cmdArgs[:len(cmdArgs)-1]
}
return cmdArgs, appURL, allowRequest, nil
}

@ -1,90 +0,0 @@
package access
import (
"fmt"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"strings"
"time"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
"github.com/cloudflare/cloudflared/log"
"github.com/coreos/go-oidc/jose"
"github.com/coreos/go-oidc/oidc"
homedir "github.com/mitchellh/go-homedir"
cli "gopkg.in/urfave/cli.v2"
)
var logger = log.CreateLogger()
// fetchToken will either load a stored token or generate a new one
func fetchToken(c *cli.Context, appURL *url.URL) (string, error) {
if token, err := getTokenIfExists(appURL); token != "" && err == nil {
fmt.Fprintf(os.Stdout, "You have an existing token:\n\n%s\n\n", token)
return token, nil
}
path, err := generateFilePathForTokenURL(appURL)
if err != nil {
return "", err
}
// this weird parameter is the resource name (token) and the key/value
// we want to send to the transfer service. the key is token and the value
// is blank (basically just the id generated in the transfer service)
const resourceName, key, value = "token", "token", ""
token, err := transfer.Run(c, appURL, resourceName, key, value, path, true)
if err != nil {
return "", err
}
fmt.Fprintf(os.Stdout, "Successfully fetched your token:\n\n%s\n\n", string(token))
return string(token), nil
}
// getTokenIfExists will return the token from local storage if it exists
func getTokenIfExists(url *url.URL) (string, error) {
path, err := generateFilePathForTokenURL(url)
if err != nil {
return "", err
}
content, err := ioutil.ReadFile(path)
if err != nil {
return "", err
}
token, err := jose.ParseJWT(string(content))
if err != nil {
return "", err
}
claims, err := token.Claims()
if err != nil {
return "", err
}
ident, err := oidc.IdentityFromClaims(claims)
if err == nil && ident.ExpiresAt.After(time.Now()) {
return token.Encode(), nil
}
return "", err
}
// generateFilePathForTokenURL will return a filepath for given access application url
func generateFilePathForTokenURL(url *url.URL) (string, error) {
configPath, err := homedir.Expand(config.DefaultConfigDirs[0])
if err != nil {
return "", err
}
ok, err := config.FileExists(configPath)
if !ok && err == nil {
// create config directory if doesn't already exist
err = os.Mkdir(configPath, 0700)
}
if err != nil {
return "", err
}
name := strings.Replace(fmt.Sprintf("%s%s-token", url.Hostname(), url.EscapedPath()), "/", "-", -1)
return filepath.Join(configPath, name), nil
}

@ -1,62 +0,0 @@
package config
import (
"os"
"path/filepath"
homedir "github.com/mitchellh/go-homedir"
"gopkg.in/urfave/cli.v2"
"gopkg.in/urfave/cli.v2/altsrc"
)
var (
// File names from which we attempt to read configuration.
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"
// FileExists checks to see if a file exist at the provided path.
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
}
// FindInputSourceContext pulls the input source from the config flag.
func FindInputSourceContext(context *cli.Context) (altsrc.InputSourceContext, error) {
if context.String("config") != "" {
return altsrc.NewYamlSourceFromFile(context.String("config"))
}
return nil, nil
}
// FindDefaultConfigPath returns the first path that contains a config file.
// If none of the combination of DefaultConfigDirs 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 ""
}

@ -1,4 +1,4 @@
package tunnel
package main
import (
"crypto/tls"
@ -15,39 +15,80 @@ import (
"strings"
"time"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tlsconfig"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/validation"
"golang.org/x/crypto/ssh/terminal"
"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 (
developerPortal = "https://developers.cloudflare.com/argo-tunnel"
quickStartUrl = developerPortal + "/quickstart/quickstart/"
serviceUrl = developerPortal + "/reference/service/"
argumentsUrl = developerPortal + "/reference/arguments/"
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"}
)
// returns the first path that contains a cert.pem file. If none of the DefaultConfigDirs
// contains a cert.pem file, return empty string
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 config.DefaultConfigDirs {
originCertPath, _ := homedir.Expand(filepath.Join(defaultConfigDir, config.DefaultCredentialFile))
if ok, _ := config.FileExists(originCertPath); ok {
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)
@ -55,6 +96,24 @@ func generateRandomClientID() string {
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) error {
// Fail if the user provided an old authentication method
if c.IsSet("api-key") || c.IsSet("api-email") || c.IsSet("api-ca-key") {
@ -64,14 +123,12 @@ func handleDeprecatedOptions(c *cli.Context) error {
return nil
}
// 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.")
if !c.IsSet("url") {
return "", errors.New("Please specify an origin URL.")
}
url = c.Args().Get(0)
}
validUrl, err := validation.ValidateUrl(url)
return validUrl, err
@ -108,7 +165,7 @@ func dnsProxyStandAlone(c *cli.Context) bool {
func getOriginCert(c *cli.Context) ([]byte, error) {
if c.String("origincert") == "" {
logger.Warnf("Cannot determine default origin certificate path. No file %s in %v", config.DefaultCredentialFile, config.DefaultConfigDirs)
logger.Warnf("Cannot determine default origin certificate path. No file %s in %v", defaultCredentialFile, defaultConfigDirs)
if isRunningFromTerminal() {
logger.Errorf("You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", argumentsUrl)
return nil, fmt.Errorf("Client didn't specify origincert path when running from terminal")
@ -123,7 +180,7 @@ func getOriginCert(c *cli.Context) ([]byte, error) {
logger.WithError(err).Errorf("Cannot resolve path %s", c.String("origincert"))
return nil, fmt.Errorf("Cannot resolve path %s", c.String("origincert"))
}
ok, err := config.FileExists(originCertPath)
ok, err := fileExists(originCertPath)
if err != nil {
logger.Errorf("Cannot check if origin cert exists at path %s", c.String("origincert"))
return nil, fmt.Errorf("Cannot check if origin cert exists at path %s", c.String("origincert"))
@ -149,7 +206,7 @@ If you don't have a certificate signed by Cloudflare, run the command:
return originCert, nil
}
func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, version string, logger, protoLogger *logrus.Logger) (*origin.TunnelConfig, error) {
func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, logger, protoLogger *logrus.Logger) (*origin.TunnelConfig, error) {
hostname, err := validation.ValidateHostname(c.String("hostname"))
if err != nil {
logger.WithError(err).Error("Invalid hostname")
@ -168,12 +225,12 @@ func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, version st
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID})
originURL, err := validateUrl(c)
url, err := validateUrl(c)
if err != nil {
logger.WithError(err).Error("Error validating origin URL")
return nil, errors.Wrap(err, "Error validating origin URL")
logger.WithError(err).Error("Error validating url")
return nil, errors.Wrap(err, "Error validating url")
}
logger.Infof("Proxying tunnel requests to %s", originURL)
logger.Infof("Proxying tunnel requests to %s", url)
originCert, err := getOriginCert(c)
if err != nil {
@ -205,15 +262,9 @@ func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, version st
httpTransport.TLSClientConfig.ServerName = c.String("origin-server-name")
}
err = validation.ValidateHTTPService(originURL, httpTransport)
if err != nil {
logger.WithError(err).Error("unable to connect to the origin")
return nil, errors.Wrap(err, "unable to connect to the origin")
}
return &origin.TunnelConfig{
EdgeAddrs: c.StringSlice("edge"),
OriginUrl: originURL,
OriginUrl: url,
Hostname: hostname,
OriginCert: originCert,
TlsConfig: tlsconfig.CreateTunnelConfig(c, c.StringSlice("edge")),
@ -223,7 +274,7 @@ func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, version st
MaxHeartbeats: c.Uint64("heartbeat-count"),
ClientID: clientID,
BuildInfo: buildInfo,
ReportedVersion: version,
ReportedVersion: Version,
LBPool: c.String("lb-pool"),
Tags: tags,
HAConnections: c.Int("ha-connections"),
@ -265,7 +316,3 @@ func loadCertPool(c *cli.Context, logger *logrus.Logger) (*x509.CertPool, error)
return originCertPool, nil
}
func isRunningFromTerminal() bool {
return terminal.IsTerminal(int(os.Stdout.Fd()))
}

@ -1,176 +0,0 @@
// Package encrypter is suitable for encrypting messages you would like to securely share between two points.
// Useful for providing end to end encryption (E2EE). It uses Box (NaCl) for encrypting the messages.
// tldr is it uses Elliptic Curves (Curve25519) for the keys, XSalsa20 and Poly1305 for encryption.
// You can read more here https://godoc.org/golang.org/x/crypto/nacl/box.
//
// msg := []byte("super safe message.")
// alice, err := New("alice_priv_key.pem", "alice_pub_key.pem")
// if err != nil {
// log.Fatal(err)
// }
//
// bob, err := New("bob_priv_key.pem", "bob_pub_key.pem")
// if err != nil {
// log.Fatal(err)
// }
// encrypted, err := alice.Encrypt(msg, bob.PublicKey())
// if err != nil {
// log.Fatal(err)
// }
//
// data, err := bob.Decrypt(encrypted, alice.PublicKey())
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(string(data))
package encrypter
import (
"bytes"
"crypto/rand"
"encoding/base64"
"encoding/pem"
"errors"
"io"
"os"
"golang.org/x/crypto/nacl/box"
)
// Encrypter represents a keypair value with auxiliary functions to make
// doing encryption and decryption easier
type Encrypter struct {
privateKey *[32]byte
publicKey *[32]byte
}
// New returns a new encrypter with initialized keypair
func New(privateKey, publicKey string) (*Encrypter, error) {
e := &Encrypter{}
pubKey, key, err := e.fetchOrGenerateKeys(privateKey, publicKey)
if err != nil {
return nil, err
}
e.privateKey, e.publicKey = key, pubKey
return e, nil
}
// PublicKey returns a base64 encoded public key. Useful for transport (like in HTTP requests)
func (e *Encrypter) PublicKey() string {
return base64.URLEncoding.EncodeToString(e.publicKey[:])
}
// Decrypt data that was encrypted using our publicKey. It will use our privateKey and the sender's publicKey to decrypt
// data is an encrypted buffer of data, mostly like from the Encrypt function. Messages contain the nonce data on the front
// of the message.
// senderPublicKey is a base64 encoded version of the sender's public key (most likely from the PublicKey function).
// The return value is the decrypted buffer or an error.
func (e *Encrypter) Decrypt(data []byte, senderPublicKey string) ([]byte, error) {
var decryptNonce [24]byte
copy(decryptNonce[:], data[:24]) // we pull the nonce from the front of the actual message.
pubKey, err := e.decodePublicKey(senderPublicKey)
if err != nil {
return nil, err
}
decrypted, ok := box.Open(nil, data[24:], &decryptNonce, pubKey, e.privateKey)
if !ok {
return nil, errors.New("failed to decrypt message")
}
return decrypted, nil
}
// Encrypt data using our privateKey and the recipient publicKey
// data is a buffer of data that we would like to encrypt. Messages will have the nonce added to front
// as they have to unique for each message shared.
// recipientPublicKey is a base64 encoded version of the sender's public key (most likely from the PublicKey function).
// The return value is the encrypted buffer or an error.
func (e *Encrypter) Encrypt(data []byte, recipientPublicKey string) ([]byte, error) {
var nonce [24]byte
if _, err := io.ReadFull(rand.Reader, nonce[:]); err != nil {
return nil, err
}
pubKey, err := e.decodePublicKey(recipientPublicKey)
if err != nil {
return nil, err
}
// This encrypts msg and adds the nonce to the front of the message, since the nonce has to be
// the same for encrypting and decrypting
return box.Seal(nonce[:], data, &nonce, pubKey, e.privateKey), nil
}
// WriteKeys keys will take the currently initialized keypair and write them to provided filenames
func (e *Encrypter) WriteKeys(privateKey, publicKey string) error {
if err := e.writeKey(e.privateKey[:], "BOX PRIVATE KEY", privateKey); err != nil {
return err
}
return e.writeKey(e.publicKey[:], "PUBLIC KEY", publicKey)
}
// fetchOrGenerateKeys will either load or create a keypair if it doesn't exist
func (e *Encrypter) fetchOrGenerateKeys(privateKey, publicKey string) (*[32]byte, *[32]byte, error) {
key, err := e.fetchKey(privateKey)
if os.IsNotExist(err) {
return box.GenerateKey(rand.Reader)
} else if err != nil {
return nil, nil, err
}
pub, err := e.fetchKey(publicKey)
if os.IsNotExist(err) {
return box.GenerateKey(rand.Reader)
} else if err != nil {
return nil, nil, err
}
return pub, key, nil
}
// writeKey will write a key to disk in DER format (it's a standard pem key)
func (e *Encrypter) writeKey(key []byte, pemType, filename string) error {
data := pem.EncodeToMemory(&pem.Block{
Type: pemType,
Bytes: key,
})
f, err := os.Create(filename)
if err != nil {
return err
}
_, err = f.Write(data)
if err != nil {
return err
}
return nil
}
// fetchKey will load a a DER formatted key from disk
func (e *Encrypter) fetchKey(filename string) (*[32]byte, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
buf := new(bytes.Buffer)
io.Copy(buf, f)
p, _ := pem.Decode(buf.Bytes())
if p == nil {
return nil, errors.New("Failed to decode key")
}
var newKey [32]byte
copy(newKey[:], p.Bytes)
return &newKey, nil
}
// decodePublicKey will base64 decode the provided key to the box representation
func (e *Encrypter) decodePublicKey(key string) (*[32]byte, error) {
pub, err := base64.URLEncoding.DecodeString(key)
if err != nil {
return nil, err
}
var newKey [32]byte
copy(newKey[:], pub)
return &newKey, nil
}

@ -1,4 +1,4 @@
package tunnel
package main
import (
"fmt"
@ -8,6 +8,7 @@ import (
"github.com/cloudflare/cloudflared/hello"
)
func helloWorld(c *cli.Context) error {
address := fmt.Sprintf(":%d", c.Int("port"))
listener, err := hello.CreateTLSListener(address)

@ -7,7 +7,6 @@ import (
"os"
"path/filepath"
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
cli "gopkg.in/urfave/cli.v2"
)
@ -31,13 +30,7 @@ func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
app.Run(os.Args)
}
// The directory and files that are used by the service.
// These are hard-coded in the templates below.
const (
serviceConfigDir = "/etc/cloudflared"
serviceConfigFile = "config.yml"
serviceCredentialFile = "cert.pem"
)
const serviceConfigDir = "/etc/cloudflared"
var systemdTemplates = []ServiceTemplate{
{
@ -181,23 +174,6 @@ func isSystemd() bool {
return false
}
func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string) error {
if err := ensureConfigDirExists(serviceConfigDir); err != nil {
return err
}
srcCredentialPath := filepath.Join(userConfigDir, userCredentialFile)
destCredentialPath := filepath.Join(serviceConfigDir, serviceCredentialFile)
if err := copyCredential(srcCredentialPath, destCredentialPath); err != nil {
return err
}
srcConfigPath := filepath.Join(userConfigDir, userConfigFile)
destConfigPath := filepath.Join(serviceConfigDir, serviceConfigFile)
if err := copyConfig(srcConfigPath, destConfigPath); err != nil {
return err
}
return nil
}
func installLinuxService(c *cli.Context) error {
etPath, err := os.Executable()
if err != nil {
@ -205,12 +181,11 @@ func installLinuxService(c *cli.Context) error {
}
templateArgs := ServiceTemplateArgs{Path: etPath}
userConfigDir := filepath.Dir(c.String("config"))
userConfigFile := filepath.Base(c.String("config"))
userCredentialFile := config.DefaultCredentialFile
if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile); err != nil {
defaultConfigDir := filepath.Dir(c.String("config"))
defaultConfigFile := filepath.Base(c.String("config"))
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, serviceCredentialFile, serviceConfigFile)
serviceConfigDir, defaultCredentialFile, defaultConfigFiles[0])
return err
}

@ -1,10 +1,11 @@
package tunnel
package main
import (
"fmt"
"os"
"github.com/cloudflare/cloudflared/log"
"github.com/rifflock/lfshook"
"github.com/sirupsen/logrus"
"gopkg.in/urfave/cli.v2"

@ -0,0 +1,194 @@
package main
import (
"crypto/rand"
"encoding/base32"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"runtime"
"syscall"
"time"
homedir "github.com/mitchellh/go-homedir"
cli "gopkg.in/urfave/cli.v2"
)
const baseLoginURL = "https://dash.cloudflare.com/warp"
const baseCertStoreURL = "https://login.cloudflarewarp.com"
const clientTimeout = time.Minute * 20
func login(c *cli.Context) error {
configPath, err := homedir.Expand(defaultConfigDirs[0])
if err != nil {
return err
}
ok, err := fileExists(configPath)
if !ok && err == nil {
// create config directory if doesn't already exist
err = os.Mkdir(configPath, 0700)
}
if err != nil {
return err
}
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.
If this is intentional, please move or delete that file then run this command again.
`, path)
return nil
}
if err != nil && err.(*os.PathError).Err != syscall.ENOENT {
return err
}
// for local debugging
baseURL := baseCertStoreURL
if c.IsSet("url") {
baseURL = c.String("url")
}
// Generate a random post URL
certURL := baseURL + generateRandomPath()
loginURL, err := url.Parse(baseLoginURL)
if err != nil {
// shouldn't happen, URL is hardcoded
return err
}
loginURL.RawQuery = "callback=" + url.QueryEscape(certURL)
err = open(loginURL.String())
if err != nil {
fmt.Fprintf(os.Stderr, `Please open the following URL and log in with your Cloudflare account:
%s
Leave cloudflared running to install the certificate automatically.
`, loginURL.String())
} else {
fmt.Fprintf(os.Stderr, `A browser window should have opened at the following URL:
%s
If the browser failed to open, open it yourself and visit the URL above.
`, loginURL.String())
}
if download(certURL, path) {
fmt.Fprintf(os.Stderr, `You have successfully logged in.
If you wish to copy your credentials to a server, they have been saved to:
%s
`, path)
} else {
fmt.Fprintf(os.Stderr, `Failed to write the certificate due to the following error:
%v
Your browser will download the certificate instead. You will have to manually
copy it to the following path:
%s
`, err, path)
}
return nil
}
// generateRandomPath generates a random URL to associate with the certificate.
func generateRandomPath() string {
randomBytes := make([]byte, 40)
_, err := rand.Read(randomBytes)
if err != nil {
panic(err)
}
return "/" + base32.StdEncoding.EncodeToString(randomBytes)
}
// open opens the specified URL in the default browser of the user.
func open(url string) error {
var cmd string
var args []string
switch runtime.GOOS {
case "windows":
cmd = "cmd"
args = []string{"/c", "start"}
case "darwin":
cmd = "open"
default: // "linux", "freebsd", "openbsd", "netbsd"
cmd = "xdg-open"
}
args = append(args, url)
return exec.Command(cmd, args...).Start()
}
func download(certURL, filePath string) bool {
client := &http.Client{Timeout: clientTimeout}
// attempt a (long-running) certificate get
for i := 0; i < 20; i++ {
ok, err := tryDownload(client, certURL, filePath)
if ok {
putSuccess(client, certURL)
return true
}
if err != nil {
logger.WithError(err).Error("Error fetching certificate")
return false
}
}
return false
}
func tryDownload(client *http.Client, certURL, filePath string) (ok bool, err error) {
resp, err := client.Get(certURL)
if err != nil {
return false, err
}
defer resp.Body.Close()
if resp.StatusCode == 404 {
return false, nil
}
if resp.StatusCode != 200 {
return false, fmt.Errorf("Unexpected HTTP error code %d", resp.StatusCode)
}
if resp.Header.Get("Content-Type") != "application/x-pem-file" {
return false, fmt.Errorf("Unexpected content type %s", resp.Header.Get("Content-Type"))
}
// write response
file, err := os.Create(filePath)
if err != nil {
return false, err
}
defer file.Close()
written, err := io.Copy(file, resp.Body)
switch {
case err != nil:
return false, err
case resp.ContentLength != written && resp.ContentLength != -1:
return false, fmt.Errorf("Short read (%d bytes) from server while writing certificate", written)
default:
return true, nil
}
}
func putSuccess(client *http.Client, certURL string) {
// indicate success to the relay server
req, err := http.NewRequest("PUT", certURL+"/ok", nil)
if err != nil {
logger.WithError(err).Error("HTTP request error")
return
}
resp, err := client.Do(req)
if err != nil {
logger.WithError(err).Error("HTTP error")
return
}
resp.Body.Close()
if resp.StatusCode != 200 {
logger.Errorf("Unexpected HTTP error code %d", resp.StatusCode)
}
}

@ -2,37 +2,48 @@ package main
import (
"fmt"
"io/ioutil"
"os"
"runtime/trace"
"sync"
"syscall"
"time"
"golang.org/x/crypto/ssh/terminal"
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
"github.com/cloudflare/cloudflared/log"
"github.com/cloudflare/cloudflared/cmd/sqlgateway"
"github.com/cloudflare/cloudflared/hello"
"github.com/cloudflare/cloudflared/metrics"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tunneldns"
"golang.org/x/crypto/ssh/terminal"
"github.com/getsentry/raven-go"
"github.com/mitchellh/go-homedir"
"gopkg.in/urfave/cli.v2"
"gopkg.in/urfave/cli.v2/altsrc"
"github.com/coreos/go-systemd/daemon"
"github.com/facebookgo/grace/gracenet"
"github.com/pkg/errors"
)
const (
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 (
Version = "DEV"
BuildTime = "unknown"
logger = log.CreateLogger()
)
func main() {
metrics.RegisterBuildInfo(BuildTime, Version)
raven.SetDSN(sentryDSN)
raven.SetRelease(Version)
// Force shutdown channel used by the app. When closed, app must terminate.
@ -44,63 +55,588 @@ func main() {
app := &cli.App{}
app.Name = "cloudflared"
app.Usage = "Cloudflare's command-line tool and agent"
app.ArgsUsage = "origin-url"
app.Copyright = fmt.Sprintf(`(c) %d Cloudflare Inc.
Use is subject to the license agreement at %s`, time.Now().Year(), licenseUrl)
app.Usage = "Cloudflare reverse tunnelling proxy agent"
app.ArgsUsage = "origin-url"
app.Version = fmt.Sprintf("%s (built %s)", Version, BuildTime)
app.Description = `cloudflared connects your machine or user identity to Cloudflare's global network.
You can use it to authenticate a session to reach an API behind Access, route web traffic to this machine,
and configure access control.`
app.Flags = flags()
app.Action = action(Version, shutdownC, graceShutdownC)
app.Before = tunnel.Before
app.Commands = commands()
tunnel.Init(Version, shutdownC, graceShutdownC) // we need this to support the tunnel sub command...
runApp(app, shutdownC, graceShutdownC)
}
app.Description = `A reverse tunnel proxy agent that connects to Cloudflare's infrastructure.
Upon connecting, you are assigned a unique subdomain on cftunnel.com.
You need to specify a hostname on a zone you control.
A DNS record will be created to CNAME your hostname to the unique subdomain on cftunnel.com.
func commands() []*cli.Command {
cmds := []*cli.Command{
Requests made to Cloudflare's servers for your hostname will be proxied
through the tunnel to your local webserver.`
app.Flags = []cli.Flag{
&cli.StringFlag{
Name: "config",
Usage: "Specifies a config file in YAML format.",
Value: findDefaultConfigPath(),
},
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: "autoupdate-freq",
Usage: "Autoupdate frequency. Default is 24h.",
Value: time.Hour * 24,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "no-autoupdate",
Usage: "Disable periodic check for updates, restarting the server with the new version.",
Value: false,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "is-autoupdated",
Usage: "Signal the new process that Argo Tunnel client has been autoupdated",
Value: false,
Hidden: true,
}),
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
Name: "edge",
Usage: "Address of the Cloudflare tunnel server.",
EnvVars: []string{"TUNNEL_EDGE"},
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "cacert",
Usage: "Certificate Authority authenticating the Cloudflare tunnel connection.",
EnvVars: []string{"TUNNEL_CACERT"},
Hidden: true,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "no-tls-verify",
Usage: "Disables TLS verification of the certificate presented by your origin. Will allow any certificate from the origin to be accepted. Note: The connection from your machine to Cloudflare's Edge is still encrypted.",
EnvVars: []string{"NO_TLS_VERIFY"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "origincert",
Usage: "Path to the certificate generated for your origin when you run cloudflared login.",
EnvVars: []string{"TUNNEL_ORIGIN_CERT"},
Value: findDefaultOriginCertPath(),
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "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",
Usage: "Connect to the local webserver at `URL`.",
EnvVars: []string{"TUNNEL_URL"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "hostname",
Usage: "Set a hostname on a Cloudflare zone to route traffic through this tunnel.",
EnvVars: []string{"TUNNEL_HOSTNAME"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "origin-server-name",
Usage: "Hostname on the origin server certificate.",
EnvVars: []string{"TUNNEL_ORIGIN_SERVER_NAME"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "id",
Usage: "A unique identifier used to tie connections to this tunnel instance.",
EnvVars: []string{"TUNNEL_ID"},
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "lb-pool",
Usage: "The name of a (new/existing) load balancing pool to add this origin to.",
EnvVars: []string{"TUNNEL_LB_POOL"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "api-key",
Usage: "This parameter has been deprecated since version 2017.10.1.",
EnvVars: []string{"TUNNEL_API_KEY"},
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "api-email",
Usage: "This parameter has been deprecated since version 2017.10.1.",
EnvVars: []string{"TUNNEL_API_EMAIL"},
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "api-ca-key",
Usage: "This parameter has been deprecated since version 2017.10.1.",
EnvVars: []string{"TUNNEL_API_CA_KEY"},
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "metrics",
Value: "localhost:",
Usage: "Listen address for metrics reporting.",
EnvVars: []string{"TUNNEL_METRICS"},
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: "metrics-update-freq",
Usage: "Frequency to update tunnel metrics",
Value: time.Second * 5,
EnvVars: []string{"TUNNEL_METRICS_UPDATE_FREQ"},
}),
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
Name: "tag",
Usage: "Custom tags used to identify this tunnel, in format `KEY=VALUE`. Multiple tags may be specified",
EnvVars: []string{"TUNNEL_TAG"},
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: "heartbeat-interval",
Usage: "Minimum idle time before sending a heartbeat.",
Value: time.Second * 5,
Hidden: true,
}),
altsrc.NewUint64Flag(&cli.Uint64Flag{
Name: "heartbeat-count",
Usage: "Minimum number of unacked heartbeats to send before closing the connection.",
Value: 5,
Hidden: true,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "loglevel",
Value: "info",
Usage: "Application logging level {panic, fatal, error, warn, info, debug}",
EnvVars: []string{"TUNNEL_LOGLEVEL"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "proto-loglevel",
Value: "warn",
Usage: "Protocol logging level {panic, fatal, error, warn, info, debug}",
EnvVars: []string{"TUNNEL_PROTO_LOGLEVEL"},
}),
altsrc.NewUintFlag(&cli.UintFlag{
Name: "retries",
Value: 5,
Usage: "Maximum number of retries for connection/protocol errors.",
EnvVars: []string{"TUNNEL_RETRIES"},
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "hello-world",
Value: false,
Usage: "Run Hello World Server",
EnvVars: []string{"TUNNEL_HELLO_WORLD"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "pidfile",
Usage: "Write the application's PID to this file after first successful connection.",
EnvVars: []string{"TUNNEL_PIDFILE"},
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "logfile",
Usage: "Save application log to this file for reporting issues.",
EnvVars: []string{"TUNNEL_LOGFILE"},
}),
altsrc.NewIntFlag(&cli.IntFlag{
Name: "ha-connections",
Value: 4,
Hidden: true,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: "proxy-connect-timeout",
Usage: "HTTP proxy timeout for establishing a new connection",
Value: time.Second * 30,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: "proxy-tls-timeout",
Usage: "HTTP proxy timeout for completing a TLS handshake",
Value: time.Second * 10,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: "proxy-tcp-keepalive",
Usage: "HTTP proxy TCP keepalive duration",
Value: time.Second * 30,
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "proxy-no-happy-eyeballs",
Usage: "HTTP proxy should disable \"happy eyeballs\" for IPv4/v6 fallback",
}),
altsrc.NewIntFlag(&cli.IntFlag{
Name: "proxy-keepalive-connections",
Usage: "HTTP proxy maximum keepalive connection pool size",
Value: 100,
}),
altsrc.NewDurationFlag(&cli.DurationFlag{
Name: "proxy-keepalive-timeout",
Usage: "HTTP proxy timeout for closing an idle connection",
Value: time.Second * 90,