Compare commits

..

24 Commits

Author SHA1 Message Date
Adam Chalmers dbe3516204 Release 2020.6.6 2020-06-30 12:55:54 -05:00
Dalton 6fc9f1b405 AUTH-2849 all log output to stderr 2020-06-30 16:54:31 +00:00
Michael Borkenstein 370c17e48c AUTH-2718: Add target for publishing deb to pkg.cloudflare repo 2020-06-30 14:46:24 +00:00
Michael Borkenstein 92da73aa9d AUTH-2652: Update cloudflare repo 2020-06-26 14:24:20 +00:00
Dalton 0c65daaa7d AUTH-2712 mac package build script and better config file handling when started as a service 2020-06-25 16:44:57 -05:00
Michael Borkenstein b46acd7f63 AUTH-2685: Adds script to create release 2020-06-25 18:39:37 +00:00
Michael Borkenstein e3a9aa4296 AUTH-2652: Adds .docker-images to push images to docker hub 2020-06-23 11:35:48 -05:00
cthuang 3886021ba5 TUN-3107: UnregisterConnection shouldn't wrap nil error as RPC error 2020-06-18 21:17:43 +08:00
Adam Chalmers 4d3ebaf984 TUN-3106: Pass NamedTunnel config to StartServer 2020-06-17 23:20:37 +00:00
Dalton 9131e842a5 Release 2020.6.5 2020-06-17 14:40:53 -05:00
Dalton 4f9cfa6542 TUN-3100 make updater report the right text 2020-06-17 17:33:19 +00:00
Adam Chalmers a1a8645294 TUN-3066: Command line action for tunnel run 2020-06-17 17:25:23 +00:00
Adam Chalmers b95b289a8c TUN-3101: Tunnel list command should only show non-deleted, by default 2020-06-16 17:55:33 -05:00
Dalton 425554077f AUTH-2815 flag check was wrong. stupid oversight 2020-06-16 16:19:38 -05:00
Dalton 7b2f286210 fix for a flaky test 2020-06-16 21:18:55 +00:00
Robert McNeil 0f893fab47 DEVTOOLS-7321: Don't skip macOS builds based on tag 2020-06-16 20:36:50 +00:00
Dalton 12c615e2e6 Release 2020.6.4 2020-06-16 14:13:06 -05:00
Dalton 6e5ccd7c85 AUTH-2815 add the log file to support the config.yaml file
added small delay to handle the possiblity of the server not being started yet
2020-06-16 17:48:12 +00:00
Adam Chalmers 3ec500bdbb TUN-3084: Generate and store tunnel_secret value during tunnel creation 2020-06-16 11:45:27 -05:00
Igor Postelnik 8f75feac94 TUN-3085: Pass connection authentication information using TunnelAuth struct 2020-06-16 16:35:46 +00:00
Igor Postelnik 448a7798f7 TUN-3015: Add a new cap'n'proto RPC interface for connection registration as well as matching client and server implementations. The old interface extends the new one for backward compatibility. 2020-06-16 16:35:46 +00:00
cthuang dc3a228d51 Release 2020.6.3 2020-06-16 08:45:03 +08:00
Dalton 554c97a8cb AUTH-2813 adds back a single file support a cloudflared log file 2020-06-16 00:43:09 +00:00
Robert McNeil fd1941dfbe DEVTOOLS-7321: Add openssh-client pkg for missing ssh-keyscan 2020-06-15 17:08:10 -07:00
37 changed files with 2641 additions and 225 deletions
+10
View File
@@ -0,0 +1,10 @@
images:
- name: cloudflared
dockerfile: Dockerfile
context: .
verions:
- latest
registries:
- name: docker.io/cloudflare
user: env:DOCKER_USER
password: env:DOCKER_PASSWORD
+1
View File
@@ -9,6 +9,7 @@ guide/public
\#*\#
cscope.*
cloudflared
cloudflared.pkg
cloudflared.exe
!cmd/cloudflared/
.DS_Store
+7
View File
@@ -0,0 +1,7 @@
#!/bin/bash
# uninstall first in case this is an upgrade
/usr/local/bin/cloudflared service uninstall
# install the new service using launchctl
/usr/local/bin/cloudflared service install
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
/usr/local/bin/cloudflared service uninstall
rm /usr/local/bin/cloudflared
pkgutil --forget com.cloudflare.cloudflared
-6
View File
@@ -2,12 +2,6 @@
set -euo pipefail
if ! git describe --tags --exact-match 2>/dev/null ; then
echo "Skipping public release for an untagged commit."
echo "##teamcity[buildStatus status='SUCCESS' text='Skipped due to lack of tag']"
exit 0
fi
if [[ "$(uname)" != "Darwin" ]] ; then
echo "This should be run on macOS"
exit 1
+17
View File
@@ -40,6 +40,12 @@ else
$(error This system's OS $(LOCAL_OS) isn't supported)
endif
ifeq ($(TARGET_OS), windows)
EXECUTABLE_PATH=./cloudflared.exe
else
EXECUTABLE_PATH=./cloudflared
endif
.PHONY: all
all: cloudflared test
@@ -63,6 +69,13 @@ test: vet
test-ssh-server:
docker-compose -f ssh_server_tests/docker-compose.yml up
.PHONY: publish-deb
publish-deb: cloudflared-deb
for HOST in $(CF_PKG_HOSTS); do \
ssh-keyscan -t rsa $$HOST >> ~/.ssh/known_hosts; \
scp -4 cloudflared_$(VERSION)_amd64.deb cfsync@$$HOST:/state/cf-pkg/staging/apt/$(FLAVOR)/cloudflared/; \
done
.PHONY: cloudflared-deb
cloudflared-deb: cloudflared
mkdir -p $(PACKAGE_DIR)
@@ -88,6 +101,10 @@ homebrew-release: homebrew-upload
release: bin/equinox
bin/equinox release $(EQUINOX_FLAGS) -- $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/cloudflared
.PHONY: github-release
github-release: cloudflared
python3 github_release.py --path $(EXECUTABLE_PATH) --release-version $(VERSION)
bin/equinox:
mkdir -p bin
curl -s https://bin.equinox.io/c/75JtLRTsJ3n/release-tool-beta-$(EQUINOX_PLATFORM).tgz | tar xz -C bin/
+28
View File
@@ -1,3 +1,31 @@
2020.6.6
- 2020-06-23 AUTH-2685: Adds script to create release
- 2020-06-25 AUTH-2652: Update cloudflare repo
- 2020-06-26 AUTH-2718: Add target for publishing deb to pkg.cloudflare repo
- 2020-06-26 AUTH-2849 all log output to stderr
- 2020-06-17 TUN-3106: Pass NamedTunnel config to StartServer
- 2020-06-18 TUN-3107: UnregisterConnection shouldn't wrap nil error as RPC error
- 2020-06-17 AUTH-2652: Adds .docker-images to push images to docker hub
- 2020-06-18 AUTH-2712 mac package build script and better config file handling when started as a service
2020.6.5
- 2020-06-16 DEVTOOLS-7321: Don't skip macOS builds based on tag
- 2020-06-16 fix for a flaky test
- 2020-06-16 AUTH-2815 flag check was wrong. stupid oversight
- 2020-06-16 TUN-3101: Tunnel list command should only show non-deleted, by default
- 2020-06-16 TUN-3066: Command line action for tunnel run
- 2020-06-16 TUN-3100 make updater report the right text
2020.6.4
- 2020-06-11 TUN-3085: Pass connection authentication information using TunnelAuth struct
- 2020-06-15 TUN-3084: Generate and store tunnel_secret value during tunnel creation
- 2020-06-16 AUTH-2815 add the log file to support the config.yaml file
- 2020-06-02 TUN-3015: Add a new cap'n'proto RPC interface for connection registration as well as matching client and server implementations. The old interface extends the new one for backward compatibility.
2020.6.3
- 2020-06-15 DEVTOOLS-7321: Add openssh-client pkg for missing ssh-keyscan
- 2020-06-15 AUTH-2813 adds back a single file support a cloudflared log file
2020.6.2
- 2020-06-11 AUTH-2648 updated usage text
- 2020-06-11 AUTH-2763 don't redirect from curl command
+76
View File
@@ -22,6 +22,17 @@ stretch: &stretch
- export GOOS=linux
- export GOARCH=amd64
- make cloudflared-deb
publish-deb:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- fakeroot
- rubygem-fpm
post-cache:
- export GOOS=linux
- export GOARCH=amd64
- make publish-deb
release-linux-amd64:
build_dir: *build_dir
builddeps:
@@ -31,6 +42,18 @@ stretch: &stretch
- export GOOS=linux
- export GOARCH=amd64
- make release
github-release-linux-amd64:
build_dir: *build_dir
builddeps:
- *pinned_go
- build-essential
- python3-setuptools
- python3-pip
post-cache:
- pip3 install pygithub
- export GOOS=linux
- export GOARCH=amd64
- make github-release
release-linux-armv6:
build_dir: *build_dir
builddeps:
@@ -42,6 +65,20 @@ stretch: &stretch
- export GOARCH=arm
- export CC=arm-linux-gnueabihf-gcc
- make release
github-release-linux-armv6:
build_dir: *build_dir
builddeps:
- *pinned_go
- crossbuild-essential-armhf
- gcc-arm-linux-gnueabihf
- python3-setuptools
- python3-pip
post-cache:
- pip3 install pygithub
- export GOOS=linux
- export GOARCH=arm
- export CC=arm-linux-gnueabihf-gcc
- make github-release
release-linux-386:
build_dir: *build_dir
builddeps:
@@ -51,6 +88,18 @@ stretch: &stretch
- export GOOS=linux
- export GOARCH=386
- make release
github-release-linux-386:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-multilib
- python3-setuptools
- python3-pip
post-cache:
- pip3 install pygithub
- export GOOS=linux
- export GOARCH=386
- make github-release
release-windows-amd64:
build_dir: *build_dir
builddeps:
@@ -61,6 +110,19 @@ stretch: &stretch
- export GOARCH=amd64
- export CC=x86_64-w64-mingw32-gcc
- make release
github-release-windows-amd64:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-mingw-w64
- python3-setuptools
- python3-pip
post-cache:
- pip3 install pygithub
- export GOOS=windows
- export GOARCH=amd64
- export CC=x86_64-w64-mingw32-gcc
- make github-release
release-windows-386:
build_dir: *build_dir
builddeps:
@@ -71,6 +133,19 @@ stretch: &stretch
- export GOARCH=386
- export CC=i686-w64-mingw32-gcc-win32
- make release
github-release-windows-386:
build_dir: *build_dir
builddeps:
- *pinned_go
- gcc-mingw-w64
- python3-setuptools
- python3-pip
post-cache:
- pip3 install pygithub
- export GOOS=windows
- export GOARCH=386
- export CC=i686-w64-mingw32-gcc-win32
- make github-release
test:
build_dir: *build_dir
builddeps:
@@ -85,6 +160,7 @@ stretch: &stretch
- make test
update-homebrew:
builddeps:
- openssh-client
- s3cmd
post-cache:
- .teamcity/update-homebrew.sh
+76 -4
View File
@@ -4,6 +4,7 @@ import (
"errors"
"os"
"path/filepath"
"runtime"
"github.com/cloudflare/cloudflared/validation"
homedir "github.com/mitchellh/go-homedir"
@@ -13,16 +14,54 @@ import (
)
var (
// File names from which we attempt to read configuration.
// DefaultConfigFiles is the file names from which we attempt to read configuration.
DefaultConfigFiles = []string{"config.yml", "config.yaml"}
// DefaultUnixConfigLocation is the primary location to find a config file
DefaultUnixConfigLocation = "/usr/local/etc/cloudflared"
// DefaultUnixLogLocation is the primary location to find log files
DefaultUnixLogLocation = "/var/log/cloudflared"
// 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"}
DefaultConfigDirs = []string{"~/.cloudflared", "~/.cloudflare-warp", "~/cloudflare-warp", "/etc/cloudflared", DefaultUnixConfigLocation}
)
const DefaultCredentialFile = "cert.pem"
// DefaultConfigDirectory returns the default directory of the config file
func DefaultConfigDirectory() string {
if runtime.GOOS == "windows" {
path := os.Getenv("CFDPATH")
if path == "" {
path = filepath.Join(os.Getenv("ProgramFiles(x86)"), "cloudflared")
if _, err := os.Stat(path); os.IsNotExist(err) { //doesn't exist, so return an empty failure string
return ""
}
}
return path
}
return DefaultUnixConfigLocation
}
// DefaultLogDirectory returns the default directory for log files
func DefaultLogDirectory() string {
if runtime.GOOS == "windows" {
return DefaultConfigDirectory()
}
return DefaultUnixLogLocation
}
// DefaultConfigPath returns the default location of a config file
func DefaultConfigPath() string {
dir := DefaultConfigDirectory()
if dir == "" {
return DefaultConfigFiles[0]
}
return filepath.Join(dir, DefaultConfigFiles[0])
}
// FileExists checks to see if a file exist at the provided path.
func FileExists(path string) (bool, error) {
f, err := os.Open(path)
@@ -64,10 +103,43 @@ func FindDefaultConfigPath() string {
return ""
}
// FindOrCreateConfigPath returns the first path that contains a config file
// or creates one in the primary default path if it doesn't exist
func FindOrCreateConfigPath() string {
path := FindDefaultConfigPath()
if path == "" {
// create the default directory if it doesn't exist
path = DefaultConfigPath()
if err := os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil {
return ""
}
// write a new config file out
file, err := os.Create(path)
if err != nil {
return ""
}
defer file.Close()
logDir := DefaultLogDirectory()
os.MkdirAll(logDir, os.ModePerm) //try and create it. Doesn't matter if it succeed or not, only byproduct will be no logs
c := Root{
LogDirectory: logDir,
}
if err := yaml.NewEncoder(file).Encode(&c); err != nil {
return ""
}
}
return path
}
// FindLogSettings gets the log directory and level from the config file
func FindLogSettings() (string, string) {
configPath := FindDefaultConfigPath()
defaultDirectory := filepath.Dir(configPath)
configPath := FindOrCreateConfigPath()
defaultDirectory := DefaultLogDirectory()
defaultLevel := "info"
file, err := os.Open(configPath)
-3
View File
@@ -49,9 +49,6 @@ func TestConfigChanged(t *testing.T) {
os.Remove(filePath)
}()
c := &Root{
OrgKey: "abcd",
ConfigType: "mytype",
CheckinInterval: 1,
Forwarders: []Forwarder{
{
URL: "test.daltoniam.com",
+5 -8
View File
@@ -34,14 +34,11 @@ type DNSResolver struct {
// Root is the base options to configure the service
type Root struct {
OrgKey string `json:"org_key" yaml:"orgKey"`
ConfigType string `json:"type"`
LogDirectory string `json:"log_directory" yaml:"logDirectory,omitempty"`
LogLevel string `json:"log_level" yaml:"logLevel"`
CheckinInterval int `json:"checkin_interval" yaml:"checkinInterval"`
Forwarders []Forwarder `json:"forwarders,omitempty"`
Tunnels []Tunnel `json:"tunnels,omitempty"`
Resolver DNSResolver `json:"resolver,omitempty"`
LogDirectory string `json:"log_directory" yaml:"logDirectory,omitempty"`
LogLevel string `json:"log_level" yaml:"logLevel,omitempty"`
Forwarders []Forwarder `json:"forwarders,omitempty" yaml:"forwarders,omitempty"`
Tunnels []Tunnel `json:"tunnels,omitempty" yaml:"tunnels,omitempty"`
Resolver DNSResolver `json:"resolver,omitempty" yaml:"resolver,omitempty"`
}
// Hash returns the computed values to see if the forwarder values change
+2 -2
View File
@@ -129,7 +129,7 @@ func action(version string, shutdownC, graceShutdownC chan struct{}) cli.ActionF
tags := make(map[string]string)
tags["hostname"] = c.String("hostname")
raven.SetTagsContext(tags)
raven.CapturePanic(func() { err = tunnel.StartServer(c, version, shutdownC, graceShutdownC) }, nil)
raven.CapturePanic(func() { err = tunnel.StartServer(c, version, shutdownC, graceShutdownC, nil) }, nil)
exitCode := 0
if err != nil {
handleError(err)
@@ -182,7 +182,7 @@ func handleServiceMode(shutdownC chan struct{}) error {
return err
}
configPath := config.FindDefaultConfigPath()
configPath := config.FindOrCreateConfigPath()
configManager, err := config.NewFileManager(f, configPath, logger)
if err != nil {
logger.Errorf("Cannot setup config file for monitoring: %s", err)
+20 -9
View File
@@ -172,6 +172,7 @@ func Commands() []*cli.Command {
subcommands = append(subcommands, buildCreateCommand())
subcommands = append(subcommands, buildListCommand())
subcommands = append(subcommands, buildDeleteCommand())
subcommands = append(subcommands, buildRunCommand())
cmds = append(cmds, &cli.Command{
Name: "tunnel",
@@ -206,7 +207,7 @@ func Commands() []*cli.Command {
}
func tunnel(c *cli.Context) error {
return StartServer(c, version, shutdownC, graceShutdownC)
return StartServer(c, version, shutdownC, graceShutdownC, nil)
}
func Init(v string, s, g chan struct{}) {
@@ -215,7 +216,12 @@ func Init(v string, s, g chan struct{}) {
func createLogger(c *cli.Context, isTransport bool) (logger.Service, error) {
loggerOpts := []logger.Option{}
logPath := c.String(logDirectoryFlag)
logPath := c.String("logfile")
if logPath == "" {
logPath = c.String(logDirectoryFlag)
}
if logPath != "" {
loggerOpts = append(loggerOpts, logger.DefaultFile(logPath))
}
@@ -232,7 +238,7 @@ func createLogger(c *cli.Context, isTransport bool) (logger.Service, error) {
return logger.New(loggerOpts...)
}
func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan struct{}) error {
func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan struct{}, namedTunnel *origin.NamedTunnelConfig) error {
logger, err := createLogger(c, false)
if err != nil {
return cliutil.PrintLoggerSetupError("error setting up logger", err)
@@ -469,7 +475,7 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
wg.Add(1)
go func() {
defer wg.Done()
errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID, reconnectCh)
errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID, reconnectCh, namedTunnel)
}()
return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, c.Duration("grace-period"), logger)
@@ -833,12 +839,17 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
Hidden: shouldHide,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: logDirectoryFlag,
Aliases: []string{"logfile"}, // This flag used to be called logfile when it was a single file
Usage: "Save application log to this directory for reporting issues.",
Name: "logfile",
Usage: "Save application log to this file for reporting issues.",
EnvVars: []string{"TUNNEL_LOGFILE"},
Hidden: shouldHide,
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: logDirectoryFlag,
Usage: "Save application log to this directory for reporting issues.",
EnvVars: []string{"TUNNEL_LOGDIRECTORY"},
Hidden: shouldHide,
}),
altsrc.NewIntFlag(&cli.IntFlag{
Name: "ha-connections",
Value: 4,
@@ -1082,8 +1093,8 @@ func stdinControl(reconnectCh chan origin.ReconnectSignal, logger logger.Service
logger.Infof("Unknown command: %s", command)
fallthrough
case "help":
logger.Info(`Supported command:
reconnect [delay]
logger.Info(`Supported command:
reconnect [delay]
- restarts one randomly chosen connection with optional delay before reconnect`)
}
}
+153 -15
View File
@@ -1,9 +1,12 @@
package tunnel
import (
"crypto/rand"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"time"
@@ -15,15 +18,30 @@ import (
"github.com/cloudflare/cloudflared/certutil"
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/origin"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/tunnelstore"
)
var (
showDeletedFlag = &cli.BoolFlag{
Name: "show-deleted",
Aliases: []string{"d"},
Usage: "Include deleted tunnels in the list",
}
outputFormatFlag = &cli.StringFlag{
Name: "output",
Aliases: []string{"o"},
Usage: "Render output using given `FORMAT`. Valid options are 'json' or 'yaml'",
}
forceFlag = &cli.StringFlag{
Name: "force",
Aliases: []string{"f"},
Usage: "By default, if a tunnel is currently being run from a cloudflared, you can't " +
"simultaneously rerun it again from a second cloudflared. The --force flag lets you " +
"overwrite the previous tunnel. If you want to use a single hostname with multiple " +
"tunnels, you can do so with Cloudflare's Load Balancer product.",
}
)
const hideSubcommands = true
@@ -39,6 +57,13 @@ func buildCreateCommand() *cli.Command {
}
}
// generateTunnelSecret as an array of 32 bytes using secure random number generator
func generateTunnelSecret() ([]byte, error) {
randomBytes := make([]byte, 32)
_, err := rand.Read(randomBytes)
return randomBytes, err
}
func createTunnel(c *cli.Context) error {
if c.NArg() != 1 {
return cliutil.UsageError(`"cloudflared tunnel create" requires exactly 1 argument, the name of tunnel to create.`)
@@ -50,16 +75,40 @@ func createTunnel(c *cli.Context) error {
return errors.Wrap(err, "error setting up logger")
}
client, err := newTunnelstoreClient(c, logger)
tunnelSecret, err := generateTunnelSecret()
if err != nil {
return err
}
tunnel, err := client.CreateTunnel(name)
originCertPath, err := findOriginCert(c, logger)
if err != nil {
return errors.Wrap(err, "Error locating origin cert")
}
cert, err := getOriginCertFromContext(originCertPath, logger)
if err != nil {
return err
}
client := newTunnelstoreClient(c, cert, logger)
tunnel, err := client.CreateTunnel(name, tunnelSecret)
if err != nil {
return errors.Wrap(err, "Error creating a new tunnel")
}
if writeFileErr := writeTunnelCredentials(tunnel.ID, cert.AccountID, originCertPath, tunnelSecret, logger); err != nil {
var errorLines []string
errorLines = append(errorLines, fmt.Sprintf("Your tunnel '%v' was created with ID %v. However, cloudflared couldn't write to the tunnel credentials file at %v.json.", tunnel.Name, tunnel.ID, tunnel.ID))
errorLines = append(errorLines, fmt.Sprintf("The file-writing error is: %v", writeFileErr))
if deleteErr := client.DeleteTunnel(tunnel.ID); deleteErr != nil {
errorLines = append(errorLines, fmt.Sprintf("Cloudflared tried to delete the tunnel for you, but encountered an error. You should use `cloudflared tunnel delete %v` to delete the tunnel yourself, because the tunnel can't be run without the tunnelfile.", tunnel.ID))
errorLines = append(errorLines, fmt.Sprintf("The delete tunnel error is: %v", deleteErr))
} else {
errorLines = append(errorLines, fmt.Sprintf("The tunnel was deleted, because the tunnel can't be run without the tunnelfile"))
}
errorMsg := strings.Join(errorLines, "\n")
return errors.New(errorMsg)
}
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
return renderOutput(outputFormat, &tunnel)
}
@@ -68,6 +117,42 @@ func createTunnel(c *cli.Context) error {
return nil
}
func tunnelFilePath(tunnelID, originCertPath string) (string, error) {
fileName := fmt.Sprintf("%v.json", tunnelID)
return filepath.Clean(fmt.Sprintf("%v/../%v", originCertPath, fileName)), nil
}
func writeTunnelCredentials(tunnelID, accountID, originCertPath string, tunnelSecret []byte, logger logger.Service) error {
filePath, err := tunnelFilePath(tunnelID, originCertPath)
if err != nil {
return err
}
body, err := json.Marshal(pogs.TunnelAuth{
AccountTag: accountID,
TunnelSecret: tunnelSecret,
})
if err != nil {
return errors.Wrap(err, "Unable to marshal tunnel credentials to JSON")
}
logger.Infof("Writing tunnel credentials to %v. cloudflared chose this file based on where your origin certificate was found.", filePath)
logger.Infof("Keep this file secret. To revoke these credentials, delete the tunnel.")
return ioutil.WriteFile(filePath, body, 400)
}
func readTunnelCredentials(tunnelID, originCertPath string) (*pogs.TunnelAuth, error) {
filePath, err := tunnelFilePath(tunnelID, originCertPath)
if err != nil {
return nil, err
}
body, err := ioutil.ReadFile(filePath)
if err != nil {
return nil, errors.Wrapf(err, "couldn't read tunnel credentials from %v", filePath)
}
auth := pogs.TunnelAuth{}
err = json.Unmarshal(body, &auth)
return &auth, errors.Wrap(err, "couldn't parse tunnel credentials from JSON")
}
func buildListCommand() *cli.Command {
return &cli.Command{
Name: "list",
@@ -75,7 +160,7 @@ func buildListCommand() *cli.Command {
Usage: "List existing tunnels",
ArgsUsage: " ",
Hidden: hideSubcommands,
Flags: []cli.Flag{outputFormatFlag},
Flags: []cli.Flag{outputFormatFlag, showDeletedFlag},
}
}
@@ -85,16 +170,32 @@ func listTunnels(c *cli.Context) error {
return errors.Wrap(err, "error setting up logger")
}
client, err := newTunnelstoreClient(c, logger)
originCertPath, err := findOriginCert(c, logger)
if err != nil {
return errors.Wrap(err, "Error locating origin cert")
}
cert, err := getOriginCertFromContext(originCertPath, logger)
if err != nil {
return err
}
client := newTunnelstoreClient(c, cert, logger)
tunnels, err := client.ListTunnels()
allTunnels, err := client.ListTunnels()
if err != nil {
return errors.Wrap(err, "Error listing tunnels")
}
var tunnels []tunnelstore.Tunnel
if c.Bool("show-deleted") {
tunnels = allTunnels
} else {
for _, tunnel := range allTunnels {
if tunnel.DeletedAt.IsZero() {
tunnels = append(tunnels, tunnel)
}
}
}
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
return renderOutput(outputFormat, tunnels)
}
@@ -155,10 +256,15 @@ func deleteTunnel(c *cli.Context) error {
return errors.Wrap(err, "error setting up logger")
}
client, err := newTunnelstoreClient(c, logger)
originCertPath, err := findOriginCert(c, logger)
if err != nil {
return errors.Wrap(err, "Error locating origin cert")
}
cert, err := getOriginCertFromContext(originCertPath, logger)
if err != nil {
return err
}
client := newTunnelstoreClient(c, cert, logger)
if err := client.DeleteTunnel(id); err != nil {
return errors.Wrapf(err, "Error deleting tunnel %s", id)
@@ -180,11 +286,12 @@ func renderOutput(format string, v interface{}) error {
}
}
func newTunnelstoreClient(c *cli.Context, logger logger.Service) (tunnelstore.Client, error) {
originCertPath, err := findOriginCert(c, logger)
if err != nil {
return nil, errors.Wrap(err, "Error locating origin cert")
}
func newTunnelstoreClient(c *cli.Context, cert *certutil.OriginCert, logger logger.Service) tunnelstore.Client {
client := tunnelstore.NewRESTClient(c.String("api-url"), cert.AccountID, cert.ServiceKey, logger)
return client
}
func getOriginCertFromContext(originCertPath string, logger logger.Service) (*certutil.OriginCert, error) {
blocks, err := readOriginCert(originCertPath, logger)
if err != nil {
@@ -199,8 +306,39 @@ func newTunnelstoreClient(c *cli.Context, logger logger.Service) (tunnelstore.Cl
if cert.AccountID == "" {
return nil, errors.Errorf(`Origin certificate needs to be refreshed before creating new tunnels.\nDelete %s and run "cloudflared login" to obtain a new cert.`, originCertPath)
}
client := tunnelstore.NewRESTClient(c.String("api-url"), cert.AccountID, cert.ServiceKey, logger)
return client, nil
return cert, nil
}
func buildRunCommand() *cli.Command {
return &cli.Command{
Name: "run",
Action: cliutil.ErrorHandler(runTunnel),
Usage: "Proxy a local web server by running the given tunnel",
ArgsUsage: "TUNNEL-ID",
Hidden: hideSubcommands,
Flags: []cli.Flag{forceFlag},
}
}
func runTunnel(c *cli.Context) error {
if c.NArg() != 1 {
return cliutil.UsageError(`"cloudflared tunnel run" requires exactly 1 argument, the ID of the tunnel to run.`)
}
id := c.Args().First()
logger, err := logger.New()
if err != nil {
return errors.Wrap(err, "error setting up logger")
}
originCertPath, err := findOriginCert(c, logger)
if err != nil {
return errors.Wrap(err, "Error locating origin cert")
}
credentials, err := readTunnelCredentials(id, originCertPath)
if err != nil {
return err
}
logger.Debugf("Read credentials for %v", credentials.AccountTag)
return StartServer(c, version, shutdownC, graceShutdownC, &origin.NamedTunnelConfig{Auth: *credentials, ID: id})
}
@@ -6,6 +6,7 @@ import (
"github.com/cloudflare/cloudflared/tunnelstore"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func Test_fmtConnections(t *testing.T) {
@@ -69,3 +70,10 @@ func Test_fmtConnections(t *testing.T) {
})
}
}
func TestTunnelfilePath(t *testing.T) {
actual, err := tunnelFilePath("tunnel", "~/.cloudflared/cert.pem")
assert.NoError(t, err)
expected := "~/.cloudflared/tunnel.json"
assert.Equal(t, expected, actual)
}
+1 -1
View File
@@ -67,7 +67,7 @@ type UpdateOutcome struct {
}
func (uo *UpdateOutcome) noUpdate() bool {
return uo.Error != nil && uo.Updated == false
return uo.Error == nil && uo.Updated == false
}
func checkForUpdateAndApply() UpdateOutcome {
+128
View File
@@ -0,0 +1,128 @@
#!/usr/bin/python3
"""
Creates Github Releases and uploads assets
"""
import argparse
import logging
import os
from github import Github, GithubException, UnknownObjectException
FORMAT = "%(levelname)s - %(asctime)s: %(message)s"
logging.basicConfig(format=FORMAT)
CLOUDFLARED_REPO = os.environ.get("GITHUB_REPO", "cloudflare/cloudflared")
GITHUB_CONFLICT_CODE = "already_exists"
def assert_tag_exists(repo, version):
""" Raise exception if repo does not contain a tag matching version """
tags = repo.get_tags()
if not tags or tags[0].name != version:
raise Exception("Tag {} not found".format(version))
def get_or_create_release(repo, version, dry_run=False):
"""
Get a Github Release matching the version tag or create a new one.
If a conflict occurs on creation, attempt to fetch the Release on last time
"""
try:
release = repo.get_release(version)
logging.info("Release %s found", version)
return release
except UnknownObjectException:
logging.info("Release %s not found", version)
# We dont want to create a new release tag if one doesnt already exist
assert_tag_exists(repo, version)
if dry_run:
logging.info("Skipping Release creation because of dry-run")
return
try:
logging.info("Creating release %s", version)
return repo.create_git_release(version, version, "")
except GithubException as e:
errors = e.data.get("errors", [])
if e.status == 422 and any(
[err.get("code") == GITHUB_CONFLICT_CODE for err in errors]
):
logging.warning(
"Conflict: Release was likely just made by a different build: %s",
e.data,
)
return repo.get_release(version)
raise e
def parse_args():
""" Parse and validate args """
parser = argparse.ArgumentParser(
description="Creates Github Releases and uploads assets."
)
parser.add_argument(
"--api-key", default=os.environ.get("API_KEY"), help="Github API key"
)
parser.add_argument(
"--release-version",
metavar="version",
default=os.environ.get("VERSION"),
help="Release version",
)
parser.add_argument(
"--path", default=os.environ.get("ASSET_PATH"), help="Asset path"
)
parser.add_argument(
"--name", default=os.environ.get("ASSET_NAME"), help="Asset Name"
)
parser.add_argument(
"--dry-run", action="store_true", help="Do not create release or upload asset"
)
args = parser.parse_args()
is_valid = True
if not args.release_version:
logging.error("Missing release version")
is_valid = False
if not args.path:
logging.error("Missing asset path")
is_valid = False
if not args.name:
logging.error("Missing asset name")
is_valid = False
if not args.api_key:
logging.error("Missing API key")
is_valid = False
if is_valid:
return args
parser.print_usage()
exit(1)
def main():
""" Attempts to upload Asset to Github Release. Creates Release if it doesnt exist """
try:
args = parse_args()
client = Github(args.api_key)
repo = client.get_repo(CLOUDFLARED_REPO)
release = get_or_create_release(repo, args.release_version, args.dry_run)
if args.dry_run:
logging.info("Skipping asset upload because of dry-run")
return
release.upload_asset(args.path, name=args.name)
except Exception as e:
logging.exception(e)
exit(1)
main()
-1
View File
@@ -54,7 +54,6 @@ require (
github.com/prometheus/common v0.7.0 // indirect
github.com/prometheus/procfs v0.0.5 // indirect
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
github.com/stretchr/testify v1.3.0
github.com/tinylib/msgp v1.1.0 // indirect
github.com/xo/dburl v0.0.0-20191005012637-293c3298d6c0
+2 -13
View File
@@ -115,20 +115,9 @@ func New(opts ...Option) (Service, error) {
if !config.terminalOutputDisabled {
if len(config.supportedTerminalLevels) == 0 {
l.Add(os.Stdout, NewTerminalFormatter(""), InfoLevel)
l.Add(os.Stderr, NewTerminalFormatter(""), ErrorLevel, FatalLevel)
l.Add(os.Stderr, NewTerminalFormatter(""), InfoLevel, ErrorLevel, FatalLevel)
} else {
errLevels := []Level{}
outLevels := []Level{}
for _, level := range config.supportedTerminalLevels {
if level == ErrorLevel || level == FatalLevel {
errLevels = append(errLevels, level)
} else {
outLevels = append(outLevels, level)
}
}
l.Add(os.Stdout, NewTerminalFormatter(""), outLevels...)
l.Add(os.Stderr, NewTerminalFormatter(""), errLevels...)
l.Add(os.Stderr, NewTerminalFormatter(""), config.supportedTerminalLevels...)
}
}
return l, nil
+25 -5
View File
@@ -33,7 +33,7 @@ func NewFileRollingWriter(directory, baseFileName string, maxFileSize int64, max
// Write is an implementation of io.writer the rolls the file once it reaches its max size
// It is expected the caller to Write is doing so in a thread safe manner (as WriteManager does).
func (w *FileRollingWriter) Write(p []byte) (n int, err error) {
logFile := buildPath(w.directory, w.baseFileName)
logFile, isSingleFile := buildPath(w.directory, w.baseFileName)
if w.fileHandle == nil {
h, err := os.OpenFile(logFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0664)
if err != nil {
@@ -55,7 +55,7 @@ func (w *FileRollingWriter) Write(p []byte) (n int, err error) {
written, err := w.fileHandle.Write(p)
// check if the file needs to be rolled
if err == nil && info.Size()+int64(written) > w.maxFileSize {
if err == nil && info.Size()+int64(written) > w.maxFileSize && !isSingleFile {
// close the file handle than do the renaming. A new one will be opened on the next write
w.Close()
w.rename(logFile, 1)
@@ -77,7 +77,10 @@ func (w *FileRollingWriter) Close() {
// but if cloudflared-1.log already exists, it is renamed to cloudflared-2.log,
// then the other files move in to their postion
func (w *FileRollingWriter) rename(sourcePath string, index uint) {
destinationPath := buildPath(w.directory, fmt.Sprintf("%s-%d", w.baseFileName, index))
destinationPath, isSingleFile := buildPath(w.directory, fmt.Sprintf("%s-%d", w.baseFileName, index))
if isSingleFile {
return //don't need to rename anything, it is a single file
}
// rolled to the max amount of files allowed on disk
if index >= w.maxFileCount {
@@ -93,8 +96,13 @@ func (w *FileRollingWriter) rename(sourcePath string, index uint) {
os.Rename(sourcePath, destinationPath)
}
func buildPath(directory, fileName string) string {
return filepath.Join(directory, fileName+".log")
// return the path to the log file and if it is a single file or not.
// true means a single file. false means a rolled file
func buildPath(directory, fileName string) (string, bool) {
if !isDirectory(directory) { // not a directory, so try and treat it as a single file for backwards compatibility sake
return directory, true
}
return filepath.Join(directory, fileName+".log"), false
}
func exists(filePath string) bool {
@@ -103,3 +111,15 @@ func exists(filePath string) bool {
}
return true
}
func isDirectory(path string) bool {
if path == "" {
return true
}
fileInfo, err := os.Stat(path)
if err != nil {
return false
}
return fileInfo.IsDir()
}
+57 -5
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
@@ -31,18 +32,23 @@ func TestFileWrite(t *testing.T) {
}
func TestRolling(t *testing.T) {
dirName := "testdir"
err := os.Mkdir(dirName, 0755)
assert.NoError(t, err)
fileName := "test_file"
firstFile := fileName + ".log"
secondFile := fileName + "-1.log"
thirdFile := fileName + "-2.log"
firstFile := filepath.Join(dirName, fileName+".log")
secondFile := filepath.Join(dirName, fileName+"-1.log")
thirdFile := filepath.Join(dirName, fileName+"-2.log")
defer func() {
os.RemoveAll(dirName)
os.Remove(firstFile)
os.Remove(secondFile)
os.Remove(thirdFile)
}()
w := NewFileRollingWriter("", fileName, 1000, 2)
w := NewFileRollingWriter(dirName, fileName, 1000, 2)
defer w.Close()
for i := 99; i >= 1; i-- {
@@ -52,5 +58,51 @@ func TestRolling(t *testing.T) {
assert.FileExists(t, firstFile, "first file doesn't exist as expected")
assert.FileExists(t, secondFile, "second file doesn't exist as expected")
assert.FileExists(t, thirdFile, "third file doesn't exist as expected")
assert.False(t, exists(fileName+"-3.log"), "limited to two files and there is more")
assert.False(t, exists(filepath.Join(dirName, fileName+"-3.log")), "limited to two files and there is more")
}
func TestSingleFile(t *testing.T) {
fileName := "test_file"
testData := []byte(string("hello Dalton, how are you doing?"))
defer func() {
os.Remove(fileName)
}()
w := NewFileRollingWriter(fileName, fileName, 1000, 2)
defer w.Close()
l, err := w.Write(testData)
assert.NoError(t, err)
assert.Equal(t, l, len(testData), "expected write length and data length to match")
d, err := ioutil.ReadFile(fileName)
assert.FileExists(t, fileName, "file doesn't exist at expected path")
assert.Equal(t, d, testData, "expected data in file to match test data")
}
func TestSingleFileInDirectory(t *testing.T) {
dirName := "testdir"
err := os.Mkdir(dirName, 0755)
assert.NoError(t, err)
fileName := "test_file"
fullPath := filepath.Join(dirName, fileName+".log")
testData := []byte(string("hello Dalton, how are you doing?"))
defer func() {
os.Remove(fullPath)
os.RemoveAll(dirName)
}()
w := NewFileRollingWriter(fullPath, fileName, 1000, 2)
defer w.Close()
l, err := w.Write(testData)
assert.NoError(t, err)
assert.Equal(t, l, len(testData), "expected write length and data length to match")
d, err := ioutil.ReadFile(fullPath)
assert.FileExists(t, fullPath, "file doesn't exist at expected path")
assert.Equal(t, d, testData, "expected data in file to match test data")
}
+17 -6
View File
@@ -2,6 +2,7 @@ package logger
import (
"fmt"
"runtime"
"time"
"github.com/acmacalister/skittles"
@@ -58,14 +59,17 @@ func (f *DefaultFormatter) Content(l Level, c string) string {
// TerminalFormatter is setup for colored output
type TerminalFormatter struct {
format string
format string
supportsColor bool
}
// NewTerminalFormatter creates a Terminal formatter for colored output
// format is the time format to use for timestamp formatting
func NewTerminalFormatter(format string) Formatter {
supportsColor := (runtime.GOOS != "windows")
return &TerminalFormatter{
format: format,
format: format,
supportsColor: supportsColor,
}
}
@@ -74,13 +78,13 @@ func (f *TerminalFormatter) Timestamp(l Level, d time.Time) string {
t := ""
switch l {
case InfoLevel:
t = skittles.Cyan("[INFO] ")
t = f.output("[INFO] ", skittles.Cyan)
case ErrorLevel:
t = skittles.Red("[ERROR] ")
t = f.output("[ERROR] ", skittles.Red)
case DebugLevel:
t = skittles.Yellow("[DEBUG] ")
t = f.output("[DEBUG] ", skittles.Yellow)
case FatalLevel:
t = skittles.Red("[FATAL] ")
t = f.output("[FATAL] ", skittles.Red)
}
return t
}
@@ -89,3 +93,10 @@ func (f *TerminalFormatter) Timestamp(l Level, d time.Time) string {
func (f *TerminalFormatter) Content(l Level, c string) string {
return c
}
func (f *TerminalFormatter) output(msg string, colorFunc func(interface{}) string) string {
if f.supportsColor {
return colorFunc(msg)
}
return msg
}
+31
View File
@@ -0,0 +1,31 @@
#!/bin/bash
TARGET_DIRECTORY=".build"
BINARY_NAME="cloudflared"
VERSION=$(git describe --tags --always --dirty="-dev")
PRODUCT="cloudflared"
echo "building cloudflared"
make cloudflared
echo "creating build directory"
mkdir ${TARGET_DIRECTORY}
mkdir ${TARGET_DIRECTORY}/contents
cp -r .mac_resources/scripts ${TARGET_DIRECTORY}/scripts
echo "move cloudflared into the build directory"
mv $BINARY_NAME {$TARGET_DIRECTORY}/contents/${PRODUCT}
echo "build the installer package"
pkgbuild --identifier com.cloudflare.${PRODUCT} \
--version ${VERSION} \
--scripts ${TARGET_DIRECTORY}/scripts \
--root ${TARGET_DIRECTORY}/contents \
--install-location /usr/local/bin \
${PRODUCT}.pkg
# TODO: our iOS/Mac account doesn't have this installer certificate type.
# need to find how we can get it --sign "Developer ID Installer: Cloudflare" \
echo "cleaning up the build directory"
rm -rf $TARGET_DIRECTORY
+5 -2
View File
@@ -68,6 +68,8 @@ type Supervisor struct {
connDigest map[uint8][]byte
bufferPool *buffer.Pool
namedTunnel *NamedTunnelConfig
}
type resolveResult struct {
@@ -80,7 +82,7 @@ type tunnelError struct {
err error
}
func NewSupervisor(config *TunnelConfig, u uuid.UUID) (*Supervisor, error) {
func NewSupervisor(config *TunnelConfig, cloudflaredUUID uuid.UUID, namedTunnel *NamedTunnelConfig) (*Supervisor, error) {
var (
edgeIPs *edgediscovery.Edge
err error
@@ -94,7 +96,7 @@ func NewSupervisor(config *TunnelConfig, u uuid.UUID) (*Supervisor, error) {
return nil, err
}
return &Supervisor{
cloudflaredUUID: u,
cloudflaredUUID: cloudflaredUUID,
config: config,
edgeIPs: edgeIPs,
tunnelErrors: make(chan tunnelError),
@@ -102,6 +104,7 @@ func NewSupervisor(config *TunnelConfig, u uuid.UUID) (*Supervisor, error) {
logger: config.Logger,
connDigest: make(map[uint8][]byte),
bufferPool: buffer.NewPool(512 * 1024),
namedTunnel: namedTunnel,
}, nil
}
+4 -4
View File
@@ -48,7 +48,7 @@ func TestRefreshAuthBackoff(t *testing.T) {
return time.After(d)
}
s, err := NewSupervisor(testConfig(logger), uuid.New())
s, err := NewSupervisor(testConfig(logger), uuid.New(), nil)
if !assert.NoError(t, err) {
t.FailNow()
}
@@ -92,7 +92,7 @@ func TestRefreshAuthSuccess(t *testing.T) {
return time.After(d)
}
s, err := NewSupervisor(testConfig(logger), uuid.New())
s, err := NewSupervisor(testConfig(logger), uuid.New(), nil)
if !assert.NoError(t, err) {
t.FailNow()
}
@@ -120,7 +120,7 @@ func TestRefreshAuthUnknown(t *testing.T) {
return time.After(d)
}
s, err := NewSupervisor(testConfig(logger), uuid.New())
s, err := NewSupervisor(testConfig(logger), uuid.New(), nil)
if !assert.NoError(t, err) {
t.FailNow()
}
@@ -142,7 +142,7 @@ func TestRefreshAuthUnknown(t *testing.T) {
func TestRefreshAuthFail(t *testing.T) {
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
s, err := NewSupervisor(testConfig(logger), uuid.New())
s, err := NewSupervisor(testConfig(logger), uuid.New(), nil)
if !assert.NoError(t, err) {
t.FailNow()
}
+13 -7
View File
@@ -26,6 +26,7 @@ import (
"github.com/cloudflare/cloudflared/logger"
"github.com/cloudflare/cloudflared/signal"
"github.com/cloudflare/cloudflared/tunnelrpc"
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
"github.com/cloudflare/cloudflared/validation"
"github.com/cloudflare/cloudflared/websocket"
@@ -178,8 +179,13 @@ func (c *TunnelConfig) SupportedFeatures() []string {
return basic
}
func StartTunnelDaemon(ctx context.Context, config *TunnelConfig, connectedSignal *signal.Signal, cloudflaredID uuid.UUID, reconnectCh chan ReconnectSignal) error {
s, err := NewSupervisor(config, cloudflaredID)
type NamedTunnelConfig struct {
Auth pogs.TunnelAuth
ID string
}
func StartTunnelDaemon(ctx context.Context, config *TunnelConfig, connectedSignal *signal.Signal, cloudflaredID uuid.UUID, reconnectCh chan ReconnectSignal, namedTunnel *NamedTunnelConfig) error {
s, err := NewSupervisor(config, cloudflaredID, namedTunnel)
if err != nil {
return err
}
@@ -192,7 +198,7 @@ func ServeTunnelLoop(ctx context.Context,
addr *net.TCPAddr,
connectionID uint8,
connectedSignal *signal.Signal,
u uuid.UUID,
cloudflaredUUID uuid.UUID,
bufferPool *buffer.Pool,
reconnectCh chan ReconnectSignal,
) error {
@@ -216,7 +222,7 @@ func ServeTunnelLoop(ctx context.Context,
addr, connectionID,
connectedFuse,
&backoff,
u,
cloudflaredUUID,
bufferPool,
reconnectCh,
)
@@ -240,7 +246,7 @@ func ServeTunnel(
connectionID uint8,
connectedFuse *h2mux.BooleanFuse,
backoff *BackoffHandler,
u uuid.UUID,
cloudflaredUUID uuid.UUID,
bufferPool *buffer.Pool,
reconnectCh chan ReconnectSignal,
) (err error, recoverable bool) {
@@ -300,7 +306,7 @@ func ServeTunnel(
connDigest = digest
}
}
return ReconnectTunnel(serveCtx, token, eventDigest, connDigest, handler.muxer, config, logger, connectionID, originLocalIP, u, credentialManager)
return ReconnectTunnel(serveCtx, token, eventDigest, connDigest, handler.muxer, config, logger, connectionID, originLocalIP, cloudflaredUUID, credentialManager)
}
// log errors and proceed to RegisterTunnel
if tokenErr != nil {
@@ -310,7 +316,7 @@ func ServeTunnel(
logger.Errorf("Couldn't get event digest: %s", eventDigestErr)
}
}
return RegisterTunnel(serveCtx, credentialManager, handler.muxer, config, logger, connectionID, originLocalIP, u)
return RegisterTunnel(serveCtx, credentialManager, handler.muxer, config, logger, connectionID, originLocalIP, cloudflaredUUID)
})
errGroup.Go(func() error {
+2 -1
View File
@@ -6,6 +6,7 @@ import (
"net"
"net/http"
"testing"
"time"
"github.com/stretchr/testify/assert"
"golang.org/x/net/proxy"
@@ -58,7 +59,6 @@ func startTestServer(t *testing.T, httpHandler func(w http.ResponseWriter, r *ht
// start the servers
go http.ListenAndServe(":8085", mux)
}
func respondWithJSON(w http.ResponseWriter, v interface{}, status int) {
@@ -78,6 +78,7 @@ func OkJSONResponseHandler(w http.ResponseWriter, r *http.Request) {
func TestSocksConnection(t *testing.T) {
startTestServer(t, OkJSONResponseHandler)
time.Sleep(100 * time.Millisecond)
b := sendSocksRequest(t)
assert.True(t, len(b) > 0, "no data returned!")
+2 -2
View File
@@ -25,12 +25,12 @@ func TestSessionLogWrite(t *testing.T) {
testStr := "hi"
logger := createSessionLogger(t)
defer func() {
logger.Close()
os.Remove(sessionLogFileName)
}()
logger.Write([]byte(testStr))
time.Sleep(2 * time.Millisecond)
logger.Close()
f, err := os.Open(sessionLogFileName)
if err != nil {
t.Fatal("couldn't read the log file!", err)
+236
View File
@@ -0,0 +1,236 @@
package pogs
import (
"context"
"errors"
"net"
"time"
"github.com/google/uuid"
"zombiezen.com/go/capnproto2/pogs"
"zombiezen.com/go/capnproto2/server"
"github.com/cloudflare/cloudflared/tunnelrpc"
)
type RegistrationServer interface {
RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error)
UnregisterConnection(ctx context.Context)
}
type ClientInfo struct {
ClientID []byte `capnp:"clientId"` // must be a slice for capnp compatibility
Features []string
Version string
Arch string
}
type ConnectionOptions struct {
Client ClientInfo
OriginLocalIP net.IP `capnp:"originLocalIp"`
ReplaceExisting bool
CompressionQuality uint8
}
type TunnelAuth struct {
AccountTag string
TunnelSecret []byte
}
func (p *ConnectionOptions) MarshalCapnproto(s tunnelrpc.ConnectionOptions) error {
return pogs.Insert(tunnelrpc.ConnectionOptions_TypeID, s.Struct, p)
}
func (p *ConnectionOptions) UnmarshalCapnproto(s tunnelrpc.ConnectionOptions) error {
return pogs.Extract(p, tunnelrpc.ConnectionOptions_TypeID, s.Struct)
}
func (a *TunnelAuth) MarshalCapnproto(s tunnelrpc.TunnelAuth) error {
return pogs.Insert(tunnelrpc.TunnelAuth_TypeID, s.Struct, a)
}
func (a *TunnelAuth) UnmarshalCapnproto(s tunnelrpc.TunnelAuth) error {
return pogs.Extract(a, tunnelrpc.TunnelAuth_TypeID, s.Struct)
}
type ConnectionDetails struct {
UUID uuid.UUID
Location string
}
func (details *ConnectionDetails) MarshalCapnproto(s tunnelrpc.ConnectionDetails) error {
if err := s.SetUuid(details.UUID[:]); err != nil {
return err
}
if err := s.SetLocationName(details.Location); err != nil {
return err
}
return nil
}
func (details *ConnectionDetails) UnmarshalCapnproto(s tunnelrpc.ConnectionDetails) error {
uuidBytes, err := s.Uuid()
if err != nil {
return err
}
details.UUID, err = uuid.FromBytes(uuidBytes)
if err != nil {
return err
}
details.Location, err = s.LocationName()
if err != nil {
return err
}
return err
}
func MarshalError(s tunnelrpc.ConnectionError, err error) error {
if err := s.SetCause(err.Error()); err != nil {
return err
}
if retryableErr, ok := err.(*RetryableError); ok {
s.SetShouldRetry(true)
s.SetRetryAfter(int64(retryableErr.Delay))
}
return nil
}
func (i TunnelServer_PogsImpl) RegisterConnection(p tunnelrpc.RegistrationServer_registerConnection) error {
server.Ack(p.Options)
auth, err := p.Params.Auth()
if err != nil {
return err
}
var pogsAuth TunnelAuth
err = pogsAuth.UnmarshalCapnproto(auth)
if err != nil {
return err
}
uuidBytes, err := p.Params.TunnelId()
if err != nil {
return err
}
tunnelID, err := uuid.FromBytes(uuidBytes)
if err != nil {
return err
}
connIndex := p.Params.ConnIndex()
options, err := p.Params.Options()
if err != nil {
return err
}
var pogsOptions ConnectionOptions
err = pogsOptions.UnmarshalCapnproto(options)
if err != nil {
return err
}
connDetails, callError := i.impl.RegisterConnection(p.Ctx, pogsAuth, tunnelID, connIndex, &pogsOptions)
resp, err := p.Results.NewResult()
if err != nil {
return err
}
if callError != nil {
if connError, err := resp.Result().NewError(); err != nil {
return err
} else {
return MarshalError(connError, callError)
}
}
if details, err := resp.Result().NewConnectionDetails(); err != nil {
return err
} else {
return connDetails.MarshalCapnproto(details)
}
}
func (i TunnelServer_PogsImpl) UnregisterConnection(p tunnelrpc.RegistrationServer_unregisterConnection) error {
server.Ack(p.Options)
i.impl.UnregisterConnection(p.Ctx)
return nil
}
func (c TunnelServer_PogsClient) RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error) {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.RegisterConnection(ctx, func(p tunnelrpc.RegistrationServer_registerConnection_Params) error {
tunnelAuth, err := p.NewAuth()
if err != nil {
return err
}
if err = auth.MarshalCapnproto(tunnelAuth); err != nil {
return err
}
err = p.SetAuth(tunnelAuth)
if err != nil {
return err
}
err = p.SetTunnelId(tunnelID[:])
if err != nil {
return err
}
p.SetConnIndex(connIndex)
connectionOptions, err := p.NewOptions()
if err != nil {
return err
}
err = options.MarshalCapnproto(connectionOptions)
if err != nil {
return err
}
return nil
})
response, err := promise.Result().Struct()
if err != nil {
return nil, wrapRPCError(err)
}
result := response.Result()
switch result.Which() {
case tunnelrpc.ConnectionResponse_result_Which_error:
resultError, err := result.Error()
if err != nil {
return nil, wrapRPCError(err)
}
cause, err := resultError.Cause()
if err != nil {
return nil, wrapRPCError(err)
}
err = errors.New(cause)
if resultError.ShouldRetry() {
err = RetryErrorAfter(err, time.Duration(resultError.RetryAfter()))
}
return nil, err
case tunnelrpc.ConnectionResponse_result_Which_connectionDetails:
connDetails, err := result.ConnectionDetails()
if err != nil {
return nil, wrapRPCError(err)
}
details := new(ConnectionDetails)
if err = details.UnmarshalCapnproto(connDetails); err != nil {
return nil, wrapRPCError(err)
}
return details, nil
}
return nil, newRPCError("unknown result which %d", result.Which())
}
func (c TunnelServer_PogsClient) Unregister(ctx context.Context) error {
client := tunnelrpc.TunnelServer{Client: c.Client}
promise := client.UnregisterConnection(ctx, func(p tunnelrpc.RegistrationServer_unregisterConnection_Params) error {
return nil
})
_, err := promise.Struct()
if err != nil {
return wrapRPCError(err)
}
return nil
}
+140
View File
@@ -0,0 +1,140 @@
package pogs
import (
"context"
"errors"
"net"
"testing"
"time"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
capnp "zombiezen.com/go/capnproto2"
"zombiezen.com/go/capnproto2/rpc"
"github.com/cloudflare/cloudflared/tunnelrpc"
)
const testAccountTag = "abc123"
func TestMarshalConnectionOptions(t *testing.T) {
clientID := uuid.New()
orig := ConnectionOptions{
Client: ClientInfo{
ClientID: clientID[:],
Features: []string{"a", "b"},
Version: "1.2.3",
Arch: "macos",
},
OriginLocalIP: []byte{10, 2, 3, 4},
ReplaceExisting: false,
CompressionQuality: 1,
}
_, seg, err := capnp.NewMessage(capnp.SingleSegment(nil))
require.NoError(t, err)
capnpOpts, err := tunnelrpc.NewConnectionOptions(seg)
require.NoError(t, err)
err = orig.MarshalCapnproto(capnpOpts)
assert.NoError(t, err)
var pogsOpts ConnectionOptions
err = pogsOpts.UnmarshalCapnproto(capnpOpts)
assert.NoError(t, err)
assert.Equal(t, orig, pogsOpts)
}
func TestConnectionRegistrationRPC(t *testing.T) {
p1, p2 := net.Pipe()
t1, t2 := rpc.StreamTransport(p1), rpc.StreamTransport(p2)
// Server-side
testImpl := testConnectionRegistrationServer{}
srv := TunnelServer_ServerToClient(&testImpl)
serverConn := rpc.NewConn(t1, rpc.MainInterface(srv.Client))
defer serverConn.Wait()
ctx := context.Background()
clientConn := rpc.NewConn(t2)
defer clientConn.Close()
client := TunnelServer_PogsClient{
Client: clientConn.Bootstrap(ctx),
Conn: clientConn,
}
defer client.Close()
clientID := uuid.New()
options := &ConnectionOptions{
Client: ClientInfo{
ClientID: clientID[:],
Features: []string{"foo"},
Version: "1.2.3",
Arch: "macos",
},
OriginLocalIP: net.IP{10, 20, 30, 40},
ReplaceExisting: true,
CompressionQuality: 0,
}
expectedDetails := ConnectionDetails{
UUID: uuid.New(),
Location: "TEST",
}
testImpl.details = &expectedDetails
testImpl.err = nil
auth := TunnelAuth{
AccountTag: testAccountTag,
TunnelSecret: []byte{1, 2, 3, 4},
}
// success
tunnelID := uuid.New()
details, err := client.RegisterConnection(ctx, auth, tunnelID, 2, options)
assert.NoError(t, err)
assert.Equal(t, expectedDetails, *details)
// regular error
testImpl.details = nil
testImpl.err = errors.New("internal")
_, err = client.RegisterConnection(ctx, auth, tunnelID, 2, options)
assert.EqualError(t, err, "internal")
// retriable error
testImpl.details = nil
const delay = 27 * time.Second
testImpl.err = RetryErrorAfter(errors.New("retryable"), delay)
_, err = client.RegisterConnection(ctx, auth, tunnelID, 2, options)
assert.EqualError(t, err, "retryable")
re, ok := err.(*RetryableError)
assert.True(t, ok)
assert.Equal(t, delay, re.Delay)
}
type testConnectionRegistrationServer struct {
mockTunnelServerBase
details *ConnectionDetails
err error
}
func (t *testConnectionRegistrationServer) RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error) {
if auth.AccountTag != testAccountTag {
panic("bad account tag: " + auth.AccountTag)
}
if t.err != nil {
return nil, t.err
}
if t.details != nil {
return t.details, nil
}
panic("either details or err mush be set")
}
+56
View File
@@ -0,0 +1,56 @@
package pogs
import (
"fmt"
"time"
)
type RetryableError struct {
err error
Delay time.Duration
}
func (re *RetryableError) Error() string {
return re.err.Error()
}
// RetryErrorAfter wraps err to indicate that client should retry after delay
func RetryErrorAfter(err error, delay time.Duration) *RetryableError {
return &RetryableError{
err: err,
Delay: delay,
}
}
func (re *RetryableError) Unwrap() error {
return re.err
}
// RPCError is used to indicate errors returned by the RPC subsystem rather
// than failure of a remote operation
type RPCError struct {
err error
}
func (re *RPCError) Error() string {
return re.err.Error()
}
func wrapRPCError(err error) *RPCError {
if err != nil {
return &RPCError{
err: err,
}
}
return nil
}
func newRPCError(format string, args ...interface{}) *RPCError {
return &RPCError{
fmt.Errorf(format, args...),
}
}
func (re *RPCError) Unwrap() error {
return re.err
}
+41
View File
@@ -0,0 +1,41 @@
package pogs
import (
"context"
"github.com/google/uuid"
)
// mockTunnelServerBase provides a placeholder implementation
// for TunnelServer interface that can be used to build
// mocks for specific unit tests without having to implement every method
type mockTunnelServerBase struct{}
func (mockTunnelServerBase) RegisterConnection(ctx context.Context, auth TunnelAuth, tunnelID uuid.UUID, connIndex byte, options *ConnectionOptions) (*ConnectionDetails, error) {
panic("unexpected call to RegisterConnection")
}
func (mockTunnelServerBase) UnregisterConnection(ctx context.Context) {
panic("unexpected call to UnregisterConnection")
}
func (mockTunnelServerBase) RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration {
panic("unexpected call to RegisterTunnel")
}
func (mockTunnelServerBase) GetServerInfo(ctx context.Context) (*ServerInfo, error) {
panic("unexpected call to GetServerInfo")
}
func (mockTunnelServerBase) UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error {
panic("unexpected call to UnregisterTunnel")
}
func (mockTunnelServerBase) Authenticate(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) (*AuthenticateResponse, error) {
panic("unexpected call to Authenticate")
}
func (mockTunnelServerBase) ReconnectTunnel(ctx context.Context, jwt, eventDigest, connDigest []byte, hostname string, options *RegistrationOptions) (*TunnelRegistration, error) {
panic("unexpected call to ReconnectTunnel")
}
+1
View File
@@ -201,6 +201,7 @@ func UnmarshalServerInfo(s tunnelrpc.ServerInfo) (*ServerInfo, error) {
}
type TunnelServer interface {
RegistrationServer
RegisterTunnel(ctx context.Context, originCert []byte, hostname string, options *RegistrationOptions) *TunnelRegistration
GetServerInfo(ctx context.Context) (*ServerInfo, error)
UnregisterTunnel(ctx context.Context, gracePeriodNanoSec int64) error
+54 -1
View File
@@ -78,7 +78,60 @@ struct AuthenticateResponse {
hoursUntilRefresh @3 :UInt8;
}
interface TunnelServer {
struct ClientInfo {
# The tunnel client's unique identifier, used to verify a reconnection.
clientId @0 :Data;
# Set of features this cloudflared knows it supports
features @1 :List(Text);
# Information about the running binary.
version @2 :Text;
# Client OS and CPU info
arch @3 :Text;
}
struct ConnectionOptions {
# client details
client @0 :ClientInfo;
# origin LAN IP
originLocalIp @1 :Data;
# What to do if connection already exists
replaceExisting @2 :Bool;
# cross stream compression setting, 0 - off, 3 - high
compressionQuality @3 :UInt8;
}
struct ConnectionResponse {
result :union {
error @0 :ConnectionError;
connectionDetails @1 :ConnectionDetails;
}
}
struct ConnectionError {
cause @0 :Text;
# How long should this connection wait to retry in ns
retryAfter @1 :Int64;
shouldRetry @2 :Bool;
}
struct ConnectionDetails {
# identifier of this connection
uuid @0 :Data;
# airport code of the colo where this connection landed
locationName @1 :Text;
}
struct TunnelAuth {
accountTag @0 :Text;
tunnelSecret @1 :Data;
}
interface RegistrationServer {
registerConnection @0 (auth :TunnelAuth, tunnelId :Data, connIndex :UInt8, options :ConnectionOptions) -> (result :ConnectionResponse);
unregisterConnection @1 () -> ();
}
interface TunnelServer extends (RegistrationServer) {
registerTunnel @0 (originCert :Data, hostname :Text, options :RegistrationOptions) -> (result :TunnelRegistration);
getServerInfo @1 () -> (result :ServerInfo);
unregisterTunnel @2 (gracePeriodNanoSec :Int64) -> ();
+1366 -126
View File
File diff suppressed because it is too large Load Diff
+7 -4
View File
@@ -30,6 +30,7 @@ type Tunnel struct {
ID string `json:"id"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
DeletedAt time.Time `json:"deleted_at"`
Connections []Connection `json:"connections"`
}
@@ -39,7 +40,7 @@ type Connection struct {
}
type Client interface {
CreateTunnel(name string) (*Tunnel, error)
CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, error)
GetTunnel(id string) (*Tunnel, error)
DeleteTunnel(id string) error
ListTunnels() ([]Tunnel, error)
@@ -74,15 +75,17 @@ func NewRESTClient(baseURL string, accountTag string, authToken string, logger l
}
type newTunnel struct {
Name string `json:"name"`
Name string `json:"name"`
TunnelSecret []byte `json:"tunnel_secret"`
}
func (r *RESTClient) CreateTunnel(name string) (*Tunnel, error) {
func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, error) {
if name == "" {
return nil, errors.New("tunnel name required")
}
body, err := json.Marshal(&newTunnel{
Name: name,
Name: name,
TunnelSecret: tunnelSecret,
})
if err != nil {
return nil, errors.Wrap(err, "Failed to serialize new tunnel request")
+45
View File
@@ -0,0 +1,45 @@
{
"product": "cloudflared",
"company": "cloudflare",
"license": "LICENSE",
"upgrade-code": "23f90fdd-9328-47ea-ab52-5380855a4b12",
"files": {
"guid": "35e5e858-9372-4449-bf73-1cd6f7267128",
"items": [
"cloudflared.exe"
]
},
"env": {
"guid": "6bb74449-d10d-4f4a-933e-6fc9fa006eae",
"vars": [
{
"name": "CFDPATH",
"value": "[INSTALLDIR].",
"permanent": "no",
"system": "yes",
"action": "set",
"part": "all"
}
]
},
"shortcuts": {},
"choco": {
"description": "cloudflared connects your machine or user identity to Cloudflare's global network.",
"project-url": "https://github.com/cloudflare/cloudflared",
"license-url": "https://github.com/cloudflare/cloudflared/blob/master/LICENSE"
},
"hooks": [
{
"command": "sc.exe create Cloudflared binPath=\"[INSTALLDIR]cloudflared.exe\" type=share start=auto DisplayName=\"Cloudflared\"",
"when": "install"
},
{
"command": "sc.exe start Cloudflared",
"when": "install"
},
{
"command": "sc.exe delete Cloudflared",
"when": "uninstall"
}
]
}