Merge branch 'master' into add_arm64_support
This commit is contained in:
commit
85c12594f7
|
@ -0,0 +1,8 @@
|
|||
images:
|
||||
- name: cloudflared
|
||||
dockerfile: Dockerfile
|
||||
context: .
|
||||
registries:
|
||||
- name: docker.io/michael9127
|
||||
user: env:DOCKER_USER
|
||||
password: env:DOCKER_PASSWORD
|
|
@ -9,6 +9,7 @@ guide/public
|
|||
\#*\#
|
||||
cscope.*
|
||||
cloudflared
|
||||
cloudflared.pkg
|
||||
cloudflared.exe
|
||||
!cmd/cloudflared/
|
||||
.DS_Store
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
|
||||
/usr/local/bin/cloudflared service uninstall
|
||||
rm /usr/local/bin/cloudflared
|
||||
pkgutil --forget com.cloudflare.cloudflared
|
|
@ -0,0 +1,21 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if [[ "$(uname)" != "Darwin" ]] ; then
|
||||
echo "This should be run on macOS"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
go version
|
||||
export GO111MODULE=on
|
||||
|
||||
# build 'cloudflared-darwin-amd64.tgz'
|
||||
mkdir -p artifacts
|
||||
FILENAME="$(pwd)/artifacts/cloudflared-darwin-amd64.tgz"
|
||||
export PATH="$PATH:/usr/local/bin"
|
||||
mkdir -p ../src/github.com/cloudflare/
|
||||
cp -r . ../src/github.com/cloudflare/cloudflared
|
||||
cd ../src/github.com/cloudflare/cloudflared
|
||||
GOCACHE="$PWD/../../../../" GOPATH="$PWD/../../../../" CGO_ENABLED=1 make cloudflared
|
||||
tar czf "$FILENAME" cloudflared
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
FILENAME="${PWD}/artifacts/cloudflared-darwin-amd64.tgz"
|
||||
|
||||
if ! VERSION="$(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 [[ ! -f "$FILENAME" ]] ; then
|
||||
echo "Missing $FILENAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${GITHUB_PRIVATE_KEY:-}" == "" ]] ; then
|
||||
echo "Missing GITHUB_PRIVATE_KEY"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# upload to s3 bucket for use by Homebrew formula
|
||||
s3cmd \
|
||||
--acl-public --signature-v2 --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \
|
||||
put "$FILENAME" "s3://cftunnel-docs/dl/cloudflared-$VERSION-darwin-amd64.tgz"
|
||||
s3cmd \
|
||||
--acl-public --signature-v2 --access_key="$AWS_ACCESS_KEY_ID" --secret_key="$AWS_SECRET_ACCESS_KEY" --host-bucket="%(bucket)s.s3.cfdata.org" \
|
||||
cp "s3://cftunnel-docs/dl/cloudflared-$VERSION-darwin-amd64.tgz" "s3://cftunnel-docs/dl/cloudflared-stable-darwin-amd64.tgz"
|
||||
SHA256=$(sha256sum "$FILENAME" | cut -b1-64)
|
||||
|
||||
# set up git (note that UserKnownHostsFile is an absolute path so we can cd wherever)
|
||||
mkdir -p tmp
|
||||
ssh-keyscan -t rsa github.com > tmp/github.txt
|
||||
echo "$GITHUB_PRIVATE_KEY" > tmp/private.key
|
||||
chmod 0400 tmp/private.key
|
||||
export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=$PWD/tmp/github.txt -i $PWD/tmp/private.key -o IdentitiesOnly=yes"
|
||||
|
||||
# clone Homebrew repo into tmp/homebrew-cloudflare
|
||||
git clone git@github.com:cloudflare/homebrew-cloudflare.git tmp/homebrew-cloudflare
|
||||
cd tmp/homebrew-cloudflare
|
||||
git checkout -f master
|
||||
git reset --hard origin/master
|
||||
|
||||
# modify cloudflared.rb
|
||||
URL="https://developers.cloudflare.com/argo-tunnel/dl/cloudflared-$VERSION-darwin-amd64.tgz"
|
||||
tee cloudflared.rb <<EOF
|
||||
class Cloudflared < Formula
|
||||
desc 'Argo Tunnel'
|
||||
homepage 'https://developers.cloudflare.com/argo-tunnel/'
|
||||
url '$URL'
|
||||
sha256 '$SHA256'
|
||||
version '$VERSION'
|
||||
def install
|
||||
bin.install 'cloudflared'
|
||||
end
|
||||
end
|
||||
EOF
|
||||
|
||||
# push cloudflared.rb
|
||||
git add cloudflared.rb
|
||||
git diff
|
||||
git config user.name "cloudflare-warp-bot"
|
||||
git config user.email "warp-bot@cloudflare.com"
|
||||
git commit -m "Release Argo Tunnel $VERSION"
|
||||
|
||||
#git push -v origin master
|
||||
# don't push to master, push to some testing branch
|
||||
git checkout -b deploy-test
|
||||
git push -v origin deploy-test
|
11
Dockerfile
11
Dockerfile
|
@ -1,8 +1,11 @@
|
|||
# use a builder image for building cloudflare
|
||||
ARG TARGET_GOOS
|
||||
ARG TARGET_GOARCH
|
||||
FROM golang:1.13.3 as builder
|
||||
ENV GO111MODULE=on
|
||||
ENV CGO_ENABLED=0
|
||||
ENV GOOS=linux
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
TARGET_GOOS=${TARGET_GOOS} \
|
||||
TARGET_GOARCH=${TARGET_GOARCH}
|
||||
|
||||
WORKDIR /go/src/github.com/cloudflare/cloudflared/
|
||||
|
||||
|
@ -12,8 +15,6 @@ COPY . .
|
|||
# compile cloudflared
|
||||
RUN make cloudflared
|
||||
|
||||
# ---
|
||||
|
||||
# use a distroless base image with glibc
|
||||
FROM gcr.io/distroless/base-debian10:nonroot
|
||||
|
||||
|
|
48
Makefile
48
Makefile
|
@ -7,17 +7,43 @@ PACKAGE_DIR := $(CURDIR)/packaging
|
|||
INSTALL_BINDIR := usr/local/bin
|
||||
|
||||
EQUINOX_FLAGS = --version="$(VERSION)" \
|
||||
--platforms="$(EQUINOX_BUILD_PLATFORMS)" \
|
||||
--app="$(EQUINOX_APP_ID)" \
|
||||
--token="$(EQUINOX_TOKEN)" \
|
||||
--channel="$(EQUINOX_CHANNEL)"
|
||||
--platforms="$(EQUINOX_BUILD_PLATFORMS)" \
|
||||
--app="$(EQUINOX_APP_ID)" \
|
||||
--token="$(EQUINOX_TOKEN)" \
|
||||
--channel="$(EQUINOX_CHANNEL)"
|
||||
|
||||
ifeq ($(EQUINOX_IS_DRAFT), true)
|
||||
EQUINOX_FLAGS := --draft $(EQUINOX_FLAGS)
|
||||
endif
|
||||
|
||||
ifeq ($(GOARCH),)
|
||||
GOARCH := amd64
|
||||
LOCAL_ARCH ?= $(shell uname -m)
|
||||
ifeq ($(LOCAL_ARCH),x86_64)
|
||||
TARGET_ARCH ?= amd64
|
||||
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 5),armv8)
|
||||
TARGET_ARCH ?= arm64
|
||||
else ifeq ($(LOCAL_ARCH),aarch64)
|
||||
TARGET_ARCH ?= arm64
|
||||
else ifeq ($(shell echo $(LOCAL_ARCH) | head -c 4),armv)
|
||||
TARGET_ARCH ?= arm
|
||||
else
|
||||
$(error This system's architecture $(LOCAL_ARCH) isn't supported)
|
||||
endif
|
||||
|
||||
LOCAL_OS ?= $(shell go env GOOS)
|
||||
ifeq ($(LOCAL_OS),linux)
|
||||
TARGET_OS ?= linux
|
||||
else ifeq ($(LOCAL_OS),darwin)
|
||||
TARGET_OS ?= darwin
|
||||
else ifeq ($(LOCAL_OS),windows)
|
||||
TARGET_OS ?= windows
|
||||
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
|
||||
|
@ -29,11 +55,11 @@ clean:
|
|||
|
||||
.PHONY: cloudflared
|
||||
cloudflared: tunnel-deps
|
||||
go build -v -mod=vendor $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/cloudflared
|
||||
GOOS=$(TARGET_OS) GOARCH=$(TARGET_ARCH) go build -v -mod=vendor $(VERSION_FLAGS) $(IMPORT_PATH)/cmd/cloudflared
|
||||
|
||||
.PHONY: container
|
||||
container:
|
||||
docker build -t cloudflare/cloudflared:"$(VERSION)" .
|
||||
docker build --build-arg=TARGET_ARCH=$(TARGET_ARCH) --build-arg=TARGET_OS=$(TARGET_OS) -t cloudflare/cloudflared-$(TARGET_OS)-$(TARGET_ARCH):"$(VERSION)" .
|
||||
|
||||
.PHONY: test
|
||||
test: vet
|
||||
|
@ -48,7 +74,7 @@ cloudflared-deb: cloudflared
|
|||
mkdir -p $(PACKAGE_DIR)
|
||||
cp cloudflared $(PACKAGE_DIR)/cloudflared
|
||||
fakeroot fpm -C $(PACKAGE_DIR) -s dir -t deb --deb-compression bzip2 \
|
||||
-a $(GOARCH) -v $(VERSION) -n cloudflared cloudflared=/usr/local/bin/
|
||||
-a $(TARGET_ARCH) -v $(VERSION) -n cloudflared cloudflared=/usr/local/bin/
|
||||
|
||||
.PHONY: cloudflared-darwin-amd64.tgz
|
||||
cloudflared-darwin-amd64.tgz: cloudflared
|
||||
|
@ -68,6 +94,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/
|
||||
|
|
109
RELEASE_NOTES
109
RELEASE_NOTES
|
@ -1,3 +1,112 @@
|
|||
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
|
||||
- 2020-06-12 TUN-3090: Upgrade crypto dep
|
||||
- 2020-06-11 TUN-3038: Add connections to tunnel list table
|
||||
- 2020-06-12 AUTH-2810 added warn for backwards compatibility sake
|
||||
|
||||
2020.6.1
|
||||
- 2020-06-09 AUTH-2796 fixed windows build
|
||||
|
||||
2020.6.0
|
||||
- 2020-06-05 AUTH-2645 protect against user mistaken flag input
|
||||
- 2020-06-05 AUTH-2687 don't copy config unnecessarily
|
||||
- 2020-06-05 AUTH-2169 make access login page more generic
|
||||
- 2020-06-05 AUTH-2729 added log file and level to cmd flags to match config file settings
|
||||
- 2020-06-08 AUTH-2785 service token flag fix and logger fix
|
||||
- 2020-05-20 AUTH-2682: Create buster build
|
||||
- 2020-05-21 TUN-2928, TUN-2929, TUN-2930: Add tunnel subcommands to interact with tunnel store service
|
||||
- 2020-05-29 Adding support for multi-architecture images and binaries (#184)
|
||||
- 2020-05-29 TUN-3019: Remove declarative tunnel entry code
|
||||
- 2020-05-29 TUN-3020: Remove declarative tunnel related RPC code
|
||||
- 2020-05-13 AUTH-2505 added aliases
|
||||
- 2020-05-14 AUTH-2529 added deprecation text to db-connect command
|
||||
- 2020-05-18 AUTH-2686: Added error handling to tunnel subcommand
|
||||
- 2020-05-04 AUTH-2369: RDP Bastion prototype
|
||||
- 2020-04-29 AUTH-2596 added new logger package and replaced logrus
|
||||
- 2020-04-25 DEVTOOLS-7321: Use SSH key from env for pushing to GitHub
|
||||
- 2020-04-25 DEVTOOLS-7321: Push to a test branch instead of to master
|
||||
- 2020-03-30 DEVTOOLS-7321: Add scripts for macOS builds and homebrew uploads
|
||||
|
||||
2020.5.1
|
||||
- 2020-05-07 TUN-2860: Enable quick reconnect feature by default
|
||||
- 2020-05-07 AUTH-2564: error handling and minor fixes
|
||||
- 2020-05-01 AUTH-2588 add DoH to service mode
|
||||
|
||||
2020.5.0
|
||||
- 2020-05-01 TUN-2943: Copy certutil from edge into cloudflared
|
||||
- 2020-05-05 TUN-2955: Fix connection and goroutine leaks when tunnel conection is terminated on error. Only unregister tunnels that had connected successfully. Close edge connection used to unregister the tunnel. Use buffered channels for error channels where receiver may quit early on context cancellation.
|
||||
- 2020-04-30 TUN-2940: Added delay parameter to stdin reconnect command.
|
||||
- 2020-04-27 TUN-2921: Rework address selection logic to avoid corner cases
|
||||
- 2020-04-28 TUN-2872: Exit with non-0 status code when the binary is updated so launchd will restart the service
|
||||
- 2020-04-13 AUTH-2587 add config watcher and reload logic for access client forwarder
|
||||
|
||||
2020.4.0
|
||||
- 2020-04-10 TUN-2881: Parameterize response meta information header name in the generating function
|
||||
- 2020-04-11 TUN-2894: ResponseMetaHeader should be public
|
||||
- 2020-04-09 TUN-2880: Return metadata about source of the response from cloudflared
|
||||
- 2020-04-04 ARES-899: Fixes DoH client as system resolver. Fixes #91
|
||||
- 2020-03-31 AUTH-2394 added socks5 proxy
|
||||
- 2020-02-24 AUTH-2235 GetTokenIfExists now parses JWT payload for json expiry field to detect if the cached access token is expired
|
||||
|
||||
2020.3.2
|
||||
- 2020-03-31 TUN-2854: Quick Reconnects should be an optional supported feature
|
||||
- 2020-03-30 TUN-2850: Tunnel stripping Cloudflare headers
|
||||
|
||||
2020.3.1
|
||||
- 2020-03-27 TUN-2846: Trigger debug reconnects from stdin commands, not SIGUSR1
|
||||
|
||||
2020.3.0
|
||||
- 2020-03-23 AUTH-2394 fixed header for websockets. Added TCP alias
|
||||
- 2020-03-10 TUN-2797: Fix panic in SetConnDigest by making mutexes values.
|
||||
- 2020-03-13 TUN-2807: cloudflared hello-world shouldn't assume it's my first tunnel
|
||||
- 2020-03-13 TUN-2756: Set connection digest after reconnect.
|
||||
- 2020-03-16 TUN-2812: Tunnel proxies and RPCs can share an edge address
|
||||
- 2020-03-18 TUN-2816: cloudflared metrics server should be more discoverable
|
||||
- 2020-03-19 TUN-2820: Serialized headers for Websockets
|
||||
- 2020-03-19 TUN-2819: cloudflared should close its connections when a signal is sent
|
||||
- 2020-03-19 TUN-2823: Bugfix. cloudflared would hang forever if error occurred.
|
||||
- 2020-03-10 TUN-2796: Implement HTTP2 CONTINUATION headers correctly
|
||||
- 2020-03-02 TUN-2779: update sample HTML pages
|
||||
- 2020-03-04 TUN-2785: Use reconnect token by default
|
||||
- 2020-03-05 TUN-2754: Add ConnDigest to cloudflared and its RPCs
|
||||
- 2020-03-06 TUN-2755: ReconnectTunnel RPC now transmits ConnectionDigest
|
||||
- 2020-03-06 TUN-2761: Use the new header management functions in cloudflared
|
||||
- 2020-03-06 TUN-2788: cloudflared should store one ConnDigest per HA connection
|
||||
- 2020-02-26 TUN-2767: Test for large headers
|
||||
- 2020-02-28 do not terminate tunnel if origin is not reachable on start-up (#177)
|
||||
- 2020-02-28 TUN-2776: Add header serialization feature in cloudflared
|
||||
- 2020-02-21 TUN-2748: Insecure randomness vulnerability in github.com/miekg/dns
|
||||
|
||||
2020.2.1
|
||||
- 2020-02-20 TUN-2745: Rename existing header management functions
|
||||
- 2020-02-21 TUN-2746: Add the new header management functions
|
||||
- 2020-02-25 perf(cloudflared): reuse memory from buffer pool to get better throughput (#161)
|
||||
- 2020-02-25 Tweak HTTP host header. Fixes #107 (#168)
|
||||
- 2020-02-25 TUN-2765: Add list of features to tunnelrpc
|
||||
- 2020-02-19 TUN-2725: Specify in code that --edge is for internal testing only
|
||||
- 2020-02-19 TUN-2703: Muxer.Serve terminates when its context is Done
|
||||
- 2020-02-09 TUN-2717: Function to serialize/deserialize HTTP headers
|
||||
- 2020-02-05 TUN-2714: New edge discovery. Connections try to reconnect to the same edge IP.
|
||||
|
||||
2020.2.0
|
||||
- 2020-01-30 TUN-2651: Fix panic in h2mux reader when a stream error is encountered
|
||||
- 2020-01-27 TUN-2645: Revert "TUN-2645: Turn on reconnect tokens"
|
||||
|
|
|
@ -5,12 +5,12 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
// DirectoryUploadManager is used to manage file uploads on an interval from a directory
|
||||
type DirectoryUploadManager struct {
|
||||
logger *logrus.Logger
|
||||
logger logger.Service
|
||||
uploader Uploader
|
||||
rootDirectory string
|
||||
sweepInterval time.Duration
|
||||
|
@ -23,7 +23,7 @@ type DirectoryUploadManager struct {
|
|||
// uploader is an Uploader to use as an actual uploading engine
|
||||
// directory is the directory to sweep for files to upload
|
||||
// sweepInterval is how often to iterate the directory and upload the files within
|
||||
func NewDirectoryUploadManager(logger *logrus.Logger, uploader Uploader, directory string, sweepInterval time.Duration, shutdownC chan struct{}) *DirectoryUploadManager {
|
||||
func NewDirectoryUploadManager(logger logger.Service, uploader Uploader, directory string, sweepInterval time.Duration, shutdownC chan struct{}) *DirectoryUploadManager {
|
||||
workerCount := 10
|
||||
manager := &DirectoryUploadManager{
|
||||
logger: logger,
|
||||
|
@ -97,7 +97,7 @@ func (m *DirectoryUploadManager) worker() {
|
|||
return
|
||||
case filepath := <-m.workQueue:
|
||||
if err := m.Upload(filepath); err != nil {
|
||||
m.logger.WithError(err).Error("Cannot upload file to s3 bucket")
|
||||
m.logger.Errorf("Cannot upload file to s3 bucket: %s", err)
|
||||
} else {
|
||||
os.Remove(filepath)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
type MockUploader struct {
|
||||
|
@ -49,7 +49,7 @@ func setupTestDirectory(t *testing.T) string {
|
|||
func createUploadManager(t *testing.T, shouldFailUpload bool) *DirectoryUploadManager {
|
||||
rootDirectory := setupTestDirectory(t)
|
||||
uploader := NewMockUploader(shouldFailUpload)
|
||||
logger := logrus.New()
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
shutdownC := make(chan struct{})
|
||||
return NewDirectoryUploadManager(logger, uploader, rootDirectory, 1*time.Second, shutdownC)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
package buffer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Pool struct {
|
||||
// A Pool must not be copied after first use.
|
||||
// https://golang.org/pkg/sync/#Pool
|
||||
buffers sync.Pool
|
||||
}
|
||||
|
||||
func NewPool(bufferSize int) *Pool {
|
||||
return &Pool{
|
||||
buffers: sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, bufferSize)
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pool) Get() []byte {
|
||||
return p.buffers.Get().([]byte)
|
||||
}
|
||||
|
||||
func (p *Pool) Put(buf []byte) {
|
||||
p.buffers.Put(buf)
|
||||
}
|
|
@ -11,9 +11,9 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
|
||||
cloudflaredWebsocket "github.com/cloudflare/cloudflared/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type StartOptions struct {
|
||||
|
@ -21,6 +21,15 @@ type StartOptions struct {
|
|||
Headers http.Header
|
||||
}
|
||||
|
||||
// Connection wraps up all the needed functions to forward over the tunnel
|
||||
type Connection interface {
|
||||
// ServeStream is used to forward data from the client to the edge
|
||||
ServeStream(*StartOptions, io.ReadWriter) error
|
||||
|
||||
// StartServer is used to listen for incoming connections from the edge to the origin
|
||||
StartServer(net.Listener, string, <-chan struct{}) error
|
||||
}
|
||||
|
||||
// StdinoutStream is empty struct for wrapping stdin/stdout
|
||||
// into a single ReadWriter
|
||||
type StdinoutStream struct {
|
||||
|
@ -44,86 +53,57 @@ func closeRespBody(resp *http.Response) {
|
|||
}
|
||||
}
|
||||
|
||||
// StartClient will copy the data from stdin/stdout over a WebSocket connection
|
||||
// to the edge (originURL)
|
||||
func StartClient(logger *logrus.Logger, stream io.ReadWriter, options *StartOptions) error {
|
||||
return serveStream(logger, stream, options)
|
||||
}
|
||||
|
||||
// StartServer will setup a listener on a specified address/port and then
|
||||
// StartForwarder will setup a listener on a specified address/port and then
|
||||
// forward connections to the origin by calling `Serve()`.
|
||||
func StartServer(logger *logrus.Logger, address string, shutdownC <-chan struct{}, options *StartOptions) error {
|
||||
func StartForwarder(conn Connection, address string, shutdownC <-chan struct{}, options *StartOptions) error {
|
||||
listener, err := net.Listen("tcp", address)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("failed to start forwarding server")
|
||||
return err
|
||||
return errors.Wrap(err, "failed to start forwarding server")
|
||||
}
|
||||
logger.Info("Started listening on ", address)
|
||||
return Serve(logger, listener, shutdownC, options)
|
||||
return Serve(conn, listener, shutdownC, options)
|
||||
}
|
||||
|
||||
// StartClient will copy the data from stdin/stdout over a WebSocket connection
|
||||
// to the edge (originURL)
|
||||
func StartClient(conn Connection, stream io.ReadWriter, options *StartOptions) error {
|
||||
return conn.ServeStream(options, stream)
|
||||
}
|
||||
|
||||
// Serve accepts incoming connections on the specified net.Listener.
|
||||
// Each connection is handled in a new goroutine: its data is copied over a
|
||||
// WebSocket connection to the edge (originURL).
|
||||
// `Serve` always closes `listener`.
|
||||
func Serve(logger *logrus.Logger, listener net.Listener, shutdownC <-chan struct{}, options *StartOptions) error {
|
||||
func Serve(remoteConn Connection, listener net.Listener, shutdownC <-chan struct{}, options *StartOptions) error {
|
||||
defer listener.Close()
|
||||
for {
|
||||
select {
|
||||
case <-shutdownC:
|
||||
return nil
|
||||
default:
|
||||
errChan := make(chan error)
|
||||
|
||||
go func() {
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
return err
|
||||
// don't block if parent goroutine quit early
|
||||
select {
|
||||
case errChan <- err:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
go serveConnection(logger, conn, options)
|
||||
go serveConnection(remoteConn, conn, options)
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-shutdownC:
|
||||
return nil
|
||||
case err := <-errChan:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// serveConnection handles connections for the Serve() call
|
||||
func serveConnection(logger *logrus.Logger, c net.Conn, options *StartOptions) {
|
||||
func serveConnection(remoteConn Connection, c net.Conn, options *StartOptions) {
|
||||
defer c.Close()
|
||||
serveStream(logger, c, options)
|
||||
}
|
||||
|
||||
// serveStream will serve the data over the WebSocket stream
|
||||
func serveStream(logger *logrus.Logger, conn io.ReadWriter, options *StartOptions) error {
|
||||
wsConn, err := createWebsocketStream(options)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("failed to connect to %s\n", options.OriginURL)
|
||||
return err
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
cloudflaredWebsocket.Stream(wsConn, conn)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createWebsocketStream will create a WebSocket connection to stream data over
|
||||
// It also handles redirects from Access and will present that flow if
|
||||
// the token is not present on the request
|
||||
func createWebsocketStream(options *StartOptions) (*cloudflaredWebsocket.Conn, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = options.Headers
|
||||
|
||||
wsConn, resp, err := cloudflaredWebsocket.ClientConnect(req, nil)
|
||||
defer closeRespBody(resp)
|
||||
if err != nil && IsAccessResponse(resp) {
|
||||
wsConn, err = createAccessAuthenticatedStream(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cloudflaredWebsocket.Conn{Conn: wsConn}, nil
|
||||
remoteConn.ServeStream(options, c)
|
||||
}
|
||||
|
||||
// IsAccessResponse checks the http Response to see if the url location
|
||||
|
@ -144,56 +124,14 @@ func IsAccessResponse(resp *http.Response) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// createAccessAuthenticatedStream will try load a token from storage and make
|
||||
// a connection with the token set on the request. If it still get redirect,
|
||||
// this probably means the token in storage is invalid (expired/revoked). If that
|
||||
// happens it deletes the token and runs the connection again, so the user can
|
||||
// login again and generate a new one.
|
||||
func createAccessAuthenticatedStream(options *StartOptions) (*websocket.Conn, error) {
|
||||
wsConn, resp, err := createAccessWebSocketStream(options)
|
||||
defer closeRespBody(resp)
|
||||
if err == nil {
|
||||
return wsConn, nil
|
||||
}
|
||||
|
||||
if !IsAccessResponse(resp) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Access Token is invalid for some reason. Go through regen flow
|
||||
originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := token.RemoveTokenIfExists(originReq.URL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wsConn, resp, err = createAccessWebSocketStream(options)
|
||||
defer closeRespBody(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wsConn, nil
|
||||
}
|
||||
|
||||
// createAccessWebSocketStream builds an Access request and makes a connection
|
||||
func createAccessWebSocketStream(options *StartOptions) (*websocket.Conn, *http.Response, error) {
|
||||
req, err := BuildAccessRequest(options)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return cloudflaredWebsocket.ClientConnect(req, nil)
|
||||
}
|
||||
|
||||
// buildAccessRequest builds an HTTP request with the Access token set
|
||||
func BuildAccessRequest(options *StartOptions) (*http.Request, error) {
|
||||
// BuildAccessRequest builds an HTTP request with the Access token set
|
||||
func BuildAccessRequest(options *StartOptions, logger logger.Service) (*http.Request, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := token.FetchToken(req.URL)
|
||||
token, err := token.FetchTokenWithRedirect(req.URL, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -204,7 +142,7 @@ func BuildAccessRequest(options *StartOptions) (*http.Request, error) {
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
originRequest.Header.Set("cf-access-token", token)
|
||||
originRequest.Header.Set(h2mux.CFAccessTokenHeader, token)
|
||||
|
||||
for k, v := range options.Headers {
|
||||
if len(v) >= 1 {
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
ws "github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -43,7 +43,8 @@ func (s *testStreamer) Write(p []byte) (int, error) {
|
|||
|
||||
func TestStartClient(t *testing.T) {
|
||||
message := "Good morning Austin! Time for another sunny day in the great state of Texas."
|
||||
logger := logrus.New()
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
wsConn := NewWSConnection(logger, false)
|
||||
ts := newTestWebSocketServer()
|
||||
defer ts.Close()
|
||||
|
||||
|
@ -52,7 +53,7 @@ func TestStartClient(t *testing.T) {
|
|||
OriginURL: "http://" + ts.Listener.Addr().String(),
|
||||
Headers: nil,
|
||||
}
|
||||
err := StartClient(logger, buf, options)
|
||||
err := StartClient(wsConn, buf, options)
|
||||
assert.NoError(t, err)
|
||||
buf.Write([]byte(message))
|
||||
|
||||
|
@ -67,8 +68,9 @@ func TestStartServer(t *testing.T) {
|
|||
t.Fatalf("Error starting listener: %v", err)
|
||||
}
|
||||
message := "Good morning Austin! Time for another sunny day in the great state of Texas."
|
||||
logger := logrus.New()
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
shutdownC := make(chan struct{})
|
||||
wsConn := NewWSConnection(logger, false)
|
||||
ts := newTestWebSocketServer()
|
||||
defer ts.Close()
|
||||
options := &StartOptions{
|
||||
|
@ -77,7 +79,7 @@ func TestStartServer(t *testing.T) {
|
|||
}
|
||||
|
||||
go func() {
|
||||
err := Serve(logger, listener, shutdownC, options)
|
||||
err := Serve(wsConn, listener, shutdownC, options)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running server: %v", err)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
package carrier
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/socks"
|
||||
cfwebsocket "github.com/cloudflare/cloudflared/websocket"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// Websocket is used to carry data via WS binary frames over the tunnel from client to the origin
|
||||
// This implements the functions for glider proxy (sock5) and the carrier interface
|
||||
type Websocket struct {
|
||||
logger logger.Service
|
||||
isSocks bool
|
||||
}
|
||||
|
||||
type wsdialer struct {
|
||||
conn *cfwebsocket.Conn
|
||||
}
|
||||
|
||||
func (d *wsdialer) Dial(address string) (io.ReadWriteCloser, *socks.AddrSpec, error) {
|
||||
local, ok := d.conn.LocalAddr().(*net.TCPAddr)
|
||||
if !ok {
|
||||
return nil, nil, fmt.Errorf("not a tcp connection")
|
||||
}
|
||||
|
||||
addr := socks.AddrSpec{IP: local.IP, Port: local.Port}
|
||||
return d.conn, &addr, nil
|
||||
}
|
||||
|
||||
// NewWSConnection returns a new connection object
|
||||
func NewWSConnection(logger logger.Service, isSocks bool) Connection {
|
||||
return &Websocket{
|
||||
logger: logger,
|
||||
isSocks: isSocks,
|
||||
}
|
||||
}
|
||||
|
||||
// ServeStream will create a Websocket client stream connection to the edge
|
||||
// it blocks and writes the raw data from conn over the tunnel
|
||||
func (ws *Websocket) ServeStream(options *StartOptions, conn io.ReadWriter) error {
|
||||
wsConn, err := createWebsocketStream(options, ws.logger)
|
||||
if err != nil {
|
||||
ws.logger.Errorf("failed to connect to %s with error: %s", options.OriginURL, err)
|
||||
return err
|
||||
}
|
||||
defer wsConn.Close()
|
||||
|
||||
if ws.isSocks {
|
||||
dialer := &wsdialer{conn: wsConn}
|
||||
requestHandler := socks.NewRequestHandler(dialer)
|
||||
socksServer := socks.NewConnectionHandler(requestHandler)
|
||||
|
||||
socksServer.Serve(conn)
|
||||
} else {
|
||||
cfwebsocket.Stream(wsConn, conn)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartServer creates a Websocket server to listen for connections.
|
||||
// This is used on the origin (tunnel) side to take data from the muxer and send it to the origin
|
||||
func (ws *Websocket) StartServer(listener net.Listener, remote string, shutdownC <-chan struct{}) error {
|
||||
return cfwebsocket.StartProxyServer(ws.logger, listener, remote, shutdownC, cfwebsocket.DefaultStreamHandler)
|
||||
}
|
||||
|
||||
// createWebsocketStream will create a WebSocket connection to stream data over
|
||||
// It also handles redirects from Access and will present that flow if
|
||||
// the token is not present on the request
|
||||
func createWebsocketStream(options *StartOptions, logger logger.Service) (*cfwebsocket.Conn, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.Header = options.Headers
|
||||
|
||||
dump, err := httputil.DumpRequest(req, false)
|
||||
logger.Debugf("Websocket request: %s", string(dump))
|
||||
|
||||
wsConn, resp, err := cfwebsocket.ClientConnect(req, nil)
|
||||
defer closeRespBody(resp)
|
||||
if err != nil && IsAccessResponse(resp) {
|
||||
wsConn, err = createAccessAuthenticatedStream(options, logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfwebsocket.Conn{Conn: wsConn}, nil
|
||||
}
|
||||
|
||||
// createAccessAuthenticatedStream will try load a token from storage and make
|
||||
// a connection with the token set on the request. If it still get redirect,
|
||||
// this probably means the token in storage is invalid (expired/revoked). If that
|
||||
// happens it deletes the token and runs the connection again, so the user can
|
||||
// login again and generate a new one.
|
||||
func createAccessAuthenticatedStream(options *StartOptions, logger logger.Service) (*websocket.Conn, error) {
|
||||
wsConn, resp, err := createAccessWebSocketStream(options, logger)
|
||||
defer closeRespBody(resp)
|
||||
if err == nil {
|
||||
return wsConn, nil
|
||||
}
|
||||
|
||||
if !IsAccessResponse(resp) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Access Token is invalid for some reason. Go through regen flow
|
||||
originReq, err := http.NewRequest(http.MethodGet, options.OriginURL, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := token.RemoveTokenIfExists(originReq.URL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wsConn, resp, err = createAccessWebSocketStream(options, logger)
|
||||
defer closeRespBody(resp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return wsConn, nil
|
||||
}
|
||||
|
||||
// createAccessWebSocketStream builds an Access request and makes a connection
|
||||
func createAccessWebSocketStream(options *StartOptions, logger logger.Service) (*websocket.Conn, *http.Response, error) {
|
||||
req, err := BuildAccessRequest(options, logger)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
dump, err := httputil.DumpRequest(req, false)
|
||||
logger.Debugf("Access Websocket request: %s", string(dump))
|
||||
|
||||
return cfwebsocket.ClientConnect(req, nil)
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
package certutil
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type namedTunnelToken struct {
|
||||
ZoneID string `json:"zoneID"`
|
||||
AccountID string `json:"accountID"`
|
||||
ServiceKey string `json:"serviceKey"`
|
||||
}
|
||||
|
||||
type OriginCert struct {
|
||||
PrivateKey interface{}
|
||||
Cert *x509.Certificate
|
||||
ZoneID string
|
||||
ServiceKey string
|
||||
AccountID string
|
||||
}
|
||||
|
||||
func DecodeOriginCert(blocks []byte) (*OriginCert, error) {
|
||||
if len(blocks) == 0 {
|
||||
return nil, fmt.Errorf("Cannot decode empty certificate")
|
||||
}
|
||||
originCert := OriginCert{}
|
||||
block, rest := pem.Decode(blocks)
|
||||
for {
|
||||
if block == nil {
|
||||
break
|
||||
}
|
||||
switch block.Type {
|
||||
case "PRIVATE KEY":
|
||||
if originCert.PrivateKey != nil {
|
||||
return nil, fmt.Errorf("Found multiple private key in the certificate")
|
||||
}
|
||||
// RSA private key
|
||||
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse private key")
|
||||
}
|
||||
originCert.PrivateKey = privateKey
|
||||
case "CERTIFICATE":
|
||||
if originCert.Cert != nil {
|
||||
return nil, fmt.Errorf("Found multiple certificates in the certificate")
|
||||
}
|
||||
cert, err := x509.ParseCertificates(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Cannot parse certificate")
|
||||
} else if len(cert) > 1 {
|
||||
return nil, fmt.Errorf("Found multiple certificates in the certificate")
|
||||
}
|
||||
originCert.Cert = cert[0]
|
||||
case "WARP TOKEN", "ARGO TUNNEL TOKEN":
|
||||
if originCert.ZoneID != "" || originCert.ServiceKey != "" {
|
||||
return nil, fmt.Errorf("Found multiple tokens in the certificate")
|
||||
}
|
||||
// The token is a string,
|
||||
// Try the newer JSON format
|
||||
ntt := namedTunnelToken{}
|
||||
if err := json.Unmarshal(block.Bytes, &ntt); err == nil {
|
||||
originCert.ZoneID = ntt.ZoneID
|
||||
originCert.ServiceKey = ntt.ServiceKey
|
||||
originCert.AccountID = ntt.AccountID
|
||||
} else {
|
||||
// Try the older format, where the zoneID and service key are seperated by
|
||||
// a new line character
|
||||
token := string(block.Bytes)
|
||||
s := strings.Split(token, "\n")
|
||||
if len(s) != 2 {
|
||||
return nil, fmt.Errorf("Cannot parse token")
|
||||
}
|
||||
originCert.ZoneID = s[0]
|
||||
originCert.ServiceKey = s[1]
|
||||
}
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown block %s in the certificate", block.Type)
|
||||
}
|
||||
block, rest = pem.Decode(rest)
|
||||
}
|
||||
|
||||
if originCert.PrivateKey == nil {
|
||||
return nil, fmt.Errorf("Missing private key in the certificate")
|
||||
} else if originCert.Cert == nil {
|
||||
return nil, fmt.Errorf("Missing certificate in the certificate")
|
||||
} else if originCert.ZoneID == "" || originCert.ServiceKey == "" {
|
||||
return nil, fmt.Errorf("Missing token in the certificate")
|
||||
}
|
||||
|
||||
return &originCert, nil
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
package certutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadOriginCert(t *testing.T) {
|
||||
cert, err := DecodeOriginCert([]byte{})
|
||||
assert.Equal(t, fmt.Errorf("Cannot decode empty certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err := ioutil.ReadFile("test-cert-no-key.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Equal(t, fmt.Errorf("Missing private key in the certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err = ioutil.ReadFile("test-cert-two-certificates.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Equal(t, fmt.Errorf("Found multiple certificates in the certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err = ioutil.ReadFile("test-cert-unknown-block.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Equal(t, fmt.Errorf("Unknown block RSA PRIVATE KEY in the certificate"), err)
|
||||
assert.Nil(t, cert)
|
||||
|
||||
blocks, err = ioutil.ReadFile("test-cert.pem")
|
||||
assert.Nil(t, err)
|
||||
cert, err = DecodeOriginCert(blocks)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cert)
|
||||
assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID)
|
||||
key := "v1.0-58bd4f9e28f7b3c28e05a35ff3e80ab4fd9644ef3fece537eb0d12e2e9258217-183442fbb0bbdb3e571558fec9b5589ebd77aafc87498ee3f09f64a4ad79ffe8791edbae08b36c1d8f1d70a8670de56922dff92b15d214a524f4ebfa1958859e-7ce80f79921312a6022c5d25e2d380f82ceaefe3fbdc43dd13b080e3ef1e26f7"
|
||||
assert.Equal(t, key, cert.ServiceKey)
|
||||
}
|
||||
|
||||
func TestNewlineArgoTunnelToken(t *testing.T) {
|
||||
ArgoTunnelTokenTest(t, "test-argo-tunnel-cert.pem")
|
||||
}
|
||||
|
||||
func TestJSONArgoTunnelToken(t *testing.T) {
|
||||
// The given cert's Argo Tunnel Token was generated by base64 encoding this JSON:
|
||||
// {
|
||||
// "zoneID": "7b0a4d77dfb881c1a3b7d61ea9443e19",
|
||||
// "serviceKey": "test-service-key",
|
||||
// "accountID": "abcdabcdabcdabcd1234567890abcdef"
|
||||
// }
|
||||
ArgoTunnelTokenTest(t, "test-argo-tunnel-cert-json.pem")
|
||||
}
|
||||
|
||||
func ArgoTunnelTokenTest(t *testing.T, path string) {
|
||||
blocks, err := ioutil.ReadFile(path)
|
||||
assert.Nil(t, err)
|
||||
cert, err := DecodeOriginCert(blocks)
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, cert)
|
||||
assert.Equal(t, "7b0a4d77dfb881c1a3b7d61ea9443e19", cert.ZoneID)
|
||||
key := "test-service-key"
|
||||
assert.Equal(t, key, cert.ServiceKey)
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN ARGO TUNNEL TOKEN-----
|
||||
eyJ6b25lSUQiOiAiN2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkiLCAi
|
||||
c2VydmljZUtleSI6ICJ0ZXN0LXNlcnZpY2Uta2V5IiwgImFjY291bnRJRCI6ICJh
|
||||
YmNkYWJjZGFiY2RhYmNkMTIzNDU2Nzg5MGFiY2RlZiJ9
|
||||
-----END ARGO TUNNEL TOKEN-----
|
|
@ -0,0 +1,56 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN ARGO TUNNEL TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdGVzdC1zZXJ2aWNlLWtl
|
||||
eQ==
|
||||
-----END ARGO TUNNEL TOKEN-----
|
|
@ -0,0 +1,33 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
|
@ -0,0 +1,85 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
|
@ -0,0 +1,89 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END RSA PRIVATE KEY-----
|
|
@ -0,0 +1,61 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCfGswL16Fz9Ei3
|
||||
sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng6yHR1H5oX1Lg
|
||||
1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bxtG0uyrXYh7Mt
|
||||
z0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyXPE6SuDvMHIeX
|
||||
6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZAzNOxVKrUsyS
|
||||
x7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOglHJ2n0sMcZ+Ja
|
||||
1Y649mPVAgMBAAECggEAEbPF0ah9fH0IzTU/CPbIeh3flyY8GDuMpR1HvwUurSWB
|
||||
IFI9bLyVAXKb8vYP1TMaTnXi5qmFof+/JShgyZc3+1tZtWTfoaiC8Y1bRfE2yk+D
|
||||
xmwddhDmijYGG7i8uEaeddSdFEh2GKAqkbV/QgBvN2Nl4EVmIOAJXXNe9l5LFyjy
|
||||
sR10aNVJRYV1FahrCTwZ3SovHP4d4AUvHh/3FFZDukHc37CFA0+CcR4uehp5yedi
|
||||
2UdqaszXqunFo/3h+Tn9dW2C7gTTZx4+mfyaws3p3YOmdYArXvpejxHIc0FGwLBm
|
||||
sb9K7wGVUiF0Bt0ch+C1mdYrCaFNHnPuDswjmm3FwQKBgQDYtxOwwSLA6ZyppozX
|
||||
Doyx9a7PhiMHCFKSdVB4l8rpK545a+AmpG6LRScTtBsMTHBhT3IQ3QPWlVm1AhjF
|
||||
AvXMa1rOeaGbCbDn1xqEoEVPtj4tys8eTfyWmtU73jWTFauOt4/xpf/urEpg91xj
|
||||
m+Gl/8qgBrpm5rQxV5Y4MysRlQKBgQC78jzzlhocXGNvw0wT/K2NsknyeoZXqpIE
|
||||
QYL60FMl4geZn6w9hwxaL1r+g/tUjTnpBPQtS1r2Ed2gXby5zspN1g/PW8U3t3to
|
||||
P7zHIJ/sLBXrCh5RJko3hUgGhDNOOCIQj4IaKUfvHYvEIbIxlyI0vdsXsgXgMuQ8
|
||||
pb9Yifn5QQKBgQCmGu0EtYQlyOlDP10EGSrN3Dm45l9CrKZdi326cN4eCkikSoLs
|
||||
G2x/YumouItiydP5QiNzuXOPrbmse4bwumwb2s0nJSMw6iSmDsFMlmuJxW2zO5e0
|
||||
6qGH7fUyhgcaTanJIfk6hrm7/mKkH/S4hGpYCc8NCRsmc/35M+D4AoAoYQKBgQC0
|
||||
LWpZaxDlF30MbAHHN3l6We2iU+vup0sMYXGb2ZOcwa/fir+ozIr++l8VmJmdWTan
|
||||
OWSM96zgMghx8Os4hhJTxF+rvqK242OfcVsc2x31X94zUaP2z+peh5uhA6Pb3Nxr
|
||||
W+iyA9k+Vujiwhr+h5D3VvtvH++aG6/KpGtoCf5nAQKBgQDXX2+d7bd5CLNLLFNd
|
||||
M2i4QoOFcSKIG+v4SuvgEJHgG8vGvxh2qlSxnMWuPV+7/1P5ATLqDj1PlKms+BNR
|
||||
y7sc5AT9PclkL3Y9MNzOu0LXyBkGYcl8M0EQfLv9VPbWT+NXiMg/O2CHiT02pAAz
|
||||
uQicoQq3yzeQh20wtrtaXzTNmA==
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIID+jCCA6CgAwIBAgIUJhFxUKEGvTRc3CjCok6dbPGH/P4wCgYIKoZIzj0EAwIw
|
||||
gagxCzAJBgNVBAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMTgwNgYD
|
||||
VQQLEy9DbG91ZEZsYXJlIE9yaWdpbiBTU0wgRUNDIENlcnRpZmljYXRlIEF1dGhv
|
||||
cml0eTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZvcm5p
|
||||
YTEXMBUGA1UEAxMOKGRldiB1c2Ugb25seSkwHhcNMTcxMDEzMTM1OTAwWhcNMzIx
|
||||
MDA5MTM1OTAwWjBiMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMR0wGwYDVQQL
|
||||
ExRDbG91ZEZsYXJlIE9yaWdpbiBDQTEmMCQGA1UEAxMdQ2xvdWRGbGFyZSBPcmln
|
||||
aW4gQ2VydGlmaWNhdGUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCf
|
||||
GswL16Fz9Ei3sAg5AmBizoN2nZdyXHP8T57UxUMcrlJXEEXCVS5RR4m9l+EmK0ng
|
||||
6yHR1H5oX1Lg1WKyXgWwr0whwmdTD+qWFJW2M8HyefyBKLrsGPuxw4CVYT0h72bx
|
||||
tG0uyrXYh7Mtz0lHjGV90qrFpq5o0jx0sLbDlDvpFPbIO58uYzKG4Sn2VTC4rOyX
|
||||
PE6SuDvMHIeX6Ekw4wSVQ9eTbksLQqTyxSqM3zp2ygc56SjGjy1nGQT8ZBGFzSbZ
|
||||
AzNOxVKrUsySx7LzZVl+zCGCPlQwaYLKObKXadZJmrqSFmErC5jcbVgBz7oJQOgl
|
||||
HJ2n0sMcZ+Ja1Y649mPVAgMBAAGjggEgMIIBHDAOBgNVHQ8BAf8EBAMCBaAwEwYD
|
||||
VR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUzA6f2Ajq
|
||||
zhX67c6piY2a1uTiUkwwHwYDVR0jBBgwFoAU2qfBlqxKMZnf0QeTeYiMelfqJfgw
|
||||
RAYIKwYBBQUHAQEEODA2MDQGCCsGAQUFBzABhihodHRwOi8vb2NzcC5jbG91ZGZs
|
||||
YXJlLmNvbS9vcmlnaW5fZWNjX2NhMCMGA1UdEQQcMBqCDCouYXJub2xkLmNvbYIK
|
||||
YXJub2xkLmNvbTA8BgNVHR8ENTAzMDGgL6AthitodHRwOi8vY3JsLmNsb3VkZmxh
|
||||
cmUuY29tL29yaWdpbl9lY2NfY2EuY3JsMAoGCCqGSM49BAMCA0gAMEUCIDV7HoMj
|
||||
K5rShE/l+90YAOzHC89OH/wUz3I5KYOFuehoAiEA8e92aIf9XBkr0K6EvFCiSsD+
|
||||
x+Yo/cL8fGfVpPt4UM8=
|
||||
-----END CERTIFICATE-----
|
||||
-----BEGIN WARP TOKEN-----
|
||||
N2IwYTRkNzdkZmI4ODFjMWEzYjdkNjFlYTk0NDNlMTkKdjEuMC01OGJkNGY5ZTI4
|
||||
ZjdiM2MyOGUwNWEzNWZmM2U4MGFiNGZkOTY0NGVmM2ZlY2U1MzdlYjBkMTJlMmU5
|
||||
MjU4MjE3LTE4MzQ0MmZiYjBiYmRiM2U1NzE1NThmZWM5YjU1ODllYmQ3N2FhZmM4
|
||||
NzQ5OGVlM2YwOWY2NGE0YWQ3OWZmZTg3OTFlZGJhZTA4YjM2YzFkOGYxZDcwYTg2
|
||||
NzBkZTU2OTIyZGZmOTJiMTVkMjE0YTUyNGY0ZWJmYTE5NTg4NTllLTdjZTgwZjc5
|
||||
OTIxMzEyYTYwMjJjNWQyNWUyZDM4MGY4MmNlYWVmZTNmYmRjNDNkZDEzYjA4MGUz
|
||||
ZWYxZTI2Zjc=
|
||||
-----END WARP TOKEN-----
|
74
cfsetup.yaml
74
cfsetup.yaml
|
@ -1,4 +1,4 @@
|
|||
pinned_go: &pinned_go go=1.12.7-1
|
||||
pinned_go: &pinned_go go=1.12.9-1
|
||||
build_dir: &build_dir /cfsetup_build
|
||||
default-flavor: stretch
|
||||
stretch: &stretch
|
||||
|
@ -31,6 +31,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:
|
||||
|
@ -53,6 +65,20 @@ stretch: &stretch
|
|||
- export GOARCH=arm64
|
||||
- export CC=gcc-aarch64-linux-gnu
|
||||
- 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:
|
||||
|
@ -62,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:
|
||||
|
@ -72,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:
|
||||
|
@ -82,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:
|
||||
|
@ -94,9 +158,17 @@ stretch: &stretch
|
|||
- (cd / && go get github.com/BurntSushi/go-sumtype)
|
||||
- export PATH="$HOME/go/bin:$PATH"
|
||||
- make test
|
||||
update-homebrew:
|
||||
builddeps:
|
||||
- openssh-client
|
||||
- s3cmd
|
||||
post-cache:
|
||||
- .teamcity/update-homebrew.sh
|
||||
|
||||
jessie: *stretch
|
||||
|
||||
buster: *stretch
|
||||
|
||||
# cfsetup compose
|
||||
default-stack: test_dbconnect
|
||||
test_dbconnect:
|
||||
|
|
|
@ -6,17 +6,75 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflared/carrier"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
"github.com/pkg/errors"
|
||||
cli "gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
// StartForwarder starts a client side websocket forward
|
||||
func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, logger logger.Service) error {
|
||||
validURLString, err := validation.ValidateUrl(forwarder.Listener)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error validating origin URL")
|
||||
}
|
||||
|
||||
validURL, err := url.Parse(validURLString)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing origin URL")
|
||||
}
|
||||
|
||||
// get the headers from the config file and add to the request
|
||||
headers := make(http.Header)
|
||||
if forwarder.TokenClientID != "" {
|
||||
headers.Set(h2mux.CFAccessClientIDHeader, forwarder.TokenClientID)
|
||||
}
|
||||
|
||||
if forwarder.TokenSecret != "" {
|
||||
headers.Set(h2mux.CFAccessClientSecretHeader, forwarder.TokenSecret)
|
||||
}
|
||||
|
||||
if forwarder.Destination != "" {
|
||||
headers.Add(h2mux.CFJumpDestinationHeader, forwarder.Destination)
|
||||
}
|
||||
|
||||
options := &carrier.StartOptions{
|
||||
OriginURL: forwarder.URL,
|
||||
Headers: headers, //TODO: TUN-2688 support custom headers from config file
|
||||
}
|
||||
|
||||
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
|
||||
wsConn := carrier.NewWSConnection(logger, false)
|
||||
|
||||
logger.Infof("Start Websocket listener on: %s", validURL.Host)
|
||||
return carrier.StartForwarder(wsConn, validURL.Host, shutdown, options)
|
||||
}
|
||||
|
||||
// ssh will start a WS proxy server for server mode
|
||||
// or copy from stdin/stdout for client mode
|
||||
// useful for proxying other protocols (like ssh) over websockets
|
||||
// (which you can put Access in front of)
|
||||
func ssh(c *cli.Context) error {
|
||||
logDirectory, logLevel := config.FindLogSettings()
|
||||
|
||||
flagLogDirectory := c.String(sshLogDirectoryFlag)
|
||||
if flagLogDirectory != "" {
|
||||
logDirectory = flagLogDirectory
|
||||
}
|
||||
|
||||
flagLogLevel := c.String(sshLogLevelFlag)
|
||||
if flagLogLevel != "" {
|
||||
logLevel = flagLogLevel
|
||||
}
|
||||
|
||||
logger, err := logger.New(logger.DefaultFile(logDirectory), logger.LogLevelString(logLevel))
|
||||
if err != nil {
|
||||
return cliutil.PrintLoggerSetupError("error setting up logger", err)
|
||||
}
|
||||
|
||||
// get the hostname from the cmdline and error out if its not provided
|
||||
rawHostName := c.String(sshHostnameFlag)
|
||||
hostname, err := validation.ValidateHostname(rawHostName)
|
||||
|
@ -28,15 +86,15 @@ func ssh(c *cli.Context) error {
|
|||
// get the headers from the cmdline and add them
|
||||
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
|
||||
if c.IsSet(sshTokenIDFlag) {
|
||||
headers.Add("CF-Access-Client-Id", c.String(sshTokenIDFlag))
|
||||
headers.Set(h2mux.CFAccessClientIDHeader, c.String(sshTokenIDFlag))
|
||||
}
|
||||
if c.IsSet(sshTokenSecretFlag) {
|
||||
headers.Add("CF-Access-Client-Secret", c.String(sshTokenSecretFlag))
|
||||
headers.Set(h2mux.CFAccessClientSecretHeader, c.String(sshTokenSecretFlag))
|
||||
}
|
||||
|
||||
destination := c.String(sshDestinationFlag)
|
||||
if destination != "" {
|
||||
headers.Add("CF-Access-SSH-Destination", destination)
|
||||
headers.Add(h2mux.CFJumpDestinationHeader, destination)
|
||||
}
|
||||
|
||||
options := &carrier.StartOptions{
|
||||
|
@ -44,21 +102,30 @@ func ssh(c *cli.Context) error {
|
|||
Headers: headers,
|
||||
}
|
||||
|
||||
// we could add a cmd line variable for this bool if we want the SOCK5 server to be on the client side
|
||||
wsConn := carrier.NewWSConnection(logger, false)
|
||||
|
||||
if c.NArg() > 0 || c.IsSet(sshURLFlag) {
|
||||
localForwarder, err := config.ValidateUrl(c)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Error validating origin URL")
|
||||
logger.Errorf("Error validating origin URL: %s", err)
|
||||
return errors.Wrap(err, "error validating origin URL")
|
||||
}
|
||||
forwarder, err := url.Parse(localForwarder)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Error validating origin URL")
|
||||
logger.Errorf("Error validating origin URL: %s", err)
|
||||
return errors.Wrap(err, "error validating origin URL")
|
||||
}
|
||||
return carrier.StartServer(logger, forwarder.Host, shutdownC, options)
|
||||
|
||||
logger.Infof("Start Websocket listener on: %s", forwarder.Host)
|
||||
err = carrier.StartForwarder(wsConn, forwarder.Host, shutdownC, options)
|
||||
if err != nil {
|
||||
logger.Errorf("Error on Websocket listener: %s", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return carrier.StartClient(logger, &carrier.StdinoutStream{}, options)
|
||||
return carrier.StartClient(wsConn, &carrier.StdinoutStream{}, options)
|
||||
}
|
||||
|
||||
func buildRequestHeaders(values []string) http.Header {
|
||||
|
|
|
@ -10,27 +10,31 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/carrier"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/token"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/sshgen"
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/net/idna"
|
||||
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
raven "github.com/getsentry/raven-go"
|
||||
cli "gopkg.in/urfave/cli.v2"
|
||||
"github.com/getsentry/raven-go"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
sshHostnameFlag = "hostname"
|
||||
sshDestinationFlag = "destination"
|
||||
sshURLFlag = "url"
|
||||
sshHeaderFlag = "header"
|
||||
sshTokenIDFlag = "service-token-id"
|
||||
sshTokenSecretFlag = "service-token-secret"
|
||||
sshGenCertFlag = "short-lived-cert"
|
||||
sshConfigTemplate = `
|
||||
sshHostnameFlag = "hostname"
|
||||
sshDestinationFlag = "destination"
|
||||
sshURLFlag = "url"
|
||||
sshHeaderFlag = "header"
|
||||
sshTokenIDFlag = "service-token-id"
|
||||
sshTokenSecretFlag = "service-token-secret"
|
||||
sshGenCertFlag = "short-lived-cert"
|
||||
sshLogDirectoryFlag = "log-directory"
|
||||
sshLogLevelFlag = "log-level"
|
||||
sshConfigTemplate = `
|
||||
Add to your {{.Home}}/.ssh/config:
|
||||
|
||||
Host {{.Hostname}}
|
||||
|
@ -51,7 +55,6 @@ Host cfpipe-{{.Hostname}}
|
|||
const sentryDSN = "https://56a9c9fa5c364ab28f34b14f35ea0f1b@sentry.io/189878"
|
||||
|
||||
var (
|
||||
logger = log.CreateLogger()
|
||||
shutdownC chan struct{}
|
||||
graceShutdownC chan struct{}
|
||||
)
|
||||
|
@ -71,17 +74,17 @@ func Commands() []*cli.Command {
|
|||
return []*cli.Command{
|
||||
{
|
||||
Name: "access",
|
||||
Category: "Access (BETA)",
|
||||
Aliases: []string{"forward"},
|
||||
Category: "Access",
|
||||
Usage: "access <subcommand>",
|
||||
Description: `(BETA) Cloudflare Access protects internal resources by securing, authenticating and monitoring access
|
||||
Description: `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`,
|
||||
applications from the command line.`,
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "login",
|
||||
Action: login,
|
||||
Action: cliutil.ErrorHandler(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.
|
||||
|
@ -97,7 +100,7 @@ func Commands() []*cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "curl",
|
||||
Action: curl,
|
||||
Action: cliutil.ErrorHandler(curl),
|
||||
Usage: "curl [--allow-request, -ar] <url> [<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.`,
|
||||
|
@ -106,7 +109,7 @@ func Commands() []*cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "token",
|
||||
Action: generateToken,
|
||||
Action: cliutil.ErrorHandler(generateToken),
|
||||
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.`,
|
||||
|
@ -117,45 +120,57 @@ func Commands() []*cli.Command {
|
|||
},
|
||||
},
|
||||
{
|
||||
Name: "ssh",
|
||||
Action: ssh,
|
||||
Aliases: []string{"rdp"},
|
||||
Name: "tcp",
|
||||
Action: cliutil.ErrorHandler(ssh),
|
||||
Aliases: []string{"rdp", "ssh", "smb"},
|
||||
Usage: "",
|
||||
ArgsUsage: "",
|
||||
Description: `The ssh subcommand sends data over a proxy to the Cloudflare edge.`,
|
||||
Description: `The tcp subcommand sends data over a proxy to the Cloudflare edge.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: sshHostnameFlag,
|
||||
Usage: "specify the hostname of your application.",
|
||||
Name: sshHostnameFlag,
|
||||
Aliases: []string{"tunnel-host", "T"},
|
||||
Usage: "specify the hostname of your application.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: sshDestinationFlag,
|
||||
Usage: "specify the destination address of your SSH server.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: sshURLFlag,
|
||||
Usage: "specify the host:port to forward data to Cloudflare edge.",
|
||||
Name: sshURLFlag,
|
||||
Aliases: []string{"listener", "L"},
|
||||
Usage: "specify the host:port to forward data to Cloudflare edge.",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: sshHeaderFlag,
|
||||
Aliases: []string{"H"},
|
||||
Usage: "specify additional headers you wish to send.",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
&cli.StringFlag{
|
||||
Name: sshTokenIDFlag,
|
||||
Aliases: []string{"id"},
|
||||
Usage: "specify an Access service token ID you wish to use.",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
&cli.StringFlag{
|
||||
Name: sshTokenSecretFlag,
|
||||
Aliases: []string{"secret"},
|
||||
Usage: "specify an Access service token secret you wish to use.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: sshLogDirectoryFlag,
|
||||
Aliases: []string{"logfile"}, //added to match the tunnel side
|
||||
Usage: "Save application log to this directory for reporting issues.",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: sshLogLevelFlag,
|
||||
Aliases: []string{"loglevel"}, //added to match the tunnel side
|
||||
Usage: "Application logging level {fatal, error, info, debug}. ",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "ssh-config",
|
||||
Action: sshConfig,
|
||||
Action: cliutil.ErrorHandler(sshConfig),
|
||||
Usage: "",
|
||||
Description: `Prints an example configuration ~/.ssh/config`,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -171,7 +186,7 @@ func Commands() []*cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "ssh-gen",
|
||||
Action: sshGen,
|
||||
Action: cliutil.ErrorHandler(sshGen),
|
||||
Usage: "",
|
||||
Description: `Generates a short lived certificate for given hostname`,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -188,8 +203,15 @@ func Commands() []*cli.Command {
|
|||
|
||||
// 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()
|
||||
if err := raven.SetDSN(sentryDSN); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
args := c.Args()
|
||||
rawURL := ensureURLScheme(args.First())
|
||||
appURL, err := url.Parse(rawURL)
|
||||
|
@ -197,17 +219,20 @@ func login(c *cli.Context) error {
|
|||
logger.Errorf("Please provide the url of the Access application\n")
|
||||
return err
|
||||
}
|
||||
if err := verifyTokenAtEdge(appURL, c); err != nil {
|
||||
logger.WithError(err).Error("Could not verify token")
|
||||
if err := verifyTokenAtEdge(appURL, c, logger); err != nil {
|
||||
logger.Errorf("Could not verify token: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
token, err := token.GetTokenIfExists(appURL)
|
||||
if err != nil || token == "" {
|
||||
cfdToken, err := token.GetTokenIfExists(appURL)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "Unable to find token for provided application.")
|
||||
return err
|
||||
} else if cfdToken == "" {
|
||||
fmt.Fprintln(os.Stderr, "token for provided application was empty.")
|
||||
return errors.New("empty application token")
|
||||
}
|
||||
fmt.Fprintf(os.Stdout, "Successfully fetched your token:\n\n%s\n\n", string(token))
|
||||
fmt.Fprintf(os.Stdout, "Successfully fetched your token:\n\n%s\n\n", cfdToken)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -224,8 +249,14 @@ func ensureURLScheme(url string) string {
|
|||
|
||||
// curl provides a wrapper around curl, passing Access JWT along in request
|
||||
func curl(c *cli.Context) error {
|
||||
raven.SetDSN(sentryDSN)
|
||||
logger := log.CreateLogger()
|
||||
if err := raven.SetDSN(sentryDSN); err != nil {
|
||||
return err
|
||||
}
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
args := c.Args()
|
||||
if args.Len() < 1 {
|
||||
logger.Error("Please provide the access app and command you wish to run.")
|
||||
|
@ -233,7 +264,7 @@ func curl(c *cli.Context) error {
|
|||
}
|
||||
|
||||
cmdArgs, allowRequest := parseAllowRequest(args.Slice())
|
||||
appURL, err := getAppURL(cmdArgs)
|
||||
appURL, err := getAppURL(cmdArgs, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -241,24 +272,26 @@ func curl(c *cli.Context) error {
|
|||
tok, err := token.GetTokenIfExists(appURL)
|
||||
if err != nil || tok == "" {
|
||||
if allowRequest {
|
||||
logger.Warn("You don't have an Access token set. Please run access token <access application> to fetch one.")
|
||||
logger.Info("You don't have an Access token set. Please run access token <access application> to fetch one.")
|
||||
return shell.Run("curl", cmdArgs...)
|
||||
}
|
||||
tok, err = token.FetchToken(appURL)
|
||||
tok, err = token.FetchToken(appURL, logger)
|
||||
if err != nil {
|
||||
logger.Error("Failed to refresh token: ", err)
|
||||
logger.Errorf("Failed to refresh token: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
cmdArgs = append(cmdArgs, "-H")
|
||||
cmdArgs = append(cmdArgs, fmt.Sprintf("cf-access-token: %s", tok))
|
||||
cmdArgs = append(cmdArgs, fmt.Sprintf("%s: %s", h2mux.CFAccessTokenHeader, tok))
|
||||
return shell.Run("curl", cmdArgs...)
|
||||
}
|
||||
|
||||
// token dumps provided token to stdout
|
||||
func generateToken(c *cli.Context) error {
|
||||
raven.SetDSN(sentryDSN)
|
||||
if err := raven.SetDSN(sentryDSN); err != nil {
|
||||
return err
|
||||
}
|
||||
appURL, err := url.Parse(c.String("app"))
|
||||
if err != nil || c.NumFlags() < 1 {
|
||||
fmt.Fprintln(os.Stderr, "Please provide a url.")
|
||||
|
@ -298,6 +331,11 @@ func sshConfig(c *cli.Context) error {
|
|||
|
||||
// sshGen generates a short lived certificate for provided hostname
|
||||
func sshGen(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
// get the hostname from the cmdline and error out if its not provided
|
||||
rawHostName := c.String(sshHostnameFlag)
|
||||
hostname, err := validation.ValidateHostname(rawHostName)
|
||||
|
@ -313,12 +351,12 @@ func sshGen(c *cli.Context) error {
|
|||
// this fetchToken function mutates the appURL param. We should refactor that
|
||||
fetchTokenURL := &url.URL{}
|
||||
*fetchTokenURL = *originURL
|
||||
token, err := token.FetchToken(fetchTokenURL)
|
||||
cfdToken, err := token.FetchTokenWithRedirect(fetchTokenURL, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := sshgen.GenerateShortLivedCertificate(originURL, token); err != nil {
|
||||
if err := sshgen.GenerateShortLivedCertificate(originURL, cfdToken); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -326,7 +364,7 @@ func sshGen(c *cli.Context) error {
|
|||
}
|
||||
|
||||
// getAppURL will pull the appURL needed for fetching a user's Access token
|
||||
func getAppURL(cmdArgs []string) (*url.URL, error) {
|
||||
func getAppURL(cmdArgs []string, logger logger.Service) (*url.URL, error) {
|
||||
if len(cmdArgs) < 1 {
|
||||
logger.Error("Please provide a valid URL as the first argument to curl.")
|
||||
return nil, errors.New("not a valid url")
|
||||
|
@ -400,17 +438,17 @@ func isFileThere(candidate string) bool {
|
|||
// verifyTokenAtEdge checks for a token on disk, or generates a new one.
|
||||
// Then makes a request to to the origin with the token to ensure it is valid.
|
||||
// Returns nil if token is valid.
|
||||
func verifyTokenAtEdge(appUrl *url.URL, c *cli.Context) error {
|
||||
func verifyTokenAtEdge(appUrl *url.URL, c *cli.Context, logger logger.Service) error {
|
||||
headers := buildRequestHeaders(c.StringSlice(sshHeaderFlag))
|
||||
if c.IsSet(sshTokenIDFlag) {
|
||||
headers.Add("CF-Access-Client-Id", c.String(sshTokenIDFlag))
|
||||
headers.Add(h2mux.CFAccessClientIDHeader, c.String(sshTokenIDFlag))
|
||||
}
|
||||
if c.IsSet(sshTokenSecretFlag) {
|
||||
headers.Add("CF-Access-Client-Secret", c.String(sshTokenSecretFlag))
|
||||
headers.Add(h2mux.CFAccessClientSecretHeader, c.String(sshTokenSecretFlag))
|
||||
}
|
||||
options := &carrier.StartOptions{OriginURL: appUrl.String(), Headers: headers}
|
||||
|
||||
if valid, err := isTokenValid(options); err != nil {
|
||||
if valid, err := isTokenValid(options, logger); err != nil {
|
||||
return err
|
||||
} else if valid {
|
||||
return nil
|
||||
|
@ -420,7 +458,7 @@ func verifyTokenAtEdge(appUrl *url.URL, c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if valid, err := isTokenValid(options); err != nil {
|
||||
if valid, err := isTokenValid(options, logger); err != nil {
|
||||
return err
|
||||
} else if !valid {
|
||||
return errors.New("failed to verify token")
|
||||
|
@ -430,8 +468,8 @@ func verifyTokenAtEdge(appUrl *url.URL, c *cli.Context) error {
|
|||
}
|
||||
|
||||
// isTokenValid makes a request to the origin and returns true if the response was not a 302.
|
||||
func isTokenValid(options *carrier.StartOptions) (bool, error) {
|
||||
req, err := carrier.BuildAccessRequest(options)
|
||||
func isTokenValid(options *carrier.StartOptions, logger logger.Service) (bool, error) {
|
||||
req, err := carrier.BuildAccessRequest(options, logger)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "Could not create access request")
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
// ForwardServiceType is used to identify what kind of overwatch service this is
|
||||
const ForwardServiceType = "forward"
|
||||
|
||||
// ForwarderService is used to wrap the access package websocket forwarders
|
||||
// into a service model for the overwatch package.
|
||||
// it also holds a reference to the config object that represents its state
|
||||
type ForwarderService struct {
|
||||
forwarder config.Forwarder
|
||||
shutdown chan struct{}
|
||||
logger logger.Service
|
||||
}
|
||||
|
||||
// NewForwardService creates a new forwarder service
|
||||
func NewForwardService(f config.Forwarder, logger logger.Service) *ForwarderService {
|
||||
return &ForwarderService{forwarder: f, shutdown: make(chan struct{}, 1), logger: logger}
|
||||
}
|
||||
|
||||
// Name is used to figure out this service is related to the others (normally the addr it binds to)
|
||||
// e.g. localhost:78641 or 127.0.0.1:2222 since this is a websocket forwarder
|
||||
func (s *ForwarderService) Name() string {
|
||||
return s.forwarder.Listener
|
||||
}
|
||||
|
||||
// Type is used to identify what kind of overwatch service this is
|
||||
func (s *ForwarderService) Type() string {
|
||||
return ForwardServiceType
|
||||
}
|
||||
|
||||
// Hash is used to figure out if this forwarder is the unchanged or not from the config file updates
|
||||
func (s *ForwarderService) Hash() string {
|
||||
return s.forwarder.Hash()
|
||||
}
|
||||
|
||||
// Shutdown stops the websocket listener
|
||||
func (s *ForwarderService) Shutdown() {
|
||||
s.shutdown <- struct{}{}
|
||||
}
|
||||
|
||||
// Run is the run loop that is started by the overwatch service
|
||||
func (s *ForwarderService) Run() error {
|
||||
return access.StartForwarder(s.forwarder, s.shutdown, s.logger)
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/tunneldns"
|
||||
)
|
||||
|
||||
// ResolverServiceType is used to identify what kind of overwatch service this is
|
||||
const ResolverServiceType = "resolver"
|
||||
|
||||
// ResolverService is used to wrap the tunneldns package's DNS over HTTP
|
||||
// into a service model for the overwatch package.
|
||||
// it also holds a reference to the config object that represents its state
|
||||
type ResolverService struct {
|
||||
resolver config.DNSResolver
|
||||
shutdown chan struct{}
|
||||
logger logger.Service
|
||||
}
|
||||
|
||||
// NewResolverService creates a new resolver service
|
||||
func NewResolverService(r config.DNSResolver, logger logger.Service) *ResolverService {
|
||||
return &ResolverService{resolver: r,
|
||||
shutdown: make(chan struct{}),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Name is used to figure out this service is related to the others (normally the addr it binds to)
|
||||
// this is just "resolver" since there can only be one DNS resolver running
|
||||
func (s *ResolverService) Name() string {
|
||||
return ResolverServiceType
|
||||
}
|
||||
|
||||
// Type is used to identify what kind of overwatch service this is
|
||||
func (s *ResolverService) Type() string {
|
||||
return ResolverServiceType
|
||||
}
|
||||
|
||||
// Hash is used to figure out if this forwarder is the unchanged or not from the config file updates
|
||||
func (s *ResolverService) Hash() string {
|
||||
return s.resolver.Hash()
|
||||
}
|
||||
|
||||
// Shutdown stops the tunneldns listener
|
||||
func (s *ResolverService) Shutdown() {
|
||||
s.shutdown <- struct{}{}
|
||||
}
|
||||
|
||||
// Run is the run loop that is started by the overwatch service
|
||||
func (s *ResolverService) Run() error {
|
||||
// create a listener
|
||||
l, err := tunneldns.CreateListener(s.resolver.AddressOrDefault(), s.resolver.PortOrDefault(),
|
||||
s.resolver.UpstreamsOrDefault(), s.resolver.BootstrapsOrDefault(), s.logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// start the listener.
|
||||
readySignal := make(chan struct{})
|
||||
err = l.Start(readySignal)
|
||||
if err != nil {
|
||||
l.Stop()
|
||||
return err
|
||||
}
|
||||
<-readySignal
|
||||
s.logger.Infof("start resolver on: %s:%d", s.resolver.AddressOrDefault(), s.resolver.PortOrDefault())
|
||||
|
||||
// wait for shutdown signal
|
||||
<-s.shutdown
|
||||
s.logger.Infof("shutdown on: %s:%d", s.resolver.AddressOrDefault(), s.resolver.PortOrDefault())
|
||||
return l.Stop()
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/overwatch"
|
||||
)
|
||||
|
||||
// AppService is the main service that runs when no command lines flags are passed to cloudflared
|
||||
// it manages all the running services such as tunnels, forwarders, DNS resolver, etc
|
||||
type AppService struct {
|
||||
configManager config.Manager
|
||||
serviceManager overwatch.Manager
|
||||
shutdownC chan struct{}
|
||||
configUpdateChan chan config.Root
|
||||
logger logger.Service
|
||||
}
|
||||
|
||||
// NewAppService creates a new AppService with needed supporting services
|
||||
func NewAppService(configManager config.Manager, serviceManager overwatch.Manager, shutdownC chan struct{}, logger logger.Service) *AppService {
|
||||
return &AppService{
|
||||
configManager: configManager,
|
||||
serviceManager: serviceManager,
|
||||
shutdownC: shutdownC,
|
||||
configUpdateChan: make(chan config.Root),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Run starts the run loop to handle config updates and run forwarders, tunnels, etc
|
||||
func (s *AppService) Run() error {
|
||||
go s.actionLoop()
|
||||
return s.configManager.Start(s)
|
||||
}
|
||||
|
||||
// Shutdown kills all the running services
|
||||
func (s *AppService) Shutdown() error {
|
||||
s.configManager.Shutdown()
|
||||
s.shutdownC <- struct{}{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigDidUpdate is a delegate notification from the config manager
|
||||
// it is trigger when the config file has been updated and now the service needs
|
||||
// to update its services accordingly
|
||||
func (s *AppService) ConfigDidUpdate(c config.Root) {
|
||||
s.configUpdateChan <- c
|
||||
}
|
||||
|
||||
// actionLoop handles the actions from running processes
|
||||
func (s *AppService) actionLoop() {
|
||||
for {
|
||||
select {
|
||||
case c := <-s.configUpdateChan:
|
||||
s.handleConfigUpdate(c)
|
||||
case <-s.shutdownC:
|
||||
for _, service := range s.serviceManager.Services() {
|
||||
service.Shutdown()
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *AppService) handleConfigUpdate(c config.Root) {
|
||||
// handle the client forward listeners
|
||||
activeServices := map[string]struct{}{}
|
||||
for _, f := range c.Forwarders {
|
||||
service := NewForwardService(f, s.logger)
|
||||
s.serviceManager.Add(service)
|
||||
activeServices[service.Name()] = struct{}{}
|
||||
}
|
||||
|
||||
// handle resolver changes
|
||||
if c.Resolver.Enabled {
|
||||
service := NewResolverService(c.Resolver, s.logger)
|
||||
s.serviceManager.Add(service)
|
||||
activeServices[service.Name()] = struct{}{}
|
||||
|
||||
}
|
||||
|
||||
// TODO: TUN-1451 - tunnels
|
||||
|
||||
// remove any services that are no longer active
|
||||
for _, service := range s.serviceManager.Services() {
|
||||
if _, ok := activeServices[service.Name()]; !ok {
|
||||
s.serviceManager.Remove(service.Name())
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ package buildinfo
|
|||
import (
|
||||
"runtime"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
type BuildInfo struct {
|
||||
|
@ -22,7 +22,7 @@ func GetBuildInfo(cloudflaredVersion string) *BuildInfo {
|
|||
}
|
||||
}
|
||||
|
||||
func (bi *BuildInfo) Log(logger *logrus.Logger) {
|
||||
func (bi *BuildInfo) Log(logger logger.Service) {
|
||||
logger.Infof("Version %s", bi.CloudflaredVersion)
|
||||
logger.Infof("GOOS: %s, GOVersion: %s, GoArch: %s", bi.GoOS, bi.GoVersion, bi.GoArch)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package cliutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
type usageError string
|
||||
|
||||
func (ue usageError) Error() string {
|
||||
return string(ue)
|
||||
}
|
||||
|
||||
func UsageError(format string, args ...interface{}) error {
|
||||
if len(args) == 0 {
|
||||
return usageError(format)
|
||||
} else {
|
||||
msg := fmt.Sprintf(format, args...)
|
||||
return usageError(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures exit with error code if actionFunc returns an error
|
||||
func ErrorHandler(actionFunc cli.ActionFunc) cli.ActionFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
err := actionFunc(ctx)
|
||||
if err != nil {
|
||||
if _, ok := err.(usageError); ok {
|
||||
msg := fmt.Sprintf("%s\nSee 'cloudflared %s --help'.", err.Error(), ctx.Command.FullName())
|
||||
return cli.Exit(msg, -1)
|
||||
}
|
||||
// os.Exits with error code if err is cli.ExitCoder or cli.MultiError
|
||||
cli.HandleExitCoder(err)
|
||||
err = cli.Exit(err.Error(), 1)
|
||||
}
|
||||
logger.SharedWriteManager.Shutdown()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// PrintLoggerSetupError returns an error to stdout to notify when a logger can't start
|
||||
func PrintLoggerSetupError(msg string, err error) error {
|
||||
l, le := logger.New()
|
||||
if le != nil {
|
||||
log.Printf("%s: %s", msg, err)
|
||||
} else {
|
||||
l.Errorf("%s: %s", msg, err)
|
||||
}
|
||||
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
|
@ -4,24 +4,64 @@ import (
|
|||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
"gopkg.in/urfave/cli.v2/altsrc"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
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)
|
||||
|
@ -63,6 +103,68 @@ 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 := FindOrCreateConfigPath()
|
||||
defaultDirectory := DefaultLogDirectory()
|
||||
defaultLevel := "info"
|
||||
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
return defaultDirectory, defaultLevel
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var config Root
|
||||
if err := yaml.NewDecoder(file).Decode(&config); err != nil {
|
||||
return defaultDirectory, defaultLevel
|
||||
}
|
||||
|
||||
directory := defaultDirectory
|
||||
if config.LogDirectory != "" {
|
||||
directory = config.LogDirectory
|
||||
}
|
||||
|
||||
level := defaultLevel
|
||||
if config.LogLevel != "" {
|
||||
level = config.LogLevel
|
||||
}
|
||||
return directory, level
|
||||
}
|
||||
|
||||
// ValidateUnixSocket ensures --unix-socket param is used exclusively
|
||||
// i.e. it fails if a user specifies both --url and --unix-socket
|
||||
func ValidateUnixSocket(c *cli.Context) (string, error) {
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// Notifier sends out config updates
|
||||
type Notifier interface {
|
||||
ConfigDidUpdate(Root)
|
||||
}
|
||||
|
||||
// Manager is the base functions of the config manager
|
||||
type Manager interface {
|
||||
Start(Notifier) error
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// FileManager watches the yaml config for changes
|
||||
// sends updates to the service to reconfigure to match the updated config
|
||||
type FileManager struct {
|
||||
watcher watcher.Notifier
|
||||
notifier Notifier
|
||||
configPath string
|
||||
logger logger.Service
|
||||
ReadConfig func(string) (Root, error)
|
||||
}
|
||||
|
||||
// NewFileManager creates a config manager
|
||||
func NewFileManager(watcher watcher.Notifier, configPath string, logger logger.Service) (*FileManager, error) {
|
||||
m := &FileManager{
|
||||
watcher: watcher,
|
||||
configPath: configPath,
|
||||
logger: logger,
|
||||
ReadConfig: readConfigFromPath,
|
||||
}
|
||||
err := watcher.Add(configPath)
|
||||
return m, err
|
||||
}
|
||||
|
||||
// Start starts the runloop to watch for config changes
|
||||
func (m *FileManager) Start(notifier Notifier) error {
|
||||
m.notifier = notifier
|
||||
|
||||
// update the notifier with a fresh config on start
|
||||
config, err := m.GetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
notifier.ConfigDidUpdate(config)
|
||||
|
||||
m.watcher.Start(m)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig reads the yaml file from the disk
|
||||
func (m *FileManager) GetConfig() (Root, error) {
|
||||
return m.ReadConfig(m.configPath)
|
||||
}
|
||||
|
||||
// Shutdown stops the watcher
|
||||
func (m *FileManager) Shutdown() {
|
||||
m.watcher.Shutdown()
|
||||
}
|
||||
|
||||
func readConfigFromPath(configPath string) (Root, error) {
|
||||
if configPath == "" {
|
||||
return Root{}, errors.New("unable to find config file")
|
||||
}
|
||||
|
||||
file, err := os.Open(configPath)
|
||||
if err != nil {
|
||||
return Root{}, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
var config Root
|
||||
if err := yaml.NewDecoder(file).Decode(&config); err != nil {
|
||||
return Root{}, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// File change notifications from the watcher
|
||||
|
||||
// WatcherItemDidChange triggers when the yaml config is updated
|
||||
// sends the updated config to the service to reload its state
|
||||
func (m *FileManager) WatcherItemDidChange(filepath string) {
|
||||
config, err := m.GetConfig()
|
||||
if err != nil {
|
||||
m.logger.Errorf("Failed to read new config: %s", err)
|
||||
return
|
||||
}
|
||||
m.logger.Info("Config file has been updated")
|
||||
m.notifier.ConfigDidUpdate(config)
|
||||
}
|
||||
|
||||
// WatcherDidError notifies of errors with the file watcher
|
||||
func (m *FileManager) WatcherDidError(err error) {
|
||||
m.logger.Errorf("Config watcher encountered an error: %s", err)
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockNotifier struct {
|
||||
configs []Root
|
||||
}
|
||||
|
||||
func (n *mockNotifier) ConfigDidUpdate(c Root) {
|
||||
n.configs = append(n.configs, c)
|
||||
}
|
||||
|
||||
type mockFileWatcher struct {
|
||||
path string
|
||||
notifier watcher.Notification
|
||||
ready chan struct{}
|
||||
}
|
||||
|
||||
func (w *mockFileWatcher) Start(n watcher.Notification) {
|
||||
w.notifier = n
|
||||
w.ready <- struct{}{}
|
||||
}
|
||||
|
||||
func (w *mockFileWatcher) Add(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *mockFileWatcher) Shutdown() {
|
||||
|
||||
}
|
||||
|
||||
func (w *mockFileWatcher) TriggerChange() {
|
||||
w.notifier.WatcherItemDidChange(w.path)
|
||||
}
|
||||
|
||||
func TestConfigChanged(t *testing.T) {
|
||||
filePath := "config.yaml"
|
||||
f, err := os.Create(filePath)
|
||||
assert.NoError(t, err)
|
||||
defer func() {
|
||||
f.Close()
|
||||
os.Remove(filePath)
|
||||
}()
|
||||
c := &Root{
|
||||
Forwarders: []Forwarder{
|
||||
{
|
||||
URL: "test.daltoniam.com",
|
||||
Listener: "127.0.0.1:8080",
|
||||
},
|
||||
},
|
||||
}
|
||||
configRead := func(configPath string) (Root, error) {
|
||||
return *c, nil
|
||||
}
|
||||
wait := make(chan struct{})
|
||||
w := &mockFileWatcher{path: filePath, ready: wait}
|
||||
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
service, err := NewFileManager(w, filePath, logger)
|
||||
service.ReadConfig = configRead
|
||||
assert.NoError(t, err)
|
||||
|
||||
n := &mockNotifier{}
|
||||
go service.Start(n)
|
||||
|
||||
<-wait
|
||||
c.Forwarders = append(c.Forwarders, Forwarder{URL: "add.daltoniam.com", Listener: "127.0.0.1:8081"})
|
||||
w.TriggerChange()
|
||||
|
||||
service.Shutdown()
|
||||
|
||||
assert.Len(t, n.configs, 2, "did not get 2 config updates as expected")
|
||||
assert.Len(t, n.configs[0].Forwarders, 1, "not the amount of forwarders expected")
|
||||
assert.Len(t, n.configs[1].Forwarders, 2, "not the amount of forwarders expected")
|
||||
|
||||
assert.Equal(t, n.configs[0].Forwarders[0].Hash(), c.Forwarders[0].Hash(), "forwarder hashes don't match")
|
||||
assert.Equal(t, n.configs[1].Forwarders[0].Hash(), c.Forwarders[0].Hash(), "forwarder hashes don't match")
|
||||
assert.Equal(t, n.configs[1].Forwarders[1].Hash(), c.Forwarders[1].Hash(), "forwarder hashes don't match")
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
package config
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Forwarder represents a client side listener to forward traffic to the edge
|
||||
type Forwarder struct {
|
||||
URL string `json:"url"`
|
||||
Listener string `json:"listener"`
|
||||
TokenClientID string `json:"service_token_id" yaml:"serviceTokenID"`
|
||||
TokenSecret string `json:"secret_token_id" yaml:"serviceTokenSecret"`
|
||||
Destination string `json:"destination"`
|
||||
}
|
||||
|
||||
// Tunnel represents a tunnel that should be started
|
||||
type Tunnel struct {
|
||||
URL string `json:"url"`
|
||||
Origin string `json:"origin"`
|
||||
ProtocolType string `json:"type"`
|
||||
}
|
||||
|
||||
// DNSResolver represents a client side DNS resolver
|
||||
type DNSResolver struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Port uint16 `json:"port,omitempty"`
|
||||
Upstreams []string `json:"upstreams,omitempty"`
|
||||
Bootstraps []string `json:"bootstraps,omitempty"`
|
||||
}
|
||||
|
||||
// Root is the base options to configure the service
|
||||
type Root struct {
|
||||
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
|
||||
func (f *Forwarder) Hash() string {
|
||||
h := md5.New()
|
||||
io.WriteString(h, f.URL)
|
||||
io.WriteString(h, f.Listener)
|
||||
io.WriteString(h, f.TokenClientID)
|
||||
io.WriteString(h, f.TokenSecret)
|
||||
io.WriteString(h, f.Destination)
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// Hash returns the computed values to see if the forwarder values change
|
||||
func (r *DNSResolver) Hash() string {
|
||||
h := md5.New()
|
||||
io.WriteString(h, r.Address)
|
||||
io.WriteString(h, strings.Join(r.Bootstraps, ","))
|
||||
io.WriteString(h, strings.Join(r.Upstreams, ","))
|
||||
io.WriteString(h, fmt.Sprintf("%d", r.Port))
|
||||
io.WriteString(h, fmt.Sprintf("%v", r.Enabled))
|
||||
return fmt.Sprintf("%x", h.Sum(nil))
|
||||
}
|
||||
|
||||
// EnabledOrDefault returns the enabled property
|
||||
func (r *DNSResolver) EnabledOrDefault() bool {
|
||||
return r.Enabled
|
||||
}
|
||||
|
||||
// AddressOrDefault returns the address or returns the default if empty
|
||||
func (r *DNSResolver) AddressOrDefault() string {
|
||||
if r.Address != "" {
|
||||
return r.Address
|
||||
}
|
||||
return "localhost"
|
||||
}
|
||||
|
||||
// PortOrDefault return the port or returns the default if 0
|
||||
func (r *DNSResolver) PortOrDefault() uint16 {
|
||||
if r.Port > 0 {
|
||||
return r.Port
|
||||
}
|
||||
return 53
|
||||
}
|
||||
|
||||
// UpstreamsOrDefault returns the upstreams or returns the default if empty
|
||||
func (r *DNSResolver) UpstreamsOrDefault() []string {
|
||||
if len(r.Upstreams) > 0 {
|
||||
return r.Upstreams
|
||||
}
|
||||
return []string{"https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"}
|
||||
}
|
||||
|
||||
// BootstrapsOrDefault returns the bootstraps or returns the default if empty
|
||||
func (r *DNSResolver) BootstrapsOrDefault() []string {
|
||||
if len(r.Bootstraps) > 0 {
|
||||
return r.Bootstraps
|
||||
}
|
||||
return []string{"https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"}
|
||||
}
|
|
@ -8,6 +8,8 @@ import (
|
|||
"path/filepath"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/pkg/errors"
|
||||
cli "gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
|
@ -118,10 +120,6 @@ case "$1" in
|
|||
echo "Starting $name"
|
||||
$cmd >> "$stdout_log" 2>> "$stderr_log" &
|
||||
echo $! > "$pid_file"
|
||||
if ! is_running; then
|
||||
echo "Unable to start, see $stdout_log and $stderr_log"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
|
@ -182,24 +180,37 @@ func isSystemd() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string) error {
|
||||
func copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile string, logger logger.Service) 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
|
||||
if srcCredentialPath != destCredentialPath {
|
||||
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
|
||||
if srcConfigPath != destConfigPath {
|
||||
if err := copyConfig(srcConfigPath, destConfigPath); err != nil {
|
||||
return err
|
||||
}
|
||||
logger.Infof("Copied %s to %s", srcConfigPath, destConfigPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func installLinuxService(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
etPath, err := os.Executable()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error determining executable path: %v", err)
|
||||
|
@ -209,8 +220,8 @@ func installLinuxService(c *cli.Context) error {
|
|||
userConfigDir := filepath.Dir(c.String("config"))
|
||||
userConfigFile := filepath.Base(c.String("config"))
|
||||
userCredentialFile := config.DefaultCredentialFile
|
||||
if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile); err != nil {
|
||||
logger.WithError(err).Infof("Failed to copy user configuration. Before running the service, ensure that %s contains two files, %s and %s",
|
||||
if err = copyUserConfiguration(userConfigDir, userConfigFile, userCredentialFile, logger); err != nil {
|
||||
logger.Errorf("Failed to copy user configuration: %s. Before running the service, ensure that %s contains two files, %s and %s", err,
|
||||
serviceConfigDir, serviceCredentialFile, serviceConfigFile)
|
||||
return err
|
||||
}
|
||||
|
@ -218,41 +229,41 @@ func installLinuxService(c *cli.Context) error {
|
|||
switch {
|
||||
case isSystemd():
|
||||
logger.Infof("Using Systemd")
|
||||
return installSystemd(&templateArgs)
|
||||
return installSystemd(&templateArgs, logger)
|
||||
default:
|
||||
logger.Infof("Using Sysv")
|
||||
return installSysv(&templateArgs)
|
||||
return installSysv(&templateArgs, logger)
|
||||
}
|
||||
}
|
||||
|
||||
func installSystemd(templateArgs *ServiceTemplateArgs) error {
|
||||
func installSystemd(templateArgs *ServiceTemplateArgs, logger logger.Service) error {
|
||||
for _, serviceTemplate := range systemdTemplates {
|
||||
err := serviceTemplate.Generate(templateArgs)
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error generating service template")
|
||||
logger.Errorf("error generating service template: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := runCommand("systemctl", "enable", "cloudflared.service"); err != nil {
|
||||
logger.WithError(err).Infof("systemctl enable cloudflared.service error")
|
||||
logger.Errorf("systemctl enable cloudflared.service error: %s", err)
|
||||
return err
|
||||
}
|
||||
if err := runCommand("systemctl", "start", "cloudflared-update.timer"); err != nil {
|
||||
logger.WithError(err).Infof("systemctl start cloudflared-update.timer error")
|
||||
logger.Errorf("systemctl start cloudflared-update.timer error: %s", err)
|
||||
return err
|
||||
}
|
||||
logger.Infof("systemctl daemon-reload")
|
||||
return runCommand("systemctl", "daemon-reload")
|
||||
}
|
||||
|
||||
func installSysv(templateArgs *ServiceTemplateArgs) error {
|
||||
func installSysv(templateArgs *ServiceTemplateArgs, logger logger.Service) error {
|
||||
confPath, err := sysvTemplate.ResolvePath()
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error resolving system path")
|
||||
logger.Errorf("error resolving system path: %s", err)
|
||||
return err
|
||||
}
|
||||
if err := sysvTemplate.Generate(templateArgs); err != nil {
|
||||
logger.WithError(err).Infof("error generating system template")
|
||||
logger.Errorf("error generating system template: %s", err)
|
||||
return err
|
||||
}
|
||||
for _, i := range [...]string{"2", "3", "4", "5"} {
|
||||
|
@ -269,28 +280,33 @@ func installSysv(templateArgs *ServiceTemplateArgs) error {
|
|||
}
|
||||
|
||||
func uninstallLinuxService(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
switch {
|
||||
case isSystemd():
|
||||
logger.Infof("Using Systemd")
|
||||
return uninstallSystemd()
|
||||
return uninstallSystemd(logger)
|
||||
default:
|
||||
logger.Infof("Using Sysv")
|
||||
return uninstallSysv()
|
||||
return uninstallSysv(logger)
|
||||
}
|
||||
}
|
||||
|
||||
func uninstallSystemd() error {
|
||||
func uninstallSystemd(logger logger.Service) error {
|
||||
if err := runCommand("systemctl", "disable", "cloudflared.service"); err != nil {
|
||||
logger.WithError(err).Infof("systemctl disable cloudflared.service error")
|
||||
logger.Errorf("systemctl disable cloudflared.service error: %s", err)
|
||||
return err
|
||||
}
|
||||
if err := runCommand("systemctl", "stop", "cloudflared-update.timer"); err != nil {
|
||||
logger.WithError(err).Infof("systemctl stop cloudflared-update.timer error")
|
||||
logger.Errorf("systemctl stop cloudflared-update.timer error: %s", err)
|
||||
return err
|
||||
}
|
||||
for _, serviceTemplate := range systemdTemplates {
|
||||
if err := serviceTemplate.Remove(); err != nil {
|
||||
logger.WithError(err).Infof("error removing service template")
|
||||
logger.Errorf("error removing service template: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -298,9 +314,9 @@ func uninstallSystemd() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func uninstallSysv() error {
|
||||
func uninstallSysv(logger logger.Service) error {
|
||||
if err := sysvTemplate.Remove(); err != nil {
|
||||
logger.WithError(err).Infof("error removing service template")
|
||||
logger.Errorf("error removing service template: %s", err)
|
||||
return err
|
||||
}
|
||||
for _, i := range [...]string{"2", "3", "4", "5"} {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
|
@ -60,7 +61,7 @@ func newLaunchdTemplate(installPath, stdoutPath, stderrPath string) *ServiceTemp
|
|||
<false/>
|
||||
</dict>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>20</integer>
|
||||
<integer>5</integer>
|
||||
</dict>
|
||||
</plist>`, launchdIdentifier, stdoutPath, stderrPath),
|
||||
}
|
||||
|
@ -105,6 +106,11 @@ func stderrPath() (string, error) {
|
|||
}
|
||||
|
||||
func installLaunchd(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
if isRootUser() {
|
||||
logger.Infof("Installing Argo Tunnel client as a system launch daemon. " +
|
||||
"Argo Tunnel client will run at boot")
|
||||
|
@ -116,35 +122,38 @@ func installLaunchd(c *cli.Context) error {
|
|||
}
|
||||
etPath, err := os.Executable()
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Error determining executable path")
|
||||
logger.Errorf("Error determining executable path: %s", err)
|
||||
return fmt.Errorf("Error determining executable path: %v", err)
|
||||
}
|
||||
installPath, err := installPath()
|
||||
if err != nil {
|
||||
logger.Errorf("Error determining install path: %s", err)
|
||||
return errors.Wrap(err, "Error determining install path")
|
||||
}
|
||||
stdoutPath, err := stdoutPath()
|
||||
if err != nil {
|
||||
logger.Errorf("error determining stdout path: %s", err)
|
||||
return errors.Wrap(err, "error determining stdout path")
|
||||
}
|
||||
stderrPath, err := stderrPath()
|
||||
if err != nil {
|
||||
logger.Errorf("error determining stderr path: %s", err)
|
||||
return errors.Wrap(err, "error determining stderr path")
|
||||
}
|
||||
launchdTemplate := newLaunchdTemplate(installPath, stdoutPath, stderrPath)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("error creating launchd template")
|
||||
logger.Errorf("error creating launchd template: %s", err)
|
||||
return errors.Wrap(err, "error creating launchd template")
|
||||
}
|
||||
templateArgs := ServiceTemplateArgs{Path: etPath}
|
||||
err = launchdTemplate.Generate(&templateArgs)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("error generating launchd template")
|
||||
logger.Errorf("error generating launchd template: %s", err)
|
||||
return err
|
||||
}
|
||||
plistPath, err := launchdTemplate.ResolvePath()
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error resolving launchd template path")
|
||||
logger.Errorf("error resolving launchd template path: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -153,6 +162,11 @@ func installLaunchd(c *cli.Context) error {
|
|||
}
|
||||
|
||||
func uninstallLaunchd(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
if isRootUser() {
|
||||
logger.Infof("Uninstalling Argo Tunnel as a system launch daemon")
|
||||
} else {
|
||||
|
@ -176,12 +190,12 @@ func uninstallLaunchd(c *cli.Context) error {
|
|||
}
|
||||
plistPath, err := launchdTemplate.ResolvePath()
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error resolving launchd template path")
|
||||
logger.Errorf("error resolving launchd template path: %s", err)
|
||||
return err
|
||||
}
|
||||
err = runCommand("launchctl", "unload", plistPath)
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error unloading")
|
||||
logger.Errorf("error unloading: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -6,10 +6,14 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
log "github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/metrics"
|
||||
"github.com/cloudflare/cloudflared/overwatch"
|
||||
"github.com/cloudflare/cloudflared/watcher"
|
||||
|
||||
raven "github.com/getsentry/raven-go"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
|
@ -25,7 +29,6 @@ const (
|
|||
var (
|
||||
Version = "DEV"
|
||||
BuildTime = "unknown"
|
||||
logger = log.CreateLogger()
|
||||
// Mostly network errors that we don't want reported back to Sentry, this is done by substring match.
|
||||
ignoredErrors = []string{
|
||||
"connection reset by peer",
|
||||
|
@ -59,7 +62,7 @@ func main() {
|
|||
app := &cli.App{}
|
||||
app.Name = "cloudflared"
|
||||
app.Usage = "Cloudflare's command-line tool and agent"
|
||||
app.ArgsUsage = "origin-url"
|
||||
app.UsageText = "cloudflared [global options] [command] [command options]"
|
||||
app.Copyright = fmt.Sprintf(
|
||||
`(c) %d Cloudflare Inc.
|
||||
Your installation of cloudflared software constitutes a symbol of your signature indicating that you accept
|
||||
|
@ -121,12 +124,12 @@ func isEmptyInvocation(c *cli.Context) bool {
|
|||
func action(version string, shutdownC, graceShutdownC chan struct{}) cli.ActionFunc {
|
||||
return func(c *cli.Context) (err error) {
|
||||
if isEmptyInvocation(c) {
|
||||
cli.ShowAppHelpAndExit(c, 1)
|
||||
return handleServiceMode(shutdownC)
|
||||
}
|
||||
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)
|
||||
|
@ -145,7 +148,6 @@ func userHomeDir() (string, error) {
|
|||
// use with sudo.
|
||||
homeDir, err := homedir.Dir()
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Cannot determine home directory for the user")
|
||||
return "", errors.Wrap(err, "Cannot determine home directory for the user")
|
||||
}
|
||||
return homeDir, nil
|
||||
|
@ -161,3 +163,43 @@ func handleError(err error) {
|
|||
}
|
||||
raven.CaptureError(err, nil)
|
||||
}
|
||||
|
||||
// cloudflared was started without any flags
|
||||
func handleServiceMode(shutdownC chan struct{}) error {
|
||||
defer log.SharedWriteManager.Shutdown()
|
||||
logDirectory, logLevel := config.FindLogSettings()
|
||||
|
||||
logger, err := log.New(log.DefaultFile(logDirectory), log.LogLevelString(logLevel))
|
||||
if err != nil {
|
||||
return cliutil.PrintLoggerSetupError("error setting up logger", err)
|
||||
}
|
||||
logger.Infof("logging to directory: %s", logDirectory)
|
||||
|
||||
// start the main run loop that reads from the config file
|
||||
f, err := watcher.NewFile()
|
||||
if err != nil {
|
||||
logger.Errorf("Cannot load config file: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
configPath := config.FindOrCreateConfigPath()
|
||||
configManager, err := config.NewFileManager(f, configPath, logger)
|
||||
if err != nil {
|
||||
logger.Errorf("Cannot setup config file for monitoring: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
serviceCallback := func(t string, name string, err error) {
|
||||
if err != nil {
|
||||
logger.Errorf("%s service: %s encountered an error: %s", t, name, err)
|
||||
}
|
||||
}
|
||||
serviceManager := overwatch.NewAppManager(serviceCallback)
|
||||
|
||||
appService := NewAppService(configManager, serviceManager, shutdownC, logger)
|
||||
if err := appService.Run(); err != nil {
|
||||
logger.Errorf("Failed to start app service: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -73,21 +73,16 @@ func runCommand(command string, args ...string) error {
|
|||
cmd := exec.Command(command, args...)
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error getting stderr pipe")
|
||||
return fmt.Errorf("error getting stderr pipe: %v", err)
|
||||
}
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error starting %s", command)
|
||||
return fmt.Errorf("error starting %s: %v", command, err)
|
||||
}
|
||||
commandErr, _ := ioutil.ReadAll(stderr)
|
||||
if len(commandErr) > 0 {
|
||||
logger.Errorf("%s: %s", command, commandErr)
|
||||
}
|
||||
|
||||
ioutil.ReadAll(stderr)
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("%s returned error", command)
|
||||
return fmt.Errorf("%s returned with error: %v", command, err)
|
||||
}
|
||||
return nil
|
||||
|
@ -148,8 +143,7 @@ func copyConfig(srcConfigPath, destConfigPath string) error {
|
|||
// Copy or create config
|
||||
destFile, exists, err := openFile(destConfigPath, true)
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("cannot open %s", destConfigPath)
|
||||
return err
|
||||
return fmt.Errorf("cannot open %s with error: %s", destConfigPath, err)
|
||||
} else if exists {
|
||||
// config already exists, do nothing
|
||||
return nil
|
||||
|
@ -173,7 +167,6 @@ func copyConfig(srcConfigPath, destConfigPath string) error {
|
|||
if err != nil {
|
||||
return fmt.Errorf("unable to copy %s to %s: %v", srcConfigPath, destConfigPath, err)
|
||||
}
|
||||
logger.Infof("Copied %s to %s", srcConfigPath, destConfigPath)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -2,17 +2,19 @@ package token
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/path"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/origin"
|
||||
"github.com/coreos/go-oidc/jose"
|
||||
)
|
||||
|
@ -21,8 +23,6 @@ const (
|
|||
keyName = "token"
|
||||
)
|
||||
|
||||
var logger = log.CreateLogger()
|
||||
|
||||
type lock struct {
|
||||
lockFilePath string
|
||||
backoff *origin.BackoffHandler
|
||||
|
@ -34,6 +34,21 @@ type signalHandler struct {
|
|||
signals []os.Signal
|
||||
}
|
||||
|
||||
type jwtPayload struct {
|
||||
Aud []string `json:"aud"`
|
||||
Email string `json:"email"`
|
||||
Exp int `json:"exp"`
|
||||
Iat int `json:"iat"`
|
||||
Nbf int `json:"nbf"`
|
||||
Iss string `json:"iss"`
|
||||
Type string `json:"type"`
|
||||
Subt string `json:"sub"`
|
||||
}
|
||||
|
||||
func (p jwtPayload) isExpired() bool {
|
||||
return int(time.Now().Unix()) > p.Exp
|
||||
}
|
||||
|
||||
func (s *signalHandler) register(handler func()) {
|
||||
s.sigChannel = make(chan os.Signal, 1)
|
||||
signal.Notify(s.sigChannel, s.signals...)
|
||||
|
@ -112,8 +127,20 @@ func isTokenLocked(lockFilePath string) bool {
|
|||
return exists && err == nil
|
||||
}
|
||||
|
||||
// FetchTokenWithRedirect will either load a stored token or generate a new one
|
||||
// it appends a redirect URL to the access cli request if opening the browser
|
||||
func FetchTokenWithRedirect(appURL *url.URL, logger logger.Service) (string, error) {
|
||||
return getToken(appURL, true, logger)
|
||||
}
|
||||
|
||||
// FetchToken will either load a stored token or generate a new one
|
||||
func FetchToken(appURL *url.URL) (string, error) {
|
||||
// it doesn't append a redirect URL to the access cli request if opening the browser
|
||||
func FetchToken(appURL *url.URL, logger logger.Service) (string, error) {
|
||||
return getToken(appURL, false, logger)
|
||||
}
|
||||
|
||||
// getToken will either load a stored token or generate a new one
|
||||
func getToken(appURL *url.URL, shouldRedirect bool, logger logger.Service) (string, error) {
|
||||
if token, err := GetTokenIfExists(appURL); token != "" && err == nil {
|
||||
return token, nil
|
||||
}
|
||||
|
@ -139,7 +166,7 @@ func FetchToken(appURL *url.URL) (string, error) {
|
|||
// 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)
|
||||
token, err := transfer.Run(appURL, keyName, keyName, "", path, true)
|
||||
token, err := transfer.Run(appURL, keyName, keyName, "", path, true, shouldRedirect, logger)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -147,7 +174,7 @@ func FetchToken(appURL *url.URL) (string, error) {
|
|||
return string(token), nil
|
||||
}
|
||||
|
||||
// GetTokenIfExists will return the token from local storage if it exists
|
||||
// GetTokenIfExists will return the token from local storage if it exists and not expired
|
||||
func GetTokenIfExists(url *url.URL) (string, error) {
|
||||
path, err := path.GenerateFilePathFromURL(url, keyName)
|
||||
if err != nil {
|
||||
|
@ -162,6 +189,17 @@ func GetTokenIfExists(url *url.URL) (string, error) {
|
|||
return "", err
|
||||
}
|
||||
|
||||
var payload jwtPayload
|
||||
err = json.Unmarshal(token.Payload, &payload)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if payload.isExpired() {
|
||||
err := os.Remove(path)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token.Encode(), nil
|
||||
}
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/encrypter"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/shell"
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -22,20 +22,18 @@ const (
|
|||
clientTimeout = time.Second * 60
|
||||
)
|
||||
|
||||
var logger = log.CreateLogger()
|
||||
|
||||
// Run does the transfer "dance" with the end result downloading the supported resource.
|
||||
// The expanded description is run is encapsulation of shared business logic needed
|
||||
// to request a resource (token/cert/etc) from the transfer service (loginhelper).
|
||||
// The "dance" we refer to is building a HTTP request, opening that in a browser waiting for
|
||||
// the user to complete an action, while it long polls in the background waiting for an
|
||||
// action to be completed to download the resource.
|
||||
func Run(transferURL *url.URL, resourceName, key, value, path string, shouldEncrypt bool) ([]byte, error) {
|
||||
func Run(transferURL *url.URL, resourceName, key, value, path string, shouldEncrypt bool, shouldRedirect bool, logger logger.Service) ([]byte, error) {
|
||||
encrypterClient, err := encrypter.New("cloudflared_priv.pem", "cloudflared_pub.pem")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requestURL, err := buildRequestURL(transferURL, key, value+encrypterClient.PublicKey(), shouldEncrypt)
|
||||
requestURL, err := buildRequestURL(transferURL, key, value+encrypterClient.PublicKey(), shouldEncrypt, shouldRedirect)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -51,7 +49,7 @@ func Run(transferURL *url.URL, resourceName, key, value, path string, shouldEncr
|
|||
var resourceData []byte
|
||||
|
||||
if shouldEncrypt {
|
||||
buf, key, err := transferRequest(baseStoreURL + "transfer/" + encrypterClient.PublicKey())
|
||||
buf, key, err := transferRequest(baseStoreURL+"transfer/"+encrypterClient.PublicKey(), logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -67,7 +65,7 @@ func Run(transferURL *url.URL, resourceName, key, value, path string, shouldEncr
|
|||
|
||||
resourceData = decrypted
|
||||
} else {
|
||||
buf, _, err := transferRequest(baseStoreURL + encrypterClient.PublicKey())
|
||||
buf, _, err := transferRequest(baseStoreURL+encrypterClient.PublicKey(), logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -84,7 +82,7 @@ func Run(transferURL *url.URL, resourceName, key, value, path string, shouldEncr
|
|||
// BuildRequestURL creates a request suitable for a resource transfer.
|
||||
// it will return a constructed url based off the base url and query key/value provided.
|
||||
// cli will build a url for cli transfer request.
|
||||
func buildRequestURL(baseURL *url.URL, key, value string, cli bool) (string, error) {
|
||||
func buildRequestURL(baseURL *url.URL, key, value string, cli, shouldRedirect bool) (string, error) {
|
||||
q := baseURL.Query()
|
||||
q.Set(key, value)
|
||||
baseURL.RawQuery = q.Encode()
|
||||
|
@ -92,24 +90,26 @@ func buildRequestURL(baseURL *url.URL, key, value string, cli bool) (string, err
|
|||
return baseURL.String(), nil
|
||||
}
|
||||
|
||||
q.Set("redirect_url", baseURL.String()) // we add the token as a query param on both the redirect_url
|
||||
baseURL.RawQuery = q.Encode() // and this actual baseURL.
|
||||
if shouldRedirect {
|
||||
q.Set("redirect_url", baseURL.String()) // we add the token as a query param on both the redirect_url and the main url
|
||||
}
|
||||
baseURL.RawQuery = q.Encode() // and this actual baseURL.
|
||||
baseURL.Path = "cdn-cgi/access/cli"
|
||||
return baseURL.String(), nil
|
||||
}
|
||||
|
||||
// transferRequest downloads the requested resource from the request URL
|
||||
func transferRequest(requestURL string) ([]byte, string, error) {
|
||||
func transferRequest(requestURL string, logger logger.Service) ([]byte, string, error) {
|
||||
client := &http.Client{Timeout: clientTimeout}
|
||||
const pollAttempts = 10
|
||||
// we do "long polling" on the endpoint to get the resource.
|
||||
for i := 0; i < pollAttempts; i++ {
|
||||
buf, key, err := poll(client, requestURL)
|
||||
buf, key, err := poll(client, requestURL, logger)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
} else if len(buf) > 0 {
|
||||
if err := putSuccess(client, requestURL); err != nil {
|
||||
logger.WithError(err).Error("Failed to update resource success")
|
||||
logger.Errorf("Failed to update resource success: %s", err)
|
||||
}
|
||||
return buf, key, nil
|
||||
}
|
||||
|
@ -118,7 +118,7 @@ func transferRequest(requestURL string) ([]byte, string, error) {
|
|||
}
|
||||
|
||||
// poll the endpoint for the request resource, waiting for the user interaction
|
||||
func poll(client *http.Client, requestURL string) ([]byte, string, error) {
|
||||
func poll(client *http.Client, requestURL string, logger logger.Service) ([]byte, string, error) {
|
||||
resp, err := client.Get(requestURL)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
|
|
|
@ -1,34 +1,38 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/awsuploader"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/dbconnect"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/hello"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/metrics"
|
||||
"github.com/cloudflare/cloudflared/origin"
|
||||
"github.com/cloudflare/cloudflared/signal"
|
||||
"github.com/cloudflare/cloudflared/socks"
|
||||
"github.com/cloudflare/cloudflared/sshlog"
|
||||
"github.com/cloudflare/cloudflared/sshserver"
|
||||
"github.com/cloudflare/cloudflared/supervisor"
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
"github.com/cloudflare/cloudflared/tunneldns"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
|
||||
"github.com/coreos/go-systemd/daemon"
|
||||
|
@ -36,6 +40,7 @@ import (
|
|||
"github.com/getsentry/raven-go"
|
||||
"github.com/gliderlabs/ssh"
|
||||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
"gopkg.in/urfave/cli.v2/altsrc"
|
||||
|
@ -76,7 +81,19 @@ const (
|
|||
// hostKeyPath is the path of the dir to save SSH host keys too
|
||||
hostKeyPath = "host-key-path"
|
||||
|
||||
noIntentMsg = "The --intent argument is required. Cloudflared looks up an Intent to determine what configuration to use (i.e. which tunnels to start). If you don't have any Intents yet, you can use a placeholder Intent Label for now. Then, when you make an Intent with that label, cloudflared will get notified and open the tunnels you specified in that Intent."
|
||||
//sshServerFlag enables cloudflared ssh proxy server
|
||||
sshServerFlag = "ssh-server"
|
||||
|
||||
// socks5Flag is to enable the socks server to deframe
|
||||
socks5Flag = "socks5"
|
||||
|
||||
// bastionFlag is to enable bastion, or jump host, operation
|
||||
bastionFlag = "bastion"
|
||||
|
||||
logDirectoryFlag = "log-directory"
|
||||
|
||||
debugLevelWarning = "At debug level, request URL, method, protocol, content legnth and header will be logged. " +
|
||||
"Response status, content length and header will also be logged in debug level."
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -93,7 +110,7 @@ func Commands() []*cli.Command {
|
|||
cmds := []*cli.Command{
|
||||
{
|
||||
Name: "login",
|
||||
Action: login,
|
||||
Action: cliutil.ErrorHandler(login),
|
||||
Usage: "Generate a configuration file with your login details",
|
||||
ArgsUsage: " ",
|
||||
Flags: []cli.Flag{
|
||||
|
@ -106,7 +123,7 @@ func Commands() []*cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "proxy-dns",
|
||||
Action: tunneldns.Run,
|
||||
Action: cliutil.ErrorHandler(tunneldns.Run),
|
||||
Usage: "Run a DNS over HTTPS proxy server.",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
|
@ -133,6 +150,12 @@ func Commands() []*cli.Command {
|
|||
Value: cli.NewStringSlice("https://1.1.1.1/dns-query", "https://1.0.0.1/dns-query"),
|
||||
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "bootstrap",
|
||||
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
|
||||
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
|
||||
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
|
||||
},
|
||||
},
|
||||
ArgsUsage: " ", // can't be the empty string or we get the default output
|
||||
Hidden: false,
|
||||
|
@ -146,14 +169,18 @@ func Commands() []*cli.Command {
|
|||
c.Hidden = false
|
||||
subcommands = append(subcommands, &c)
|
||||
}
|
||||
subcommands = append(subcommands, buildCreateCommand())
|
||||
subcommands = append(subcommands, buildListCommand())
|
||||
subcommands = append(subcommands, buildDeleteCommand())
|
||||
subcommands = append(subcommands, buildRunCommand())
|
||||
|
||||
cmds = append(cmds, &cli.Command{
|
||||
Name: "tunnel",
|
||||
Action: tunnel,
|
||||
Action: cliutil.ErrorHandler(tunnel),
|
||||
Before: Before,
|
||||
Category: "Tunnel",
|
||||
Usage: "Make a locally-running web service accessible over the internet using Argo Tunnel.",
|
||||
ArgsUsage: "[origin-url]",
|
||||
ArgsUsage: " ",
|
||||
Description: `Argo Tunnel asks you to specify a hostname on a Cloudflare-powered
|
||||
domain you control and a local address. Traffic from that hostname is routed
|
||||
(optionally via a Cloudflare Load Balancer) to this machine and appears on the
|
||||
|
@ -180,14 +207,43 @@ 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{}) {
|
||||
version, shutdownC, graceShutdownC = v, s, g
|
||||
}
|
||||
|
||||
func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan struct{}) error {
|
||||
func createLogger(c *cli.Context, isTransport bool) (logger.Service, error) {
|
||||
loggerOpts := []logger.Option{}
|
||||
|
||||
logPath := c.String("logfile")
|
||||
if logPath == "" {
|
||||
logPath = c.String(logDirectoryFlag)
|
||||
}
|
||||
|
||||
if logPath != "" {
|
||||
loggerOpts = append(loggerOpts, logger.DefaultFile(logPath))
|
||||
}
|
||||
|
||||
logLevel := c.String("loglevel")
|
||||
if isTransport {
|
||||
logLevel = c.String("transport-loglevel")
|
||||
if logLevel == "" {
|
||||
logLevel = "fatal"
|
||||
}
|
||||
}
|
||||
loggerOpts = append(loggerOpts, logger.LogLevelString(logLevel))
|
||||
|
||||
return logger.New(loggerOpts...)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
_ = raven.SetDSN(sentryDSN)
|
||||
var wg sync.WaitGroup
|
||||
listeners := gracenet.Net{}
|
||||
|
@ -196,64 +252,45 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
dnsReadySignal := make(chan struct{})
|
||||
|
||||
if c.String("config") == "" {
|
||||
logger.Warnf("Cannot determine default configuration path. No file %v in %v", config.DefaultConfigFiles, config.DefaultConfigDirs)
|
||||
}
|
||||
|
||||
if err := configMainLogger(c); err != nil {
|
||||
return errors.Wrap(err, "Error configuring logger")
|
||||
}
|
||||
|
||||
transportLogger, err := configTransportLogger(c)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error configuring transport logger")
|
||||
logger.Infof("Cannot determine default configuration path. No file %v in %v", config.DefaultConfigFiles, config.DefaultConfigDirs)
|
||||
}
|
||||
|
||||
if c.IsSet("trace-output") {
|
||||
tmpTraceFile, err := ioutil.TempFile("", "trace")
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Failed to create new temporary file to save trace output")
|
||||
logger.Errorf("Failed to create new temporary file to save trace output: %s", err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := tmpTraceFile.Close(); err != nil {
|
||||
logger.WithError(err).Errorf("Failed to close trace output file %s", tmpTraceFile.Name())
|
||||
logger.Errorf("Failed to close trace output file %s with error: %s", tmpTraceFile.Name(), err)
|
||||
}
|
||||
if err := os.Rename(tmpTraceFile.Name(), c.String("trace-output")); err != nil {
|
||||
logger.WithError(err).Errorf("Failed to rename temporary trace output file %s to %s", tmpTraceFile.Name(), c.String("trace-output"))
|
||||
logger.Errorf("Failed to rename temporary trace output file %s to %s with error: %s", tmpTraceFile.Name(), c.String("trace-output"), err)
|
||||
} else {
|
||||
err := os.Remove(tmpTraceFile.Name())
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Failed to remove the temporary trace file %s", tmpTraceFile.Name())
|
||||
logger.Errorf("Failed to remove the temporary trace file %s with error: %s", tmpTraceFile.Name(), err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := trace.Start(tmpTraceFile); err != nil {
|
||||
logger.WithError(err).Error("Failed to start trace")
|
||||
logger.Errorf("Failed to start trace: %s", err)
|
||||
return errors.Wrap(err, "Error starting tracing")
|
||||
}
|
||||
defer trace.Stop()
|
||||
}
|
||||
|
||||
if c.String("logfile") != "" {
|
||||
if err := initLogFile(c, logger, transportLogger); err != nil {
|
||||
logger.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := handleDeprecatedOptions(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
buildInfo := buildinfo.GetBuildInfo(version)
|
||||
buildInfo.Log(logger)
|
||||
logClientOptions(c)
|
||||
logClientOptions(c, logger)
|
||||
|
||||
if c.IsSet("proxy-dns") {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errC <- runDNSProxyServer(c, dnsReadySignal, shutdownC)
|
||||
errC <- runDNSProxyServer(c, dnsReadySignal, shutdownC, logger)
|
||||
}()
|
||||
} else {
|
||||
close(dnsReadySignal)
|
||||
|
@ -264,7 +301,7 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
|
||||
metricsListener, err := listeners.Listen("tcp", c.String("metrics"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Error opening metrics server listener")
|
||||
logger.Errorf("Error opening metrics server listener: %s", err)
|
||||
return errors.Wrap(err, "Error opening metrics server listener")
|
||||
}
|
||||
defer metricsListener.Close()
|
||||
|
@ -276,12 +313,12 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
|
||||
go notifySystemd(connectedSignal)
|
||||
if c.IsSet("pidfile") {
|
||||
go writePidFile(connectedSignal, c.String("pidfile"))
|
||||
go writePidFile(connectedSignal, c.String("pidfile"), logger)
|
||||
}
|
||||
|
||||
cloudflaredID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Cannot generate cloudflared ID")
|
||||
logger.Errorf("Cannot generate cloudflared ID: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -291,17 +328,13 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
cancel()
|
||||
}()
|
||||
|
||||
if c.IsSet("use-declarative-tunnels") {
|
||||
return startDeclarativeTunnel(ctx, c, cloudflaredID, buildInfo, &listeners)
|
||||
}
|
||||
|
||||
// update needs to be after DNS proxy is up to resolve equinox server address
|
||||
if updater.IsAutoupdateEnabled(c) {
|
||||
if updater.IsAutoupdateEnabled(c, logger) {
|
||||
logger.Infof("Autoupdate frequency is set to %v", c.Duration("autoupdate-freq"))
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
autoupdater := updater.NewAutoUpdater(c.Duration("autoupdate-freq"), &listeners)
|
||||
autoupdater := updater.NewAutoUpdater(c.Duration("autoupdate-freq"), &listeners, logger)
|
||||
errC <- autoupdater.Run(ctx)
|
||||
}()
|
||||
}
|
||||
|
@ -310,14 +343,14 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
if dnsProxyStandAlone(c) {
|
||||
connectedSignal.Notify()
|
||||
// no grace period, handle SIGINT/SIGTERM immediately
|
||||
return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, 0)
|
||||
return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, 0, logger)
|
||||
}
|
||||
|
||||
if c.IsSet("hello-world") {
|
||||
logger.Infof("hello-world set")
|
||||
helloListener, err := hello.CreateTLSListener("127.0.0.1:")
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Cannot start Hello World Server")
|
||||
logger.Errorf("Cannot start Hello World Server: %s", err)
|
||||
return errors.Wrap(err, "Cannot start Hello World Server")
|
||||
}
|
||||
defer helloListener.Close()
|
||||
|
@ -329,12 +362,11 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
c.Set("url", "https://"+helloListener.Addr().String())
|
||||
}
|
||||
|
||||
if c.IsSet("ssh-server") {
|
||||
if c.IsSet(sshServerFlag) {
|
||||
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
|
||||
msg := fmt.Sprintf("--ssh-server is not supported on %s", runtime.GOOS)
|
||||
logger.Error(msg)
|
||||
return errors.New(msg)
|
||||
|
||||
}
|
||||
|
||||
logger.Infof("ssh-server set")
|
||||
|
@ -345,13 +377,13 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
c.String(accessKeyIDFlag), c.String(secretIDFlag), c.String(sessionTokenIDFlag), c.String(s3URLFlag))
|
||||
if err != nil {
|
||||
msg := "Cannot create uploader for SSH Server"
|
||||
logger.WithError(err).Error(msg)
|
||||
logger.Errorf("%s: %s", msg, err)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(sshLogFileDirectory, 0700); err != nil {
|
||||
msg := fmt.Sprintf("Cannot create SSH log file directory %s", sshLogFileDirectory)
|
||||
logger.WithError(err).Errorf(msg)
|
||||
logger.Errorf("%s: %s", msg, err)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
|
||||
|
@ -365,14 +397,14 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
server, err := sshserver.New(logManager, logger, version, localServerAddress, c.String("hostname"), c.Path(hostKeyPath), shutdownC, c.Duration(sshIdleTimeoutFlag), c.Duration(sshMaxTimeoutFlag))
|
||||
if err != nil {
|
||||
msg := "Cannot create new SSH Server"
|
||||
logger.WithError(err).Error(msg)
|
||||
logger.Errorf("%s: %s", msg, err)
|
||||
return errors.Wrap(err, msg)
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err = server.Start(); err != nil && err != ssh.ErrServerClosed {
|
||||
logger.WithError(err).Error("SSH server error")
|
||||
logger.Errorf("SSH server error: %s", err)
|
||||
// TODO: remove when declarative tunnels are implemented.
|
||||
close(shutdownC)
|
||||
}
|
||||
|
@ -380,46 +412,92 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
|||
c.Set("url", "ssh://"+localServerAddress)
|
||||
}
|
||||
|
||||
if host := hostnameFromURI(c.String("url")); host != "" {
|
||||
url := c.String("url")
|
||||
hostname := c.String("hostname")
|
||||
if url == hostname && url != "" && hostname != "" {
|
||||
errText := "hostname and url shouldn't match. See --help for more information"
|
||||
logger.Error(errText)
|
||||
return fmt.Errorf(errText)
|
||||
}
|
||||
|
||||
if staticHost := hostnameFromURI(c.String("url")); isProxyDestinationConfigured(staticHost, c) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:")
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Cannot start Websocket Proxy Server")
|
||||
logger.Errorf("Cannot start Websocket Proxy Server: %s", err)
|
||||
return errors.Wrap(err, "Cannot start Websocket Proxy Server")
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errC <- websocket.StartProxyServer(logger, listener, host, shutdownC)
|
||||
streamHandler := websocket.DefaultStreamHandler
|
||||
if c.IsSet(socks5Flag) {
|
||||
logger.Info("SOCKS5 server started")
|
||||
streamHandler = func(wsConn *websocket.Conn, remoteConn net.Conn, _ http.Header) {
|
||||
dialer := socks.NewConnDialer(remoteConn)
|
||||
requestHandler := socks.NewRequestHandler(dialer)
|
||||
socksServer := socks.NewConnectionHandler(requestHandler)
|
||||
|
||||
socksServer.Serve(wsConn)
|
||||
}
|
||||
} else if c.IsSet(sshServerFlag) {
|
||||
streamHandler = func(wsConn *websocket.Conn, remoteConn net.Conn, requestHeaders http.Header) {
|
||||
if finalDestination := requestHeaders.Get(h2mux.CFJumpDestinationHeader); finalDestination != "" {
|
||||
token := requestHeaders.Get(h2mux.CFAccessTokenHeader)
|
||||
if err := websocket.SendSSHPreamble(remoteConn, finalDestination, token); err != nil {
|
||||
logger.Errorf("Failed to send SSH preamble: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
websocket.DefaultStreamHandler(wsConn, remoteConn, requestHeaders)
|
||||
}
|
||||
}
|
||||
errC <- websocket.StartProxyServer(logger, listener, staticHost, shutdownC, streamHandler)
|
||||
}()
|
||||
c.Set("url", "http://"+listener.Addr().String())
|
||||
}
|
||||
|
||||
transportLogger, err := createLogger(c, true)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up transport logger")
|
||||
}
|
||||
|
||||
tunnelConfig, err := prepareTunnelConfig(c, buildInfo, version, logger, transportLogger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reconnectCh := make(chan origin.ReconnectSignal, 1)
|
||||
if c.IsSet("stdin-control") {
|
||||
logger.Info("Enabling control through stdin")
|
||||
go stdinControl(reconnectCh, logger)
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID)
|
||||
errC <- origin.StartTunnelDaemon(ctx, tunnelConfig, connectedSignal, cloudflaredID, reconnectCh, namedTunnel)
|
||||
}()
|
||||
|
||||
return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, c.Duration("grace-period"))
|
||||
return waitToShutdown(&wg, errC, shutdownC, graceShutdownC, c.Duration("grace-period"), logger)
|
||||
}
|
||||
|
||||
func Before(c *cli.Context) error {
|
||||
logger, err := createLogger(c, false)
|
||||
if err != nil {
|
||||
return cliutil.PrintLoggerSetupError("error setting up logger", err)
|
||||
}
|
||||
|
||||
if c.String("config") == "" {
|
||||
logger.Debugf("Cannot determine default configuration path. No file %v in %v", config.DefaultConfigFiles, config.DefaultConfigDirs)
|
||||
}
|
||||
inputSource, err := config.FindInputSourceContext(c)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Cannot load configuration from %s", c.String("config"))
|
||||
logger.Errorf("Cannot load configuration from %s: %s", c.String("config"), err)
|
||||
return err
|
||||
} else if inputSource != nil {
|
||||
err := altsrc.ApplyInputSourceValues(c, inputSource, c.App.Flags)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Cannot apply configuration from %s", c.String("config"))
|
||||
logger.Errorf("Cannot apply configuration from %s: %s", c.String("config"), err)
|
||||
return err
|
||||
}
|
||||
logger.Debugf("Applied configuration from %s", c.String("config"))
|
||||
|
@ -427,138 +505,27 @@ func Before(c *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func startDeclarativeTunnel(ctx context.Context,
|
||||
c *cli.Context,
|
||||
cloudflaredID uuid.UUID,
|
||||
buildInfo *buildinfo.BuildInfo,
|
||||
listeners *gracenet.Net,
|
||||
) error {
|
||||
reverseProxyOrigin, err := defaultOriginConfig(c)
|
||||
if err != nil {
|
||||
logger.WithError(err)
|
||||
return err
|
||||
}
|
||||
reverseProxyConfig, err := pogs.NewReverseProxyConfig(
|
||||
c.String("hostname"),
|
||||
reverseProxyOrigin,
|
||||
c.Uint64("retries"),
|
||||
c.Duration("proxy-connection-timeout"),
|
||||
c.Uint64("compression-quality"),
|
||||
)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Cannot initialize default client config because reverse proxy config is invalid")
|
||||
return err
|
||||
}
|
||||
defaultClientConfig := &pogs.ClientConfig{
|
||||
Version: pogs.InitVersion(),
|
||||
SupervisorConfig: &pogs.SupervisorConfig{
|
||||
AutoUpdateFrequency: c.Duration("autoupdate-freq"),
|
||||
MetricsUpdateFrequency: c.Duration("metrics-update-freq"),
|
||||
GracePeriod: c.Duration("grace-period"),
|
||||
},
|
||||
EdgeConnectionConfig: &pogs.EdgeConnectionConfig{
|
||||
NumHAConnections: uint8(c.Int("ha-connections")),
|
||||
HeartbeatInterval: c.Duration("heartbeat-interval"),
|
||||
Timeout: c.Duration("dial-edge-timeout"),
|
||||
MaxFailedHeartbeats: c.Uint64("heartbeat-count"),
|
||||
},
|
||||
DoHProxyConfigs: []*pogs.DoHProxyConfig{},
|
||||
ReverseProxyConfigs: []*pogs.ReverseProxyConfig{reverseProxyConfig},
|
||||
}
|
||||
|
||||
autoupdater := updater.NewAutoUpdater(defaultClientConfig.SupervisorConfig.AutoUpdateFrequency, listeners)
|
||||
|
||||
originCert, err := getOriginCert(c)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("error getting origin cert")
|
||||
return err
|
||||
}
|
||||
toEdgeTLSConfig, err := tlsconfig.CreateTunnelConfig(c)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("unable to create TLS config to connect with edge")
|
||||
return err
|
||||
}
|
||||
|
||||
tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("unable to parse tag")
|
||||
return err
|
||||
}
|
||||
|
||||
intentLabel := c.String("intent")
|
||||
if intentLabel == "" {
|
||||
logger.Error("--intent was empty")
|
||||
return fmt.Errorf(noIntentMsg)
|
||||
}
|
||||
|
||||
cloudflaredConfig := &connection.CloudflaredConfig{
|
||||
BuildInfo: buildInfo,
|
||||
CloudflaredID: cloudflaredID,
|
||||
IntentLabel: intentLabel,
|
||||
Tags: tags,
|
||||
}
|
||||
|
||||
serviceDiscoverer, err := serviceDiscoverer(c, logger)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("unable to create service discoverer")
|
||||
return err
|
||||
}
|
||||
supervisor, err := supervisor.NewSupervisor(defaultClientConfig, originCert, toEdgeTLSConfig,
|
||||
serviceDiscoverer, cloudflaredConfig, autoupdater, updater.SupportAutoUpdate(), logger)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("unable to create Supervisor")
|
||||
return err
|
||||
}
|
||||
return supervisor.Run(ctx)
|
||||
}
|
||||
|
||||
func defaultOriginConfig(c *cli.Context) (pogs.OriginConfig, error) {
|
||||
if c.IsSet("hello-world") {
|
||||
return &pogs.HelloWorldOriginConfig{}, nil
|
||||
}
|
||||
originConfig := &pogs.HTTPOriginConfig{
|
||||
TCPKeepAlive: c.Duration("proxy-tcp-keepalive"),
|
||||
DialDualStack: !c.Bool("proxy-no-happy-eyeballs"),
|
||||
TLSHandshakeTimeout: c.Duration("proxy-tls-timeout"),
|
||||
TLSVerify: !c.Bool("no-tls-verify"),
|
||||
OriginCAPool: c.String("origin-ca-pool"),
|
||||
OriginServerName: c.String("origin-server-name"),
|
||||
MaxIdleConnections: c.Uint64("proxy-keepalive-connections"),
|
||||
IdleConnectionTimeout: c.Duration("proxy-keepalive-timeout"),
|
||||
ProxyConnectionTimeout: c.Duration("proxy-connection-timeout"),
|
||||
ExpectContinueTimeout: c.Duration("proxy-expect-continue-timeout"),
|
||||
ChunkedEncoding: c.Bool("no-chunked-encoding"),
|
||||
}
|
||||
if c.IsSet("unix-socket") {
|
||||
unixSocket, err := config.ValidateUnixSocket(c)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error validating --unix-socket")
|
||||
}
|
||||
originConfig.URLString = unixSocket
|
||||
}
|
||||
originAddr, err := config.ValidateUrl(c)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error validating origin URL")
|
||||
}
|
||||
originConfig.URLString = originAddr
|
||||
return originConfig, nil
|
||||
// isProxyDestinationConfigured returns true if there is a static host set or if bastion mode is set.
|
||||
func isProxyDestinationConfigured(staticHost string, c *cli.Context) bool {
|
||||
return staticHost != "" || c.IsSet(bastionFlag)
|
||||
}
|
||||
|
||||
func waitToShutdown(wg *sync.WaitGroup,
|
||||
errC chan error,
|
||||
shutdownC, graceShutdownC chan struct{},
|
||||
gracePeriod time.Duration,
|
||||
logger logger.Service,
|
||||
) error {
|
||||
var err error
|
||||
if gracePeriod > 0 {
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceShutdownC, gracePeriod)
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceShutdownC, gracePeriod, logger)
|
||||
} else {
|
||||
err = waitForSignal(errC, shutdownC)
|
||||
err = waitForSignal(errC, shutdownC, logger)
|
||||
close(graceShutdownC)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Quitting due to error")
|
||||
logger.Errorf("Quitting due to error: %s", err)
|
||||
} else {
|
||||
logger.Info("Quitting...")
|
||||
}
|
||||
|
@ -576,11 +543,17 @@ func notifySystemd(waitForSignal *signal.Signal) {
|
|||
daemon.SdNotify(false, "READY=1")
|
||||
}
|
||||
|
||||
func writePidFile(waitForSignal *signal.Signal, pidFile string) {
|
||||
func writePidFile(waitForSignal *signal.Signal, pidFile string, logger logger.Service) {
|
||||
<-waitForSignal.Wait()
|
||||
file, err := os.Create(pidFile)
|
||||
expandedPath, err := homedir.Expand(pidFile)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Unable to write pid to %s", pidFile)
|
||||
logger.Errorf("Unable to expand %s, try to use absolute path in --pidfile: %s", pidFile, err)
|
||||
return
|
||||
}
|
||||
file, err := os.Create(expandedPath)
|
||||
if err != nil {
|
||||
logger.Errorf("Unable to write pid to %s: %s", expandedPath, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
fmt.Fprintf(file, "%d", os.Getpid())
|
||||
|
@ -596,6 +569,10 @@ func hostnameFromURI(uri string) string {
|
|||
return addPortIfMissing(u, 22)
|
||||
case "rdp":
|
||||
return addPortIfMissing(u, 3389)
|
||||
case "smb":
|
||||
return addPortIfMissing(u, 445)
|
||||
case "tcp":
|
||||
return addPortIfMissing(u, 7864) // just a random port since there isn't a default in this case
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
@ -623,13 +600,13 @@ func dbConnectCmd() *cli.Command {
|
|||
}
|
||||
|
||||
// Override action to setup the Proxy, then if successful, start the tunnel daemon.
|
||||
cmd.Action = func(c *cli.Context) error {
|
||||
cmd.Action = cliutil.ErrorHandler(func(c *cli.Context) error {
|
||||
err := dbconnect.CmdAction(c)
|
||||
if err == nil {
|
||||
err = tunnel(c)
|
||||
}
|
||||
return err
|
||||
}
|
||||
})
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
@ -690,7 +667,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
}),
|
||||
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
|
||||
Name: "edge",
|
||||
Usage: "Address of the Cloudflare tunnel server.",
|
||||
Usage: "Address of the Cloudflare tunnel server. Only works in Cloudflare's internal testing environment.",
|
||||
EnvVars: []string{"TUNNEL_EDGE"},
|
||||
Hidden: true,
|
||||
}),
|
||||
|
@ -780,6 +757,13 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
EnvVars: []string{"TUNNEL_API_CA_KEY"},
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "api-url",
|
||||
Usage: "Base URL for Cloudflare API v4",
|
||||
EnvVars: []string{"TUNNEL_API_URL"},
|
||||
Value: "https://api.cloudflare.com/client/v4",
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "metrics",
|
||||
Value: "localhost:",
|
||||
|
@ -815,15 +799,15 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "loglevel",
|
||||
Value: "info",
|
||||
Usage: "Application logging level {panic, fatal, error, warn, info, debug}. " + debugLevelWarning,
|
||||
Usage: "Application logging level {fatal, error, info, debug}. " + debugLevelWarning,
|
||||
EnvVars: []string{"TUNNEL_LOGLEVEL"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "transport-loglevel",
|
||||
Aliases: []string{"proto-loglevel"}, // This flag used to be called proto-loglevel
|
||||
Value: "warn",
|
||||
Usage: "Transport logging level(previously called protocol logging level) {panic, fatal, error, warn, info, debug}",
|
||||
Value: "fatal",
|
||||
Usage: "Transport logging level(previously called protocol logging level) {fatal, error, info, debug}",
|
||||
EnvVars: []string{"TUNNEL_PROTO_LOGLEVEL", "TUNNEL_TRANSPORT_LOGLEVEL"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
|
@ -842,7 +826,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: "ssh-server",
|
||||
Name: sshServerFlag,
|
||||
Value: false,
|
||||
Usage: "Run an SSH Server",
|
||||
EnvVars: []string{"TUNNEL_SSH_SERVER"},
|
||||
|
@ -860,6 +844,12 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
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,
|
||||
|
@ -939,6 +929,13 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
EnvVars: []string{"TUNNEL_DNS_UPSTREAM"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewStringSliceFlag(&cli.StringSliceFlag{
|
||||
Name: "proxy-dns-bootstrap",
|
||||
Usage: "bootstrap endpoint URL, you can specify multiple endpoints for redundancy.",
|
||||
Value: cli.NewStringSlice("https://162.159.36.1/dns-query", "https://162.159.46.1/dns-query", "https://[2606:4700:4700::1111]/dns-query", "https://[2606:4700:4700::1001]/dns-query"),
|
||||
EnvVars: []string{"TUNNEL_DNS_BOOTSTRAP"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewDurationFlag(&cli.DurationFlag{
|
||||
Name: "grace-period",
|
||||
Usage: "Duration to accept new requests after cloudflared receives first SIGINT/SIGTERM. A second SIGINT/SIGTERM will force cloudflared to shutdown immediately.",
|
||||
|
@ -966,21 +963,17 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: "use-declarative-tunnels",
|
||||
Usage: "Test establishing connections with declarative tunnel methods.",
|
||||
EnvVars: []string{"TUNNEL_USE_DECLARATIVE"},
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: "intent",
|
||||
Usage: "The label of an Intent from which `cloudflared` should gets its tunnels from. Intents can be created in the Origin Registry UI.",
|
||||
EnvVars: []string{"TUNNEL_INTENT"},
|
||||
Name: "use-reconnect-token",
|
||||
Usage: "Test reestablishing connections with the new 'reconnect token' flow.",
|
||||
Value: true,
|
||||
EnvVars: []string{"TUNNEL_USE_RECONNECT_TOKEN"},
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: "use-reconnect-token",
|
||||
Usage: "Test reestablishing connections with the new 'reconnect token' flow.",
|
||||
EnvVars: []string{"TUNNEL_USE_RECONNECT_TOKEN"},
|
||||
Name: "use-quick-reconnects",
|
||||
Usage: "Test reestablishing connections with the new 'connection digest' flow.",
|
||||
Value: true,
|
||||
EnvVars: []string{"TUNNEL_USE_QUICK_RECONNECTS"},
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewDurationFlag(&cli.DurationFlag{
|
||||
|
@ -1051,5 +1044,59 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
|||
EnvVars: []string{"HOST_KEY_PATH"},
|
||||
Hidden: true,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: "stdin-control",
|
||||
Usage: "Control the process using commands sent through stdin",
|
||||
EnvVars: []string{"STDIN-CONTROL"},
|
||||
Hidden: true,
|
||||
Value: false,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: socks5Flag,
|
||||
Usage: "specify if this tunnel is running as a SOCK5 Server",
|
||||
EnvVars: []string{"TUNNEL_SOCKS"},
|
||||
Value: false,
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: bastionFlag,
|
||||
Value: false,
|
||||
Usage: "Runs as jump host",
|
||||
EnvVars: []string{"TUNNEL_BASTION"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
func stdinControl(reconnectCh chan origin.ReconnectSignal, logger logger.Service) {
|
||||
for {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
for scanner.Scan() {
|
||||
command := scanner.Text()
|
||||
parts := strings.SplitN(command, " ", 2)
|
||||
|
||||
switch parts[0] {
|
||||
case "":
|
||||
break
|
||||
case "reconnect":
|
||||
var reconnect origin.ReconnectSignal
|
||||
if len(parts) > 1 {
|
||||
var err error
|
||||
if reconnect.Delay, err = time.ParseDuration(parts[1]); err != nil {
|
||||
logger.Error(err.Error())
|
||||
continue
|
||||
}
|
||||
}
|
||||
logger.Infof("Sending reconnect signal %+v", reconnect)
|
||||
reconnectCh <- reconnect
|
||||
default:
|
||||
logger.Infof("Unknown command: %s", command)
|
||||
fallthrough
|
||||
case "help":
|
||||
logger.Info(`Supported command:
|
||||
reconnect [delay]
|
||||
- restarts one randomly chosen connection with optional delay before reconnect`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/origin"
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
|
@ -23,7 +23,6 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
@ -47,25 +46,16 @@ func findDefaultOriginCertPath() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func generateRandomClientID(logger *logrus.Logger) (string, error) {
|
||||
func generateRandomClientID(logger logger.Service) (string, error) {
|
||||
u, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("couldn't create UUID for client ID")
|
||||
logger.Errorf("couldn't create UUID for client ID %s", err)
|
||||
return "", err
|
||||
}
|
||||
return u.String(), nil
|
||||
}
|
||||
|
||||
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") {
|
||||
logger.Error("You don't need to give us your api-key anymore. Please use the new login method. Just run cloudflared login")
|
||||
return fmt.Errorf("Client provided deprecated options")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func logClientOptions(c *cli.Context) {
|
||||
func logClientOptions(c *cli.Context, logger logger.Service) {
|
||||
flags := make(map[string]interface{})
|
||||
for _, flag := range c.LocalFlagNames() {
|
||||
flags[flag] = c.Generic(flag)
|
||||
|
@ -79,7 +69,7 @@ func logClientOptions(c *cli.Context) {
|
|||
}
|
||||
|
||||
if len(flags) > 0 {
|
||||
logger.WithFields(flags).Info("Flags")
|
||||
logger.Infof("Environment variables %v", flags)
|
||||
}
|
||||
|
||||
envs := make(map[string]string)
|
||||
|
@ -102,27 +92,29 @@ func dnsProxyStandAlone(c *cli.Context) bool {
|
|||
return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world"))
|
||||
}
|
||||
|
||||
func getOriginCert(c *cli.Context) ([]byte, error) {
|
||||
if c.String("origincert") == "" {
|
||||
logger.Warnf("Cannot determine default origin certificate path. No file %s in %v", config.DefaultCredentialFile, config.DefaultConfigDirs)
|
||||
func findOriginCert(c *cli.Context, logger logger.Service) (string, error) {
|
||||
originCertPath := c.String("origincert")
|
||||
if originCertPath == "" {
|
||||
logger.Infof("Cannot determine default origin certificate path. No file %s in %v", config.DefaultCredentialFile, config.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")
|
||||
return "", fmt.Errorf("Client didn't specify origincert path when running from terminal")
|
||||
} else {
|
||||
logger.Errorf("You need to specify the origin certificate path by specifying the origincert option in the configuration file, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", serviceUrl)
|
||||
return nil, fmt.Errorf("Client didn't specify origincert path")
|
||||
return "", fmt.Errorf("Client didn't specify origincert path")
|
||||
}
|
||||
}
|
||||
// Check that the user has acquired a certificate using the login command
|
||||
originCertPath, err := homedir.Expand(c.String("origincert"))
|
||||
var err error
|
||||
originCertPath, err = homedir.Expand(originCertPath)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Cannot resolve path %s", c.String("origincert"))
|
||||
return nil, fmt.Errorf("Cannot resolve path %s", c.String("origincert"))
|
||||
logger.Errorf("Cannot resolve path %s: %s", originCertPath, err)
|
||||
return "", fmt.Errorf("Cannot resolve path %s", originCertPath)
|
||||
}
|
||||
// Check that the user has acquired a certificate using the login command
|
||||
ok, err := config.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"))
|
||||
logger.Errorf("Cannot check if origin cert exists at path %s", originCertPath)
|
||||
return "", fmt.Errorf("Cannot check if origin cert exists at path %s", originCertPath)
|
||||
}
|
||||
if !ok {
|
||||
logger.Errorf(`Cannot find a valid certificate for your origin at the path:
|
||||
|
@ -134,26 +126,42 @@ If you don't have a certificate signed by Cloudflare, run the command:
|
|||
|
||||
%s login
|
||||
`, originCertPath, os.Args[0])
|
||||
return nil, fmt.Errorf("Cannot find a valid certificate at the path %s", originCertPath)
|
||||
return "", fmt.Errorf("Cannot find a valid certificate at the path %s", originCertPath)
|
||||
}
|
||||
|
||||
return originCertPath, nil
|
||||
}
|
||||
|
||||
func readOriginCert(originCertPath string, logger logger.Service) ([]byte, error) {
|
||||
logger.Debugf("Reading origin cert from %s", originCertPath)
|
||||
|
||||
// Easier to send the certificate as []byte via RPC than decoding it at this point
|
||||
originCert, err := ioutil.ReadFile(originCertPath)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Cannot read %s to load origin certificate", originCertPath)
|
||||
logger.Errorf("Cannot read %s to load origin certificate: %s", originCertPath, err)
|
||||
return nil, fmt.Errorf("Cannot read %s to load origin certificate", originCertPath)
|
||||
}
|
||||
return originCert, nil
|
||||
}
|
||||
|
||||
func getOriginCert(c *cli.Context, logger logger.Service) ([]byte, error) {
|
||||
if originCertPath, err := findOriginCert(c, logger); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return readOriginCert(originCertPath, logger)
|
||||
}
|
||||
}
|
||||
|
||||
func prepareTunnelConfig(
|
||||
c *cli.Context,
|
||||
buildInfo *buildinfo.BuildInfo,
|
||||
version string, logger,
|
||||
transportLogger *logrus.Logger,
|
||||
version string,
|
||||
logger logger.Service,
|
||||
transportLogger logger.Service,
|
||||
) (*origin.TunnelConfig, error) {
|
||||
hostname, err := validation.ValidateHostname(c.String("hostname"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Invalid hostname")
|
||||
logger.Errorf("Invalid hostname: %s", err)
|
||||
return nil, errors.Wrap(err, "Invalid hostname")
|
||||
}
|
||||
isFreeTunnel := hostname == ""
|
||||
|
@ -167,7 +175,7 @@ func prepareTunnelConfig(
|
|||
|
||||
tags, err := NewTagSliceFromCLI(c.StringSlice("tag"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Tag parse failure")
|
||||
logger.Errorf("Tag parse failure: %s", err)
|
||||
return nil, errors.Wrap(err, "Tag parse failure")
|
||||
}
|
||||
|
||||
|
@ -175,13 +183,13 @@ func prepareTunnelConfig(
|
|||
|
||||
originURL, err := config.ValidateUrl(c)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Error validating origin URL")
|
||||
logger.Errorf("Error validating origin URL: %s", err)
|
||||
return nil, errors.Wrap(err, "Error validating origin URL")
|
||||
}
|
||||
|
||||
var originCert []byte
|
||||
if !isFreeTunnel {
|
||||
originCert, err = getOriginCert(c)
|
||||
originCert, err = getOriginCert(c, logger)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error getting origin cert")
|
||||
}
|
||||
|
@ -189,7 +197,7 @@ func prepareTunnelConfig(
|
|||
|
||||
originCertPool, err := tlsconfig.LoadOriginCA(c, logger)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Error loading cert pool")
|
||||
logger.Errorf("Error loading cert pool: %s", err)
|
||||
return nil, errors.Wrap(err, "Error loading cert pool")
|
||||
}
|
||||
|
||||
|
@ -216,7 +224,7 @@ func prepareTunnelConfig(
|
|||
if c.IsSet("unix-socket") {
|
||||
unixSocket, err := config.ValidateUnixSocket(c)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Error validating --unix-socket")
|
||||
logger.Errorf("Error validating --unix-socket: %s", err)
|
||||
return nil, errors.Wrap(err, "Error validating --unix-socket")
|
||||
}
|
||||
|
||||
|
@ -233,16 +241,16 @@ func prepareTunnelConfig(
|
|||
if !c.IsSet("hello-world") && c.IsSet("origin-server-name") {
|
||||
httpTransport.TLSClientConfig.ServerName = c.String("origin-server-name")
|
||||
}
|
||||
|
||||
err = validation.ValidateHTTPService(originURL, hostname, 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")
|
||||
// If tunnel running in bastion mode, a connection to origin will not exist until initiated by the client.
|
||||
if !c.IsSet(bastionFlag) {
|
||||
if err = validation.ValidateHTTPService(originURL, hostname, httpTransport); err != nil {
|
||||
logger.Errorf("unable to connect to the origin: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
toEdgeTLSConfig, err := tlsconfig.CreateTunnelConfig(c)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("unable to create TLS config to connect with edge")
|
||||
logger.Errorf("unable to create TLS config to connect with edge: %s", err)
|
||||
return nil, errors.Wrap(err, "unable to create TLS config to connect with edge")
|
||||
}
|
||||
|
||||
|
@ -263,6 +271,7 @@ func prepareTunnelConfig(
|
|||
IsFreeTunnel: isFreeTunnel,
|
||||
LBPool: c.String("lb-pool"),
|
||||
Logger: logger,
|
||||
TransportLogger: transportLogger,
|
||||
MaxHeartbeats: c.Uint64("heartbeat-count"),
|
||||
Metrics: tunnelMetrics,
|
||||
MetricsUpdateFreq: c.Duration("metrics-update-freq"),
|
||||
|
@ -274,21 +283,12 @@ func prepareTunnelConfig(
|
|||
RunFromTerminal: isRunningFromTerminal(),
|
||||
Tags: tags,
|
||||
TlsConfig: toEdgeTLSConfig,
|
||||
TransportLogger: transportLogger,
|
||||
UseDeclarativeTunnel: c.Bool("use-declarative-tunnels"),
|
||||
UseReconnectToken: c.Bool("use-reconnect-token"),
|
||||
UseQuickReconnects: c.Bool("use-quick-reconnects"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func serviceDiscoverer(c *cli.Context, logger *logrus.Logger) (*edgediscovery.Edge, error) {
|
||||
// If --edge is specfied, resolve edge server addresses
|
||||
if len(c.StringSlice("edge")) > 0 {
|
||||
return edgediscovery.StaticEdge(logger, c.StringSlice("edge"))
|
||||
}
|
||||
// Otherwise lookup edge server addresses through service discovery
|
||||
return edgediscovery.ResolveEdge(logger)
|
||||
}
|
||||
|
||||
func isRunningFromTerminal() bool {
|
||||
return terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
}
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
"github.com/rifflock/lfshook"
|
||||
"github.com/sirupsen/logrus"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const debugLevelWarning = "At debug level, request URL, method, protocol, content legnth and header will be logged. " +
|
||||
"Response status, content length and header will also be logged in debug level."
|
||||
|
||||
var logger = log.CreateLogger()
|
||||
|
||||
func configMainLogger(c *cli.Context) error {
|
||||
logLevel, err := logrus.ParseLevel(c.String("loglevel"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Unknown logging level specified")
|
||||
return errors.Wrap(err, "Unknown logging level specified")
|
||||
}
|
||||
logger.SetLevel(logLevel)
|
||||
if logLevel == logrus.DebugLevel {
|
||||
logger.Warn(debugLevelWarning)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func configTransportLogger(c *cli.Context) (*logrus.Logger, error) {
|
||||
transportLogLevel, err := logrus.ParseLevel(c.String("transport-loglevel"))
|
||||
if err != nil {
|
||||
logger.WithError(err).Fatal("Unknown transport logging level specified")
|
||||
return nil, errors.Wrap(err, "Unknown transport logging level specified")
|
||||
}
|
||||
transportLogger := logrus.New()
|
||||
transportLogger.Level = transportLogLevel
|
||||
return transportLogger, nil
|
||||
}
|
||||
|
||||
func initLogFile(c *cli.Context, loggers ...*logrus.Logger) error {
|
||||
filePath, err := homedir.Expand(c.String("logfile"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot resolve logfile path")
|
||||
}
|
||||
|
||||
fileMode := os.O_WRONLY | os.O_APPEND | os.O_CREATE | os.O_TRUNC
|
||||
// do not truncate log file if the client has been autoupdated
|
||||
if c.Bool("is-autoupdated") {
|
||||
fileMode = os.O_WRONLY | os.O_APPEND | os.O_CREATE
|
||||
}
|
||||
f, err := os.OpenFile(filePath, fileMode, 0664)
|
||||
if err != nil {
|
||||
errors.Wrap(err, fmt.Sprintf("Cannot open file %s", filePath))
|
||||
}
|
||||
defer f.Close()
|
||||
pathMap := lfshook.PathMap{
|
||||
logrus.DebugLevel: filePath,
|
||||
logrus.WarnLevel: filePath,
|
||||
logrus.InfoLevel: filePath,
|
||||
logrus.ErrorLevel: filePath,
|
||||
logrus.FatalLevel: filePath,
|
||||
logrus.PanicLevel: filePath,
|
||||
}
|
||||
|
||||
for _, l := range loggers {
|
||||
l.Hooks.Add(lfshook.NewHook(pathMap, &logrus.JSONFormatter{}))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -9,7 +9,9 @@ import (
|
|||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/transfer"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
cli "gopkg.in/urfave/cli.v2"
|
||||
)
|
||||
|
||||
|
@ -19,6 +21,11 @@ const (
|
|||
)
|
||||
|
||||
func login(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
path, ok, err := checkForExistingCert()
|
||||
if ok {
|
||||
fmt.Fprintf(os.Stdout, "You have an existing certificate at %s which login would overwrite.\nIf this is intentional, please move or delete that file then run this command again.\n", path)
|
||||
|
@ -33,7 +40,7 @@ func login(c *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
_, err = transfer.Run(loginURL, "cert", "callback", callbackStoreURL, path, false)
|
||||
_, err = transfer.Run(loginURL, "cert", "callback", callbackStoreURL, path, false, true, logger)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write the certificate due to the following error:\n%v\n\nYour browser will download the certificate instead. You will have to manually\ncopy it to the following path:\n\n%s\n", err, path)
|
||||
return err
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/tunneldns"
|
||||
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
|
@ -8,23 +9,20 @@ import (
|
|||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func runDNSProxyServer(c *cli.Context, dnsReadySignal, shutdownC chan struct{}) error {
|
||||
func runDNSProxyServer(c *cli.Context, dnsReadySignal, shutdownC chan struct{}, logger logger.Service) error {
|
||||
port := c.Int("proxy-dns-port")
|
||||
if port <= 0 || port > 65535 {
|
||||
logger.Errorf("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
|
||||
return errors.New("The 'proxy-dns-port' must be a valid port number in <1, 65535> range.")
|
||||
}
|
||||
listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream"))
|
||||
listener, err := tunneldns.CreateListener(c.String("proxy-dns-address"), uint16(port), c.StringSlice("proxy-dns-upstream"), c.StringSlice("proxy-dns-bootstrap"), logger)
|
||||
if err != nil {
|
||||
close(dnsReadySignal)
|
||||
listener.Stop()
|
||||
logger.WithError(err).Error("Cannot create the DNS over HTTPS proxy server")
|
||||
return errors.Wrap(err, "Cannot create the DNS over HTTPS proxy server")
|
||||
}
|
||||
|
||||
err = listener.Start(dnsReadySignal)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Cannot start the DNS over HTTPS proxy server")
|
||||
return errors.Wrap(err, "Cannot start the DNS over HTTPS proxy server")
|
||||
}
|
||||
<-shutdownC
|
||||
|
|
|
@ -5,21 +5,25 @@ import (
|
|||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
// waitForSignal notifies all routines to shutdownC immediately by closing the
|
||||
// shutdownC when one of the routines in main exits, or when this process receives
|
||||
// SIGTERM/SIGINT
|
||||
func waitForSignal(errC chan error, shutdownC chan struct{}) error {
|
||||
func waitForSignal(errC chan error, shutdownC chan struct{}, logger logger.Service) error {
|
||||
signals := make(chan os.Signal, 10)
|
||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
||||
defer signal.Stop(signals)
|
||||
|
||||
select {
|
||||
case err := <-errC:
|
||||
logger.Infof("terminating due to error: %v", err)
|
||||
close(shutdownC)
|
||||
return err
|
||||
case <-signals:
|
||||
case s := <-signals:
|
||||
logger.Infof("terminating due to signal %s", s)
|
||||
close(shutdownC)
|
||||
case <-shutdownC:
|
||||
}
|
||||
|
@ -37,6 +41,7 @@ func waitForSignal(errC chan error, shutdownC chan struct{}) error {
|
|||
func waitForSignalWithGraceShutdown(errC chan error,
|
||||
shutdownC, graceShutdownC chan struct{},
|
||||
gracePeriod time.Duration,
|
||||
logger logger.Service,
|
||||
) error {
|
||||
signals := make(chan os.Signal, 10)
|
||||
signal.Notify(signals, syscall.SIGTERM, syscall.SIGINT)
|
||||
|
@ -44,14 +49,16 @@ func waitForSignalWithGraceShutdown(errC chan error,
|
|||
|
||||
select {
|
||||
case err := <-errC:
|
||||
logger.Infof("Initiating graceful shutdown due to %v ...", err)
|
||||
close(graceShutdownC)
|
||||
close(shutdownC)
|
||||
return err
|
||||
case <-signals:
|
||||
case s := <-signals:
|
||||
logger.Infof("Initiating graceful shutdown due to signal %s ...", s)
|
||||
close(graceShutdownC)
|
||||
waitForGracePeriod(signals, errC, shutdownC, gracePeriod)
|
||||
waitForGracePeriod(signals, errC, shutdownC, gracePeriod, logger)
|
||||
case <-graceShutdownC:
|
||||
waitForGracePeriod(signals, errC, shutdownC, gracePeriod)
|
||||
waitForGracePeriod(signals, errC, shutdownC, gracePeriod, logger)
|
||||
case <-shutdownC:
|
||||
close(graceShutdownC)
|
||||
}
|
||||
|
@ -63,8 +70,8 @@ func waitForGracePeriod(signals chan os.Signal,
|
|||
errC chan error,
|
||||
shutdownC chan struct{},
|
||||
gracePeriod time.Duration,
|
||||
logger logger.Service,
|
||||
) {
|
||||
logger.Infof("Initiating graceful shutdown...")
|
||||
// Unregister signal handler early, so the client can send a second SIGTERM/SIGINT
|
||||
// to force shutdown cloudflared
|
||||
signal.Stop(signals)
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -27,6 +28,8 @@ func testChannelClosed(t *testing.T, c chan struct{}) {
|
|||
}
|
||||
|
||||
func TestWaitForSignal(t *testing.T) {
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
// Test handling server error
|
||||
errC := make(chan error)
|
||||
shutdownC := make(chan struct{})
|
||||
|
@ -36,7 +39,7 @@ func TestWaitForSignal(t *testing.T) {
|
|||
}()
|
||||
|
||||
// received error, shutdownC should be closed
|
||||
err := waitForSignal(errC, shutdownC)
|
||||
err := waitForSignal(errC, shutdownC, logger)
|
||||
assert.Equal(t, serverErr, err)
|
||||
testChannelClosed(t, shutdownC)
|
||||
|
||||
|
@ -56,7 +59,7 @@ func TestWaitForSignal(t *testing.T) {
|
|||
syscall.Kill(syscall.Getpid(), sig)
|
||||
}(sig)
|
||||
|
||||
err = waitForSignal(errC, shutdownC)
|
||||
err = waitForSignal(errC, shutdownC, logger)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, shutdownErr, <-errC)
|
||||
testChannelClosed(t, shutdownC)
|
||||
|
@ -73,8 +76,10 @@ func TestWaitForSignalWithGraceShutdown(t *testing.T) {
|
|||
errC <- serverErr
|
||||
}()
|
||||
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
// received error, both shutdownC and graceshutdownC should be closed
|
||||
err := waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
|
||||
err := waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick, logger)
|
||||
assert.Equal(t, serverErr, err)
|
||||
testChannelClosed(t, shutdownC)
|
||||
testChannelClosed(t, graceshutdownC)
|
||||
|
@ -84,7 +89,7 @@ func TestWaitForSignalWithGraceShutdown(t *testing.T) {
|
|||
shutdownC = make(chan struct{})
|
||||
graceshutdownC = make(chan struct{})
|
||||
close(shutdownC)
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick, logger)
|
||||
assert.NoError(t, err)
|
||||
testChannelClosed(t, shutdownC)
|
||||
testChannelClosed(t, graceshutdownC)
|
||||
|
@ -94,7 +99,7 @@ func TestWaitForSignalWithGraceShutdown(t *testing.T) {
|
|||
shutdownC = make(chan struct{})
|
||||
graceshutdownC = make(chan struct{})
|
||||
close(graceshutdownC)
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick, logger)
|
||||
assert.NoError(t, err)
|
||||
testChannelClosed(t, shutdownC)
|
||||
testChannelClosed(t, graceshutdownC)
|
||||
|
@ -117,7 +122,7 @@ func TestWaitForSignalWithGraceShutdown(t *testing.T) {
|
|||
syscall.Kill(syscall.Getpid(), sig)
|
||||
}(sig)
|
||||
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick, logger)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, graceShutdownErr, <-errC)
|
||||
testChannelClosed(t, shutdownC)
|
||||
|
@ -143,7 +148,7 @@ func TestWaitForSignalWithGraceShutdown(t *testing.T) {
|
|||
syscall.Kill(syscall.Getpid(), sig)
|
||||
}(sig)
|
||||
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick)
|
||||
err = waitForSignalWithGraceShutdown(errC, shutdownC, graceshutdownC, tick, logger)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, shutdownErr, <-errC)
|
||||
testChannelClosed(t, shutdownC)
|
||||
|
|
|
@ -0,0 +1,344 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
"gopkg.in/yaml.v2"
|
||||
|
||||
"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
|
||||
|
||||
func buildCreateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Action: cliutil.ErrorHandler(createTunnel),
|
||||
Usage: "Create a new tunnel with given name",
|
||||
ArgsUsage: "TUNNEL-NAME",
|
||||
Hidden: hideSubcommands,
|
||||
Flags: []cli.Flag{outputFormatFlag},
|
||||
}
|
||||
}
|
||||
|
||||
// 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.`)
|
||||
}
|
||||
name := c.Args().First()
|
||||
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
tunnelSecret, err := generateTunnelSecret()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
logger.Infof("Created tunnel %s with id %s", tunnel.Name, tunnel.ID)
|
||||
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",
|
||||
Action: cliutil.ErrorHandler(listTunnels),
|
||||
Usage: "List existing tunnels",
|
||||
ArgsUsage: " ",
|
||||
Hidden: hideSubcommands,
|
||||
Flags: []cli.Flag{outputFormatFlag, showDeletedFlag},
|
||||
}
|
||||
}
|
||||
|
||||
func listTunnels(c *cli.Context) error {
|
||||
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")
|
||||
}
|
||||
cert, err := getOriginCertFromContext(originCertPath, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client := newTunnelstoreClient(c, cert, logger)
|
||||
|
||||
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)
|
||||
}
|
||||
if len(tunnels) > 0 {
|
||||
const listFormat = "%-40s%-30s%-30s%s\n"
|
||||
fmt.Printf(listFormat, "ID", "NAME", "CREATED", "CONNECTIONS")
|
||||
for _, t := range tunnels {
|
||||
fmt.Printf(listFormat, t.ID, t.Name, t.CreatedAt.Format(time.RFC3339), fmtConnections(t.Connections))
|
||||
}
|
||||
} else {
|
||||
fmt.Println("You have no tunnels, use 'cloudflared tunnel create' to define a new tunnel")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func fmtConnections(connections []tunnelstore.Connection) string {
|
||||
|
||||
// Count connections per colo
|
||||
numConnsPerColo := make(map[string]uint, len(connections))
|
||||
for _, connection := range connections {
|
||||
numConnsPerColo[connection.ColoName]++
|
||||
}
|
||||
|
||||
// Get sorted list of colos
|
||||
sortedColos := []string{}
|
||||
for coloName := range numConnsPerColo {
|
||||
sortedColos = append(sortedColos, coloName)
|
||||
}
|
||||
sort.Strings(sortedColos)
|
||||
|
||||
// Map each colo to its frequency, combine into output string.
|
||||
var output []string
|
||||
for _, coloName := range sortedColos {
|
||||
output = append(output, fmt.Sprintf("%dx%s", numConnsPerColo[coloName], coloName))
|
||||
}
|
||||
return strings.Join(output, ", ")
|
||||
}
|
||||
|
||||
func buildDeleteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Action: cliutil.ErrorHandler(deleteTunnel),
|
||||
Usage: "Delete existing tunnel with given ID",
|
||||
ArgsUsage: "TUNNEL-ID",
|
||||
Hidden: hideSubcommands,
|
||||
}
|
||||
}
|
||||
|
||||
func deleteTunnel(c *cli.Context) error {
|
||||
if c.NArg() != 1 {
|
||||
return cliutil.UsageError(`"cloudflared tunnel delete" requires exactly 1 argument, the ID of the tunnel to delete.`)
|
||||
}
|
||||
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")
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func renderOutput(format string, v interface{}) error {
|
||||
switch format {
|
||||
case "json":
|
||||
encoder := json.NewEncoder(os.Stdout)
|
||||
encoder.SetIndent("", " ")
|
||||
return encoder.Encode(v)
|
||||
case "yaml":
|
||||
return yaml.NewEncoder(os.Stdout).Encode(v)
|
||||
default:
|
||||
return errors.Errorf("Unknown output format '%s'", format)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, errors.Wrapf(err, "Can't read origin cert from %s", originCertPath)
|
||||
}
|
||||
|
||||
cert, err := certutil.DecodeOriginCert(blocks)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error decoding origin cert")
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
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})
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package tunnel
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_fmtConnections(t *testing.T) {
|
||||
type args struct {
|
||||
connections []tunnelstore.Connection
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "empty",
|
||||
args: args{
|
||||
connections: []tunnelstore.Connection{},
|
||||
},
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "trivial",
|
||||
args: args{
|
||||
connections: []tunnelstore.Connection{
|
||||
{
|
||||
ColoName: "DFW",
|
||||
ID: uuid.MustParse("ea550130-57fd-4463-aab1-752822231ddd"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "1xDFW",
|
||||
},
|
||||
{
|
||||
name: "many colos",
|
||||
args: args{
|
||||
connections: []tunnelstore.Connection{
|
||||
{
|
||||
ColoName: "YRV",
|
||||
ID: uuid.MustParse("ea550130-57fd-4463-aab1-752822231ddd"),
|
||||
},
|
||||
{
|
||||
ColoName: "DFW",
|
||||
ID: uuid.MustParse("c13c0b3b-0fbf-453c-8169-a1990fced6d0"),
|
||||
},
|
||||
{
|
||||
ColoName: "ATL",
|
||||
ID: uuid.MustParse("70c90639-e386-4e8d-9a4e-7f046d70e63f"),
|
||||
},
|
||||
{
|
||||
ColoName: "DFW",
|
||||
ID: uuid.MustParse("30ad6251-0305-4635-a670-d3994f474981"),
|
||||
},
|
||||
},
|
||||
},
|
||||
want: "1xATL, 2xDFW, 1xYRV",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := fmtConnections(tt.args.connections); got != tt.want {
|
||||
t.Errorf("fmtConnections() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
|
@ -2,6 +2,7 @@ package updater
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
@ -9,9 +10,10 @@ import (
|
|||
"golang.org/x/crypto/ssh/terminal"
|
||||
"gopkg.in/urfave/cli.v2"
|
||||
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/equinox-io/equinox"
|
||||
"github.com/facebookgo/grace/gracenet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -29,9 +31,35 @@ dsCmJ/QZ6aw0w9qkkwEpne1Lmo6+0pGexZzFZOH6w5amShn+RXt7qkSid9iWlzGq
|
|||
EKx0BZogHSor9Wy5VztdFaAaVbsJiCbO
|
||||
-----END ECDSA PUBLIC KEY-----
|
||||
`)
|
||||
logger = log.CreateLogger()
|
||||
)
|
||||
|
||||
// BinaryUpdated implements ExitCoder interface, the app will exit with status code 11
|
||||
// https://pkg.go.dev/gopkg.in/urfave/cli.v2?tab=doc#ExitCoder
|
||||
type statusSuccess struct {
|
||||
newVersion string
|
||||
}
|
||||
|
||||
func (u *statusSuccess) Error() string {
|
||||
return fmt.Sprintf("cloudflared has been updated to version %s", u.newVersion)
|
||||
}
|
||||
|
||||
func (u *statusSuccess) ExitCode() int {
|
||||
return 11
|
||||
}
|
||||
|
||||
// UpdateErr implements ExitCoder interface, the app will exit with status code 10
|
||||
type statusErr struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (e *statusErr) Error() string {
|
||||
return fmt.Sprintf("failed to update cloudflared: %v", e.err)
|
||||
}
|
||||
|
||||
func (e *statusErr) ExitCode() int {
|
||||
return 10
|
||||
}
|
||||
|
||||
type UpdateOutcome struct {
|
||||
Updated bool
|
||||
Version string
|
||||
|
@ -39,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 {
|
||||
|
@ -65,26 +93,32 @@ func checkForUpdateAndApply() UpdateOutcome {
|
|||
}
|
||||
|
||||
func Update(_ *cli.Context) error {
|
||||
updateOutcome := loggedUpdate()
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
updateOutcome := loggedUpdate(logger)
|
||||
if updateOutcome.Error != nil {
|
||||
os.Exit(10)
|
||||
return &statusErr{updateOutcome.Error}
|
||||
}
|
||||
|
||||
if updateOutcome.noUpdate() {
|
||||
logger.Infof("cloudflared is up to date (%s)", updateOutcome.Version)
|
||||
return nil
|
||||
}
|
||||
|
||||
return updateOutcome.Error
|
||||
return &statusSuccess{newVersion: updateOutcome.Version}
|
||||
}
|
||||
|
||||
// Checks for an update and applies it if one is available
|
||||
func loggedUpdate() UpdateOutcome {
|
||||
func loggedUpdate(logger logger.Service) UpdateOutcome {
|
||||
updateOutcome := checkForUpdateAndApply()
|
||||
if updateOutcome.Updated {
|
||||
logger.Infof("cloudflared has been updated to version %s", updateOutcome.Version)
|
||||
}
|
||||
if updateOutcome.Error != nil {
|
||||
logger.WithError(updateOutcome.Error).Error("update check failed")
|
||||
logger.Errorf("update check failed: %s", updateOutcome.Error)
|
||||
}
|
||||
|
||||
return updateOutcome
|
||||
|
@ -95,6 +129,7 @@ type AutoUpdater struct {
|
|||
configurable *configurable
|
||||
listeners *gracenet.Net
|
||||
updateConfigChan chan *configurable
|
||||
logger logger.Service
|
||||
}
|
||||
|
||||
// AutoUpdaterConfigurable is the attributes of AutoUpdater that can be reconfigured during runtime
|
||||
|
@ -103,7 +138,7 @@ type configurable struct {
|
|||
freq time.Duration
|
||||
}
|
||||
|
||||
func NewAutoUpdater(freq time.Duration, listeners *gracenet.Net) *AutoUpdater {
|
||||
func NewAutoUpdater(freq time.Duration, listeners *gracenet.Net, logger logger.Service) *AutoUpdater {
|
||||
updaterConfigurable := &configurable{
|
||||
enabled: true,
|
||||
freq: freq,
|
||||
|
@ -116,6 +151,7 @@ func NewAutoUpdater(freq time.Duration, listeners *gracenet.Net) *AutoUpdater {
|
|||
configurable: updaterConfigurable,
|
||||
listeners: listeners,
|
||||
updateConfigChan: make(chan *configurable),
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,18 +159,22 @@ func (a *AutoUpdater) Run(ctx context.Context) error {
|
|||
ticker := time.NewTicker(a.configurable.freq)
|
||||
for {
|
||||
if a.configurable.enabled {
|
||||
updateOutcome := loggedUpdate()
|
||||
updateOutcome := loggedUpdate(a.logger)
|
||||
if updateOutcome.Updated {
|
||||
os.Args = append(os.Args, "--is-autoupdated=true")
|
||||
pid, err := a.listeners.StartProcess()
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Unable to restart server automatically")
|
||||
return err
|
||||
if IsSysV() {
|
||||
// SysV doesn't have a mechanism to keep service alive, we have to restart the process
|
||||
a.logger.Info("Restarting service managed by SysV...")
|
||||
pid, err := a.listeners.StartProcess()
|
||||
if err != nil {
|
||||
a.logger.Errorf("Unable to restart server automatically: %s", err)
|
||||
return &statusErr{err: err}
|
||||
}
|
||||
// stop old process after autoupdate. Otherwise we create a new process
|
||||
// after each update
|
||||
a.logger.Infof("PID of the new process is %d", pid)
|
||||
}
|
||||
// stop old process after autoupdate. Otherwise we create a new process
|
||||
// after each update
|
||||
logger.Infof("PID of the new process is %d", pid)
|
||||
return nil
|
||||
return &statusSuccess{newVersion: updateOutcome.Version}
|
||||
}
|
||||
}
|
||||
select {
|
||||
|
@ -164,14 +204,14 @@ func (a *AutoUpdater) Update(newFreq time.Duration) {
|
|||
a.updateConfigChan <- newConfigurable
|
||||
}
|
||||
|
||||
func IsAutoupdateEnabled(c *cli.Context) bool {
|
||||
if !SupportAutoUpdate() {
|
||||
func IsAutoupdateEnabled(c *cli.Context, l logger.Service) bool {
|
||||
if !SupportAutoUpdate(l) {
|
||||
return false
|
||||
}
|
||||
return !c.Bool("no-autoupdate") && c.Duration("autoupdate-freq") != 0
|
||||
}
|
||||
|
||||
func SupportAutoUpdate() bool {
|
||||
func SupportAutoUpdate(logger logger.Service) bool {
|
||||
if runtime.GOOS == "windows" {
|
||||
logger.Info(noUpdateOnWindowsMessage)
|
||||
return false
|
||||
|
@ -187,3 +227,14 @@ func SupportAutoUpdate() bool {
|
|||
func isRunningFromTerminal() bool {
|
||||
return terminal.IsTerminal(int(os.Stdout.Fd()))
|
||||
}
|
||||
|
||||
func IsSysV() bool {
|
||||
if runtime.GOOS != "linux" {
|
||||
return false
|
||||
}
|
||||
|
||||
if _, err := os.Stat("/run/systemd/system"); err == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -4,13 +4,15 @@ import (
|
|||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/facebookgo/grace/gracenet"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDisabledAutoUpdater(t *testing.T) {
|
||||
listeners := &gracenet.Net{}
|
||||
autoupdater := NewAutoUpdater(0, listeners)
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
autoupdater := NewAutoUpdater(0, listeners, logger)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
errC := make(chan error)
|
||||
go func() {
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/pkg/errors"
|
||||
cli "gopkg.in/urfave/cli.v2"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
|
@ -65,6 +67,12 @@ func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) {
|
|||
// 2. get ERROR_FAILED_SERVICE_CONTROLLER_CONNECT
|
||||
// This involves actually trying to start the service.
|
||||
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
isIntSess, err := svc.IsAnInteractiveSession()
|
||||
if err != nil {
|
||||
logger.Fatalf("failed to determine if we are running in an interactive session: %v", err)
|
||||
|
@ -97,9 +105,15 @@ type windowsService struct {
|
|||
|
||||
// called by the package code at the start of the service
|
||||
func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeRequest, statusChan chan<- svc.Status) (ssec bool, errno uint32) {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
|
||||
elog, err := eventlog.Open(windowsServiceName)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Cannot open event log for %s", windowsServiceName)
|
||||
logger.Errorf("Cannot open event log for %s with error: %s", windowsServiceName, err)
|
||||
return
|
||||
}
|
||||
defer elog.Close()
|
||||
|
@ -160,6 +174,11 @@ func (s *windowsService) Execute(serviceArgs []string, r <-chan svc.ChangeReques
|
|||
}
|
||||
|
||||
func installWindowsService(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
logger.Infof("Installing Argo Tunnel Windows service")
|
||||
exepath, err := os.Executable()
|
||||
if err != nil {
|
||||
|
@ -168,7 +187,7 @@ func installWindowsService(c *cli.Context) error {
|
|||
}
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Cannot establish a connection to the service control manager")
|
||||
logger.Errorf("Cannot establish a connection to the service control manager: %s", err)
|
||||
return err
|
||||
}
|
||||
defer m.Disconnect()
|
||||
|
@ -189,18 +208,23 @@ func installWindowsService(c *cli.Context) error {
|
|||
err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info)
|
||||
if err != nil {
|
||||
s.Delete()
|
||||
logger.WithError(err).Errorf("Cannot install event logger")
|
||||
logger.Errorf("Cannot install event logger: %s", err)
|
||||
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
|
||||
}
|
||||
err = configRecoveryOption(s.Handle)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Cannot set service recovery actions")
|
||||
logger.Errorf("Cannot set service recovery actions: %s", err)
|
||||
logger.Infof("See %s to manually configure service recovery actions", windowsServiceUrl)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func uninstallWindowsService(c *cli.Context) error {
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
logger.Infof("Uninstalling Argo Tunnel Windows Service")
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const (
|
||||
openStreamTimeout = 30 * time.Second
|
||||
)
|
||||
|
||||
type Connection struct {
|
||||
id uuid.UUID
|
||||
muxer *h2mux.Muxer
|
||||
addr *net.TCPAddr
|
||||
isLongLived bool
|
||||
longLivedID int
|
||||
}
|
||||
|
||||
func newConnection(muxer *h2mux.Muxer, addr *net.TCPAddr) (*Connection, error) {
|
||||
id, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Connection{
|
||||
id: id,
|
||||
muxer: muxer,
|
||||
addr: addr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Connection) Serve(ctx context.Context) error {
|
||||
// Serve doesn't return until h2mux is shutdown
|
||||
return c.muxer.Serve(ctx)
|
||||
}
|
||||
|
||||
// Connect is used to establish connections with cloudflare's edge network
|
||||
func (c *Connection) Connect(ctx context.Context, parameters *tunnelpogs.ConnectParameters, logger *logrus.Entry) (tunnelpogs.ConnectResult, error) {
|
||||
tsClient, err := NewRPCClient(ctx, c.muxer, logger.WithField("rpc", "connect"), openStreamTimeout)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot create new RPC connection")
|
||||
}
|
||||
defer tsClient.Close()
|
||||
return tsClient.Connect(ctx, parameters)
|
||||
}
|
||||
|
||||
func (c *Connection) Shutdown() {
|
||||
c.muxer.Shutdown()
|
||||
}
|
|
@ -1,302 +0,0 @@
|
|||
package connection
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/streamhandler"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
const (
|
||||
quickStartLink = "https://developers.cloudflare.com/argo-tunnel/quickstart/"
|
||||
faqLink = "https://developers.cloudflare.com/argo-tunnel/faq/"
|
||||
defaultRetryAfter = time.Second * 5
|
||||
packageNamespace = "connection"
|
||||
edgeManagerSubsystem = "edgemanager"
|
||||
)
|
||||
|
||||
// EdgeManager manages connections with the edge
|
||||
type EdgeManager struct {
|
||||
// streamHandler handles stream opened by the edge
|
||||
streamHandler *streamhandler.StreamHandler
|
||||
// TLSConfig is the TLS configuration to connect with edge
|
||||
tlsConfig *tls.Config
|
||||
// cloudflaredConfig is the cloudflared configuration that is determined when the process first starts
|
||||
cloudflaredConfig *CloudflaredConfig
|
||||
// serviceDiscoverer returns the next edge addr to connect to
|
||||
serviceDiscoverer *edgediscovery.Edge
|
||||
// state is attributes of ConnectionManager that can change during runtime.
|
||||
state *edgeManagerState
|
||||
|
||||
logger *logrus.Entry
|
||||
|
||||
metrics *metrics
|
||||
}
|
||||
|
||||
type metrics struct {
|
||||
// activeStreams is a gauge shared by all muxers of this process to expose the total number of active streams
|
||||
activeStreams prometheus.Gauge
|
||||
}
|
||||
|
||||
func newMetrics(namespace, subsystem string) *metrics {
|
||||
return &metrics{
|
||||
activeStreams: h2mux.NewActiveStreamsMetrics(namespace, subsystem),
|
||||
}
|
||||
}
|
||||
|
||||
// EdgeManagerConfigurable is the configurable attributes of a EdgeConnectionManager
|
||||
type EdgeManagerConfigurable struct {
|
||||
TunnelHostnames []h2mux.TunnelHostname
|
||||
*tunnelpogs.EdgeConnectionConfig
|
||||
}
|
||||
|
||||
type CloudflaredConfig struct {
|
||||
CloudflaredID uuid.UUID
|
||||
Tags []tunnelpogs.Tag
|
||||
BuildInfo *buildinfo.BuildInfo
|
||||
IntentLabel string
|
||||
}
|
||||
|
||||
func NewEdgeManager(
|
||||
streamHandler *streamhandler.StreamHandler,
|
||||
edgeConnMgrConfigurable *EdgeManagerConfigurable,
|
||||
userCredential []byte,
|
||||
tlsConfig *tls.Config,
|
||||
serviceDiscoverer *edgediscovery.Edge,
|
||||
cloudflaredConfig *CloudflaredConfig,
|
||||
logger *logrus.Logger,
|
||||
) *EdgeManager {
|
||||
return &EdgeManager{
|
||||
streamHandler: streamHandler,
|
||||
tlsConfig: tlsConfig,
|
||||
cloudflaredConfig: cloudflaredConfig,
|
||||
serviceDiscoverer: serviceDiscoverer,
|
||||
state: newEdgeConnectionManagerState(edgeConnMgrConfigurable, userCredential),
|
||||
logger: logger.WithField("subsystem", "connectionManager"),
|
||||
metrics: newMetrics(packageNamespace, edgeManagerSubsystem),
|
||||
}
|
||||
}
|
||||
|
||||
func (em *EdgeManager) Run(ctx context.Context) error {
|
||||
defer em.shutdown()
|
||||
|
||||
// Currently, declarative tunnels don't have any concept of a stable connection
|
||||
// Each edge connection is transient and when it dies, it is replaced by a different one,
|
||||
// not restarted.
|
||||
// So in the future we should really change this so that n connections are stored individually
|
||||
connIndex := 0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Wrap(ctx.Err(), "EdgeConnectionManager terminated")
|
||||
default:
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
// Create/delete connection one at a time, so we don't need to adjust for connections that are being created/deleted
|
||||
// in shouldCreateConnection or shouldReduceConnection calculation
|
||||
if em.state.shouldCreateConnection(em.serviceDiscoverer.AvailableAddrs()) {
|
||||
if connErr := em.newConnection(ctx, connIndex); connErr != nil {
|
||||
if !connErr.ShouldRetry {
|
||||
em.logger.WithError(connErr).Error(em.noRetryMessage())
|
||||
return connErr
|
||||
}
|
||||
em.logger.WithError(connErr).Error("cannot create new connection")
|
||||
} else {
|
||||
connIndex++
|
||||
}
|
||||
} else if em.state.shouldReduceConnection() {
|
||||
if err := em.closeConnection(ctx); err != nil {
|
||||
em.logger.WithError(err).Error("cannot close connection")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (em *EdgeManager) UpdateConfigurable(newConfigurable *EdgeManagerConfigurable) {
|
||||
em.logger.Infof("New edge connection manager configuration %+v", newConfigurable)
|
||||
em.state.updateConfigurable(newConfigurable)
|
||||
}
|
||||
|
||||
func (em *EdgeManager) newConnection(ctx context.Context, index int) *tunnelpogs.ConnectError {
|
||||
edgeTCPAddr, err := em.serviceDiscoverer.GetAddr(index)
|
||||
if err != nil {
|
||||
return retryConnection(fmt.Sprintf("edge address discovery error: %v", err))
|
||||
}
|
||||
configurable := em.state.getConfigurable()
|
||||
edgeConn, err := DialEdge(ctx, configurable.Timeout, em.tlsConfig, edgeTCPAddr)
|
||||
if err != nil {
|
||||
return retryConnection(fmt.Sprintf("dial edge error: %v", err))
|
||||
}
|
||||
// Establish a muxed connection with the edge
|
||||
// Client mux handshake with agent server
|
||||
muxer, err := h2mux.Handshake(edgeConn, edgeConn, h2mux.MuxerConfig{
|
||||
Timeout: configurable.Timeout,
|
||||
Handler: em.streamHandler,
|
||||
IsClient: true,
|
||||
HeartbeatInterval: configurable.HeartbeatInterval,
|
||||
MaxHeartbeats: configurable.MaxFailedHeartbeats,
|
||||
Logger: em.logger.WithField("subsystem", "muxer"),
|
||||
}, em.metrics.activeStreams)
|
||||
if err != nil {
|
||||
retryConnection(fmt.Sprintf("couldn't perform handshake with edge: %v", err))
|
||||
}
|
||||
|
||||
h2muxConn, err := newConnection(muxer, edgeTCPAddr)
|
||||
if err != nil {
|
||||
return retryConnection(fmt.Sprintf("couldn't create h2mux connection: %v", err))
|
||||
}
|
||||
|
||||
go em.serveConn(ctx, h2muxConn)
|
||||
|
||||
connResult, err := h2muxConn.Connect(ctx, &tunnelpogs.ConnectParameters{
|
||||
CloudflaredID: em.cloudflaredConfig.CloudflaredID,
|
||||
CloudflaredVersion: em.cloudflaredConfig.BuildInfo.CloudflaredVersion,
|
||||
NumPreviousAttempts: 0,
|
||||
OriginCert: em.state.getUserCredential(),
|
||||
IntentLabel: em.cloudflaredConfig.IntentLabel,
|
||||
Tags: em.cloudflaredConfig.Tags,
|
||||
}, em.logger)
|
||||
if err != nil {
|
||||
h2muxConn.Shutdown()
|
||||
return retryConnection(fmt.Sprintf("couldn't connect to edge: %v", err))
|
||||
}
|
||||
|
||||
if connErr := connResult.ConnectError(); connErr != nil {
|
||||
return connErr
|
||||
}
|
||||
|
||||
em.state.newConnection(h2muxConn)
|
||||
em.logger.Infof("connected to %s", connResult.ConnectedTo())
|
||||
|
||||
if connResult.ClientConfig() != nil {
|
||||
em.streamHandler.UseConfiguration(ctx, connResult.ClientConfig())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (em *EdgeManager) closeConnection(ctx context.Context) error {
|
||||
conn := em.state.getFirstConnection()
|
||||
if conn == nil {
|
||||
return fmt.Errorf("no connection to close")
|
||||
}
|
||||
conn.Shutdown()
|
||||
// teardown will be handled by EdgeManager.serveConn in another goroutine
|
||||
return nil
|
||||
}
|
||||
|
||||
func (em *EdgeManager) serveConn(ctx context.Context, conn *Connection) {
|
||||
err := conn.Serve(ctx)
|
||||
em.logger.WithError(err).Warn("Connection closed")
|
||||
em.state.closeConnection(conn)
|
||||
em.serviceDiscoverer.GiveBack(conn.addr)
|
||||
}
|
||||
|
||||
func (em *EdgeManager) noRetryMessage() string {
|
||||
messageTemplate := "cloudflared could not register an Argo Tunnel on your account. Please confirm the following before trying again:" +
|
||||
"1. You have Argo Smart Routing enabled in your account, See Enable Argo section of %s." +
|
||||
"2. Your credential at %s is still valid. See %s."
|
||||
return fmt.Sprintf(messageTemplate, quickStartLink, em.state.getConfigurable().UserCredentialPath, faqLink)
|
||||
}
|
||||
|
||||
func (em *EdgeManager) shutdown() {
|
||||
em.state.shutdown()
|
||||
}
|
||||
|
||||
type edgeManagerState struct {
|
||||
sync.RWMutex
|
||||
configurable *EdgeManagerConfigurable
|
||||
userCredential []byte
|
||||
conns map[uuid.UUID]*Connection
|
||||
}
|
||||
|
||||
func newEdgeConnectionManagerState(configurable *EdgeManagerConfigurable, userCredential []byte) *edgeManagerState {
|
||||
return &edgeManagerState{
|
||||
configurable: configurable,
|
||||
userCredential: userCredential,
|
||||
conns: make(map[uuid.UUID]*Connection),
|
||||
}
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) shouldCreateConnection(availableEdgeAddrs int) bool {
|
||||
ems.RLock()
|
||||
defer ems.RUnlock()
|
||||
expectedHAConns := int(ems.configurable.NumHAConnections)
|
||||
if availableEdgeAddrs < expectedHAConns {
|
||||
expectedHAConns = availableEdgeAddrs
|
||||
}
|
||||
return len(ems.conns) < expectedHAConns
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) shouldReduceConnection() bool {
|
||||
ems.RLock()
|
||||
defer ems.RUnlock()
|
||||
return uint8(len(ems.conns)) > ems.configurable.NumHAConnections
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) newConnection(conn *Connection) {
|
||||
ems.Lock()
|
||||
defer ems.Unlock()
|
||||
ems.conns[conn.id] = conn
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) closeConnection(conn *Connection) {
|
||||
ems.Lock()
|
||||
defer ems.Unlock()
|
||||
delete(ems.conns, conn.id)
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) getFirstConnection() *Connection {
|
||||
ems.RLock()
|
||||
defer ems.RUnlock()
|
||||
|
||||
for _, conn := range ems.conns {
|
||||
return conn
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) shutdown() {
|
||||
ems.Lock()
|
||||
defer ems.Unlock()
|
||||
for _, conn := range ems.conns {
|
||||
conn.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) getConfigurable() *EdgeManagerConfigurable {
|
||||
ems.Lock()
|
||||
defer ems.Unlock()
|
||||
return ems.configurable
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) updateConfigurable(newConfigurable *EdgeManagerConfigurable) {
|
||||
ems.Lock()
|
||||
defer ems.Unlock()
|
||||
ems.configurable = newConfigurable
|
||||
}
|
||||
|
||||
func (ems *edgeManagerState) getUserCredential() []byte {
|
||||
ems.RLock()
|
||||
defer ems.RUnlock()
|
||||
return ems.userCredential
|
||||
}
|
||||
|
||||
func retryConnection(cause string) *tunnelpogs.ConnectError {
|
||||
return &tunnelpogs.ConnectError{
|
||||
Cause: cause,
|
||||
RetryAfter: defaultRetryAfter,
|
||||
ShouldRetry: true,
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package connection
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/streamhandler"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
var (
|
||||
configurable = &EdgeManagerConfigurable{
|
||||
[]h2mux.TunnelHostname{
|
||||
"http.example.com",
|
||||
"ws.example.com",
|
||||
"hello.example.com",
|
||||
},
|
||||
&pogs.EdgeConnectionConfig{
|
||||
NumHAConnections: 1,
|
||||
HeartbeatInterval: 1 * time.Second,
|
||||
Timeout: 5 * time.Second,
|
||||
MaxFailedHeartbeats: 3,
|
||||
UserCredentialPath: "/etc/cloudflared/cert.pem",
|
||||
},
|
||||
}
|
||||
cloudflaredConfig = &CloudflaredConfig{
|
||||
CloudflaredID: uuid.New(),
|
||||
Tags: []pogs.Tag{
|
||||
{Name: "pool", Value: "east-6"},
|
||||
},
|
||||
BuildInfo: &buildinfo.BuildInfo{
|
||||
GoOS: "linux",
|
||||
GoVersion: "1.12",
|
||||
GoArch: "amd64",
|
||||
CloudflaredVersion: "2019.6.0",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func mockEdgeManager() *EdgeManager {
|
||||
newConfigChan := make(chan<- *pogs.ClientConfig)
|
||||
useConfigResultChan := make(<-chan *pogs.UseConfigurationResult)
|
||||
logger := logrus.New()
|
||||
edge := edgediscovery.MockEdge(logger, []*net.TCPAddr{})
|
||||
return NewEdgeManager(
|
||||
streamhandler.NewStreamHandler(newConfigChan, useConfigResultChan, logger),
|
||||
configurable,
|
||||
[]byte{},
|
||||
nil,
|
||||
edge,
|
||||
cloudflaredConfig,
|
||||
logger,
|
||||
)
|
||||
}
|
||||
|
||||
func TestUpdateConfigurable(t *testing.T) {
|
||||
m := mockEdgeManager()
|
||||
newConfigurable := &EdgeManagerConfigurable{
|
||||
[]h2mux.TunnelHostname{
|
||||
"second.example.com",
|
||||
},
|
||||
&pogs.EdgeConnectionConfig{
|
||||
NumHAConnections: 2,
|
||||
},
|
||||
}
|
||||
m.UpdateConfigurable(newConfigurable)
|
||||
|
||||
assert.Equal(t, newConfigurable, m.state.getConfigurable())
|
||||
}
|
|
@ -5,10 +5,10 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
rpc "zombiezen.com/go/capnproto2/rpc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/tunnelrpc"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
@ -18,7 +18,7 @@ import (
|
|||
func NewRPCClient(
|
||||
ctx context.Context,
|
||||
muxer *h2mux.Muxer,
|
||||
logger *logrus.Entry,
|
||||
logger logger.Service,
|
||||
openStreamTimeout time.Duration,
|
||||
) (client tunnelpogs.TunnelServer_PogsClient, err error) {
|
||||
openStreamCtx, openStreamCancel := context.WithTimeout(ctx, openStreamTimeout)
|
||||
|
|
|
@ -15,11 +15,19 @@ import (
|
|||
// The tunnel package is responsible for appending this to tunnel.Commands().
|
||||
func Cmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Category: "Database Connect (ALPHA)",
|
||||
Category: "Database Connect (ALPHA) - Deprecated",
|
||||
Name: "db-connect",
|
||||
Usage: "Access your SQL database from Cloudflare Workers or the browser",
|
||||
Usage: "deprecated: Access your SQL database from Cloudflare Workers or the browser",
|
||||
ArgsUsage: " ",
|
||||
Description: `
|
||||
This feature has been deprecated.
|
||||
Please see:
|
||||
|
||||
cloudflared access tcp --help
|
||||
|
||||
for setting up database connections to the cloudflare edge.
|
||||
|
||||
|
||||
Creates a connection between your database and the Cloudflare edge.
|
||||
Now you can execute SQL commands anywhere you can send HTTPS requests.
|
||||
|
||||
|
|
|
@ -11,17 +11,17 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/hello"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/validation"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// Proxy is an HTTP server that proxies requests to a Client.
|
||||
type Proxy struct {
|
||||
client Client
|
||||
accessValidator *validation.Access
|
||||
logger *logrus.Logger
|
||||
logger logger.Service
|
||||
}
|
||||
|
||||
// NewInsecureProxy creates a Proxy that talks to a Client at an origin.
|
||||
|
@ -43,7 +43,12 @@ func NewInsecureProxy(ctx context.Context, origin string) (*Proxy, error) {
|
|||
return nil, errors.Wrap(err, "could not connect to the database")
|
||||
}
|
||||
|
||||
return &Proxy{client, nil, logrus.New()}, nil
|
||||
logger, err := logger.New()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error setting up logger")
|
||||
}
|
||||
|
||||
return &Proxy{client, nil, logger}, nil
|
||||
}
|
||||
|
||||
// NewSecureProxy creates a Proxy that talks to a Client at an origin.
|
||||
|
@ -90,7 +95,8 @@ func (proxy *Proxy) IsAllowed(r *http.Request, verbose ...bool) bool {
|
|||
// Warn administrators that invalid JWTs are being rejected. This is indicative
|
||||
// of either a misconfiguration of the CLI or a massive failure of upstream systems.
|
||||
if len(verbose) > 0 {
|
||||
proxy.httpLog(r, err).Error("Failed JWT authentication")
|
||||
cfRay := proxy.getRayHeader(r)
|
||||
proxy.logger.Infof("dbproxy: Failed JWT authentication: cf-ray: %s %s", cfRay, err)
|
||||
}
|
||||
|
||||
return false
|
||||
|
@ -234,13 +240,14 @@ func (proxy *Proxy) httpRespondErr(w http.ResponseWriter, r *http.Request, defau
|
|||
|
||||
proxy.httpRespond(w, r, status, err.Error())
|
||||
if len(err.Error()) > 0 {
|
||||
proxy.httpLog(r, err).Warn("Database proxy error")
|
||||
cfRay := proxy.getRayHeader(r)
|
||||
proxy.logger.Infof("dbproxy: Database proxy error: cf-ray: %s %s", cfRay, err)
|
||||
}
|
||||
}
|
||||
|
||||
// httpLog returns a logrus.Entry that is formatted to output a request Cf-ray.
|
||||
func (proxy *Proxy) httpLog(r *http.Request, err error) *logrus.Entry {
|
||||
return proxy.logger.WithContext(r.Context()).WithField("CF-RAY", r.Header.Get("Cf-ray")).WithError(err)
|
||||
// getRayHeader returns the request's Cf-ray header.
|
||||
func (proxy *Proxy) getRayHeader(r *http.Request) string {
|
||||
return r.Header.Get("Cf-ray")
|
||||
}
|
||||
|
||||
// httpError extracts common errors and returns an status code and friendly error.
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -58,15 +58,15 @@ var friendlyDNSErrorLines = []string{
|
|||
}
|
||||
|
||||
// EdgeDiscovery implements HA service discovery lookup.
|
||||
func edgeDiscovery(logger *logrus.Entry) ([][]*net.TCPAddr, error) {
|
||||
func edgeDiscovery(logger logger.Service) ([][]*net.TCPAddr, error) {
|
||||
_, addrs, err := netLookupSRV(srvService, srvProto, srvName)
|
||||
if err != nil {
|
||||
_, fallbackAddrs, fallbackErr := fallbackLookupSRV(srvService, srvProto, srvName)
|
||||
if fallbackErr != nil || len(fallbackAddrs) == 0 {
|
||||
// use the original DNS error `err` in messages, not `fallbackErr`
|
||||
logger.Errorln("Error looking up Cloudflare edge IPs: the DNS query failed:", err)
|
||||
logger.Errorf("Error looking up Cloudflare edge IPs: the DNS query failed: %s", err)
|
||||
for _, s := range friendlyDNSErrorLines {
|
||||
logger.Errorln(s)
|
||||
logger.Error(s)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "Could not lookup srv records on _%v._%v.%v", srvService, srvProto, srvName)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ package allregions
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -19,7 +19,8 @@ func TestEdgeDiscovery(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
addrLists, err := edgeDiscovery(logrus.New().WithFields(logrus.Fields{}))
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
addrLists, err := edgeDiscovery(l)
|
||||
assert.NoError(t, err)
|
||||
actualAddrSet := map[string]bool{}
|
||||
for _, addrs := range addrLists {
|
||||
|
|
|
@ -56,6 +56,10 @@ func (r Region) GetUnusedIP(excluding *net.TCPAddr) *net.TCPAddr {
|
|||
|
||||
// Use the address, assigning it to a proxy connection.
|
||||
func (r Region) Use(addr *net.TCPAddr, connID int) {
|
||||
if addr == nil {
|
||||
//logrus.Errorf("Attempted to use nil address for connection %d", connID)
|
||||
return
|
||||
}
|
||||
r.connFor[addr] = InUse(connID)
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
// Regions stores Cloudflare edge network IPs, partitioned into two regions.
|
||||
|
@ -19,7 +19,7 @@ type Regions struct {
|
|||
// ------------------------------------
|
||||
|
||||
// ResolveEdge resolves the Cloudflare edge, returning all regions discovered.
|
||||
func ResolveEdge(logger *logrus.Entry) (*Regions, error) {
|
||||
func ResolveEdge(logger logger.Service) (*Regions, error) {
|
||||
addrLists, err := edgeDiscovery(logger)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -68,8 +68,8 @@ func NewNoResolve(addrs []*net.TCPAddr) *Regions {
|
|||
|
||||
// GetAnyAddress returns an arbitrary address from the larger region.
|
||||
func (rs *Regions) GetAnyAddress() *net.TCPAddr {
|
||||
if rs.region1.AvailableAddrs() > rs.region2.AvailableAddrs() {
|
||||
return rs.region1.GetAnyAddress()
|
||||
if addr := rs.region1.GetAnyAddress(); addr != nil {
|
||||
return addr
|
||||
}
|
||||
return rs.region2.GetAnyAddress()
|
||||
}
|
||||
|
@ -86,21 +86,28 @@ func (rs *Regions) AddrUsedBy(connID int) *net.TCPAddr {
|
|||
// GetUnusedAddr gets an unused addr from the edge, excluding the given addr. Prefer to use addresses
|
||||
// evenly across both regions.
|
||||
func (rs *Regions) GetUnusedAddr(excluding *net.TCPAddr, connID int) *net.TCPAddr {
|
||||
var addr *net.TCPAddr
|
||||
if rs.region1.AvailableAddrs() > rs.region2.AvailableAddrs() {
|
||||
addr = rs.region1.GetUnusedIP(excluding)
|
||||
rs.region1.Use(addr, connID)
|
||||
} else {
|
||||
addr = rs.region2.GetUnusedIP(excluding)
|
||||
rs.region2.Use(addr, connID)
|
||||
return getAddrs(excluding, connID, &rs.region1, &rs.region2)
|
||||
}
|
||||
|
||||
if addr == nil {
|
||||
return nil
|
||||
return getAddrs(excluding, connID, &rs.region2, &rs.region1)
|
||||
}
|
||||
|
||||
// getAddrs tries to grab address form `first` region, then `second` region
|
||||
// this is an unrolled loop over 2 element array
|
||||
func getAddrs(excluding *net.TCPAddr, connID int, first *Region, second *Region) *net.TCPAddr {
|
||||
addr := first.GetUnusedIP(excluding)
|
||||
if addr != nil {
|
||||
first.Use(addr, connID)
|
||||
return addr
|
||||
}
|
||||
addr = second.GetUnusedIP(excluding)
|
||||
if addr != nil {
|
||||
second.Use(addr, connID)
|
||||
return addr
|
||||
}
|
||||
|
||||
// Mark the address as used and return it
|
||||
return addr
|
||||
return nil
|
||||
}
|
||||
|
||||
// AvailableAddrs returns how many edge addresses aren't used.
|
||||
|
|
|
@ -58,6 +58,7 @@ func TestRegions_Giveback_Region1(t *testing.T) {
|
|||
rs.GiveBack(&addr0)
|
||||
assert.Equal(t, &addr0, rs.GetUnusedAddr(nil, 3))
|
||||
}
|
||||
|
||||
func TestRegions_Giveback_Region2(t *testing.T) {
|
||||
rs := makeRegions()
|
||||
rs.region1.Use(&addr0, 0)
|
||||
|
|
|
@ -6,8 +6,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/cloudflare/cloudflared/edgediscovery/allregions"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -20,7 +19,7 @@ var errNoAddressesLeft = fmt.Errorf("There are no free edge addresses left")
|
|||
type Edge struct {
|
||||
regions *allregions.Regions
|
||||
sync.Mutex
|
||||
logger *logrus.Entry
|
||||
logger logger.Service
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
|
@ -29,37 +28,34 @@ type Edge struct {
|
|||
|
||||
// ResolveEdge runs the initial discovery of the Cloudflare edge, finding Addrs that can be allocated
|
||||
// to connections.
|
||||
func ResolveEdge(l *logrus.Logger) (*Edge, error) {
|
||||
logger := l.WithField("subsystem", subsystem)
|
||||
regions, err := allregions.ResolveEdge(logger)
|
||||
func ResolveEdge(l logger.Service) (*Edge, error) {
|
||||
regions, err := allregions.ResolveEdge(l)
|
||||
if err != nil {
|
||||
return new(Edge), err
|
||||
}
|
||||
return &Edge{
|
||||
logger: logger,
|
||||
logger: l,
|
||||
regions: regions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// StaticEdge creates a list of edge addresses from the list of hostnames. Mainly used for testing connectivity.
|
||||
func StaticEdge(l *logrus.Logger, hostnames []string) (*Edge, error) {
|
||||
logger := l.WithField("subsystem", subsystem)
|
||||
func StaticEdge(l logger.Service, hostnames []string) (*Edge, error) {
|
||||
regions, err := allregions.StaticEdge(hostnames)
|
||||
if err != nil {
|
||||
return new(Edge), err
|
||||
}
|
||||
return &Edge{
|
||||
logger: logger,
|
||||
logger: l,
|
||||
regions: regions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MockEdge creates a Cloudflare Edge from arbitrary TCP addresses. Used for testing.
|
||||
func MockEdge(l *logrus.Logger, addrs []*net.TCPAddr) *Edge {
|
||||
logger := l.WithField("subsystem", subsystem)
|
||||
func MockEdge(l logger.Service, addrs []*net.TCPAddr) *Edge {
|
||||
regions := allregions.NewNoResolve(addrs)
|
||||
return &Edge{
|
||||
logger: logger,
|
||||
logger: l,
|
||||
regions: regions,
|
||||
}
|
||||
}
|
||||
|
@ -83,24 +79,20 @@ func (ed *Edge) GetAddrForRPC() (*net.TCPAddr, error) {
|
|||
func (ed *Edge) GetAddr(connID int) (*net.TCPAddr, error) {
|
||||
ed.Lock()
|
||||
defer ed.Unlock()
|
||||
logger := ed.logger.WithFields(logrus.Fields{
|
||||
"connID": connID,
|
||||
"function": "GetAddr",
|
||||
})
|
||||
|
||||
// If this connection has already used an edge addr, return it.
|
||||
if addr := ed.regions.AddrUsedBy(connID); addr != nil {
|
||||
logger.Debug("Returning same address back to proxy connection")
|
||||
ed.logger.Debugf("edgediscovery - GetAddr: Returning same address back to proxy connection: connID: %d", connID)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
// Otherwise, give it an unused one
|
||||
addr := ed.regions.GetUnusedAddr(nil, connID)
|
||||
if addr == nil {
|
||||
logger.Debug("No addresses left to give proxy connection")
|
||||
ed.logger.Debugf("edgediscovery - GetAddr: No addresses left to give proxy connection: connID: %d", connID)
|
||||
return nil, errNoAddressesLeft
|
||||
}
|
||||
logger.Debug("Giving connection its new address")
|
||||
ed.logger.Debugf("edgediscovery - GetAddr: Giving connection its new address %s: connID: %d", addr, connID)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
|
@ -108,10 +100,6 @@ func (ed *Edge) GetAddr(connID int) (*net.TCPAddr, error) {
|
|||
func (ed *Edge) GetDifferentAddr(connID int) (*net.TCPAddr, error) {
|
||||
ed.Lock()
|
||||
defer ed.Unlock()
|
||||
logger := ed.logger.WithFields(logrus.Fields{
|
||||
"connID": connID,
|
||||
"function": "GetDifferentAddr",
|
||||
})
|
||||
|
||||
oldAddr := ed.regions.AddrUsedBy(connID)
|
||||
if oldAddr != nil {
|
||||
|
@ -119,10 +107,11 @@ func (ed *Edge) GetDifferentAddr(connID int) (*net.TCPAddr, error) {
|
|||
}
|
||||
addr := ed.regions.GetUnusedAddr(oldAddr, connID)
|
||||
if addr == nil {
|
||||
logger.Debug("No addresses left to give proxy connection")
|
||||
ed.logger.Debugf("edgediscovery - GetDifferentAddr: No addresses left to give proxy connection: connID: %d", connID)
|
||||
// note: if oldAddr were not nil, it will become available on the next iteration
|
||||
return nil, errNoAddressesLeft
|
||||
}
|
||||
logger.Debug("Giving connection its new address")
|
||||
ed.logger.Debugf("edgediscovery - GetDifferentAddr: Giving connection its new address %s: connID: %d", addr, connID)
|
||||
return addr, nil
|
||||
}
|
||||
|
||||
|
@ -138,6 +127,6 @@ func (ed *Edge) AvailableAddrs() int {
|
|||
func (ed *Edge) GiveBack(addr *net.TCPAddr) bool {
|
||||
ed.Lock()
|
||||
defer ed.Unlock()
|
||||
ed.logger.WithField("function", "GiveBack").Debug("Address now unused")
|
||||
ed.logger.Debug("edgediscovery - GiveBack: Address now unused")
|
||||
return ed.regions.GiveBack(addr)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ import (
|
|||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -32,7 +32,7 @@ var (
|
|||
)
|
||||
|
||||
func TestGiveBack(t *testing.T) {
|
||||
l := logrus.New()
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
edge := MockEdge(l, []*net.TCPAddr{&addr0, &addr1, &addr2, &addr3})
|
||||
|
||||
// Give this connection an address
|
||||
|
@ -47,8 +47,26 @@ func TestGiveBack(t *testing.T) {
|
|||
edge.GiveBack(addr)
|
||||
assert.Equal(t, 4, edge.AvailableAddrs())
|
||||
}
|
||||
|
||||
func TestRPCAndProxyShareSingleEdgeIP(t *testing.T) {
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
// Make an edge with a single IP
|
||||
edge := MockEdge(l, []*net.TCPAddr{&addr0})
|
||||
tunnelConnID := 0
|
||||
|
||||
// Use the IP for a tunnel
|
||||
addrTunnel, err := edge.GetAddr(tunnelConnID)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Ensure the IP can be used for RPC too
|
||||
addrRPC, err := edge.GetAddrForRPC()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, addrTunnel, addrRPC)
|
||||
}
|
||||
|
||||
func TestGetAddrForRPC(t *testing.T) {
|
||||
l := logrus.New()
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
edge := MockEdge(l, []*net.TCPAddr{&addr0, &addr1, &addr2, &addr3})
|
||||
|
||||
// Get a connection
|
||||
|
@ -65,8 +83,32 @@ func TestGetAddrForRPC(t *testing.T) {
|
|||
assert.Equal(t, 4, edge.AvailableAddrs())
|
||||
}
|
||||
|
||||
func TestOnePerRegion(t *testing.T) {
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
// Make an edge with only one address
|
||||
edge := MockEdge(l, []*net.TCPAddr{&addr0, &addr1})
|
||||
|
||||
// Use the only address
|
||||
const connID = 0
|
||||
a1, err := edge.GetAddr(connID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, a1)
|
||||
|
||||
// if the first address is bad, get the second one
|
||||
a2, err := edge.GetDifferentAddr(connID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, a2)
|
||||
assert.NotEqual(t, a1, a2)
|
||||
|
||||
// now that second one is bad, get the first one again
|
||||
a3, err := edge.GetDifferentAddr(connID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, a1, a3)
|
||||
}
|
||||
|
||||
func TestOnlyOneAddrLeft(t *testing.T) {
|
||||
l := logrus.New()
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
// Make an edge with only one address
|
||||
edge := MockEdge(l, []*net.TCPAddr{&addr0})
|
||||
|
@ -80,10 +122,15 @@ func TestOnlyOneAddrLeft(t *testing.T) {
|
|||
// If that edge address is "bad", there's no alternative address.
|
||||
_, err = edge.GetDifferentAddr(connID)
|
||||
assert.Error(t, err)
|
||||
|
||||
// previously bad address should become available again on next iteration.
|
||||
addr, err = edge.GetDifferentAddr(connID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, addr)
|
||||
}
|
||||
|
||||
func TestNoAddrsLeft(t *testing.T) {
|
||||
l := logrus.New()
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
// Make an edge with no addresses
|
||||
edge := MockEdge(l, []*net.TCPAddr{})
|
||||
|
@ -95,7 +142,7 @@ func TestNoAddrsLeft(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetAddr(t *testing.T) {
|
||||
l := logrus.New()
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
edge := MockEdge(l, []*net.TCPAddr{&addr0, &addr1, &addr2, &addr3})
|
||||
|
||||
// Give this connection an address
|
||||
|
@ -111,7 +158,7 @@ func TestGetAddr(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGetDifferentAddr(t *testing.T) {
|
||||
l := logrus.New()
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
edge := MockEdge(l, []*net.TCPAddr{&addr0, &addr1, &addr2, &addr3})
|
||||
|
||||
// Give this connection an address
|
||||
|
|
|
@ -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()
|
28
go.mod
28
go.mod
|
@ -3,12 +3,16 @@ module github.com/cloudflare/cloudflared
|
|||
go 1.12
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/go-sumtype v0.0.0-20190304192233-fcb4a6205bdc // indirect
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3
|
||||
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect
|
||||
github.com/aws/aws-sdk-go v1.25.8
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 // indirect
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 // indirect
|
||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93
|
||||
github.com/cloudflare/cfssl v0.0.0-20141119014638-2f7f44e802e2 // indirect
|
||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc
|
||||
github.com/coredns/coredns v1.2.0
|
||||
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73
|
||||
|
@ -21,11 +25,11 @@ require (
|
|||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10
|
||||
github.com/gliderlabs/ssh v0.0.0-20191009160644-63518b5243e0
|
||||
github.com/go-sql-driver/mysql v1.4.1
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||
github.com/google/certificate-transparency-go v1.1.0
|
||||
github.com/google/uuid v1.1.1
|
||||
github.com/gorilla/mux v1.7.3
|
||||
github.com/gorilla/websocket v1.4.0
|
||||
|
@ -33,12 +37,13 @@ require (
|
|||
github.com/jmoiron/sqlx v1.2.0
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 // indirect
|
||||
github.com/kshvakov/clickhouse v1.3.11
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/lib/pq v1.2.0
|
||||
github.com/mattn/go-colorable v0.1.4
|
||||
github.com/mattn/go-colorable v0.1.4 // indirect
|
||||
github.com/mattn/go-isatty v0.0.10 // indirect
|
||||
github.com/mattn/go-sqlite3 v1.11.0
|
||||
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b // indirect
|
||||
github.com/miekg/dns v1.1.8
|
||||
github.com/miekg/dns v1.1.27
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/opentracing/opentracing-go v1.1.0 // indirect
|
||||
github.com/philhofer/fwd v1.0.0 // indirect
|
||||
|
@ -48,22 +53,23 @@ require (
|
|||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 // indirect
|
||||
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
|
||||
github.com/sirupsen/logrus v1.4.2
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 // 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
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 // indirect
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be
|
||||
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20191007204434-a023cd5227bd // indirect
|
||||
google.golang.org/grpc v1.24.0 // indirect
|
||||
gopkg.in/coreos/go-oidc.v2 v2.1.0
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 // indirect
|
||||
gopkg.in/urfave/cli.v2 v2.0.0-20180128181224-d604b6ffeee8
|
||||
gopkg.in/yaml.v2 v2.2.4 // indirect
|
||||
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible
|
||||
)
|
||||
|
||||
// ../../go/pkg/mod/github.com/coredns/coredns@v1.2.0/plugin/metrics/metrics.go:40:49: too many arguments in call to prometheus.NewProcessCollector
|
||||
|
|
300
go.sum
300
go.sum
|
@ -1,59 +1,56 @@
|
|||
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
|
||||
contrib.go.opencensus.io/exporter/stackdriver v0.12.1/go.mod h1:iwB6wGarfphGGe/e5CWqyUk/cLzKnWsOKPVW3no6OTw=
|
||||
contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcigGlFvXwEGEnkRLA=
|
||||
github.com/BurntSushi/go-sumtype v0.0.0-20190304192233-fcb4a6205bdc h1:nvTP+jmloR0+J4YQur/rLRdLcGVEU4SquDgH+Bo7gBY=
|
||||
github.com/BurntSushi/go-sumtype v0.0.0-20190304192233-fcb4a6205bdc/go.mod h1:7yTWMMG2vOm4ABVciEt4EgNVP7fxwtcKIb/EuiLiKqY=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3 h1:CWUqKXe0s8A2z6qCgkP4Kru7wC11YoAnoupUKFDnH08=
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/OpenPeeDeeP/depguard v1.0.0/go.mod h1:7/4sitnI9YlQgTLLk734QlzXT8DuHVnAyztLplQjk+o=
|
||||
github.com/StackExchange/wmi v0.0.0-20180116203802-5d049714c4a6/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg=
|
||||
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1 h1:RKnVV4C7qoN/sToLX2y1dqH7T6kKLMHcwRJlgwb9Ggk=
|
||||
github.com/acmacalister/skittles v0.0.0-20160609003031-7423546701e1/go.mod h1:gI5CyA/CEnS6eqNV22rqs4dG3aGfaSbXgPORIlwr2r0=
|
||||
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4 h1:Hs82Z41s6SdL1CELW+XaDYmOH4hkBN4/N9og/AsOv7E=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.19.18/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/aws/aws-sdk-go v1.25.8 h1:n7I+HUUXjun2CsX7JK+1hpRIkZrlKhd3nayeb+Xmavs=
|
||||
github.com/aws/aws-sdk-go v1.25.8/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0 h1:RXc4wYsyz985CkXXeX04y4VnZFGG8Rd43pRaHsOXAKk=
|
||||
github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261 h1:6/yVvBsKeAw05IUj4AzvrxaCnDjN4nUqKjW9+w5wixg=
|
||||
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894 h1:JLaf/iINcLyjwbtTsCJjc6rtlASgHeIJPrB6QmwURnA=
|
||||
github.com/certifi/gocertifi v0.0.0-20200211180108-c7c1fbc02894/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
|
||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93 h1:QrGfkZDnMxcWHaYDdB7CmqS9i26OAnUj/xcus/abYkY=
|
||||
github.com/cloudflare/brotli-go v0.0.0-20191101163834-d34379f7ff93/go.mod h1:QiTe66jFdP7cUKMCCf/WrvDyYdtdmdZfVcdoLbzaKVY=
|
||||
github.com/cloudflare/cfssl v0.0.0-20141119014638-2f7f44e802e2 h1:GOj9uxwfkVdMaHYGQqhab3hr1rjMDqfaQI+JjpKXgMM=
|
||||
github.com/cloudflare/cfssl v0.0.0-20141119014638-2f7f44e802e2/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
|
||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc h1:Dvk3ySBsOm5EviLx6VCyILnafPcQinXGP5jbTdHUJgE=
|
||||
github.com/cloudflare/golibs v0.0.0-20170913112048-333127dbecfc/go.mod h1:HlgKKR8V5a1wroIDDIz3/A+T+9Janfq+7n1P5sEFdi0=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58 h1:F1EaeKL/ta07PY/k9Os/UFtwERei2/XzGemhpGnBKNg=
|
||||
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
|
||||
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
|
||||
github.com/coredns/coredns v1.2.0 h1:YEI38K2BJYzL/SxO2tZFD727T/C68DqVWkBQjT0sWPU=
|
||||
github.com/coredns/coredns v1.2.0/go.mod h1:zASH/MVDgR6XZTbxvOnsZfffS+31vg6Ackf/wo1+AM0=
|
||||
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
|
||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
|
||||
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73 h1:7CNPV0LWRCa1FNmqg700pbXhzvmoaXKyfxWRkjRym7Q=
|
||||
github.com/coreos/go-oidc v0.0.0-20171002155002-a93f71fdfe73/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc=
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a h1:W8b4lQ4tFF21aspRGoBuCNV6V2fFJBF+pm1J6OY8Lys=
|
||||
github.com/coreos/go-systemd v0.0.0-20190620071333-e64a0ec8b42a/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
|
||||
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0 h1:epsH3lb7KVbXHYk7LYGN5EiE0MxcevHU85CKITJ0wUY=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20191001013358-cfbb681360f0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/equinox-io/equinox v1.2.0 h1:bBS7Ou+Y7Jwgmy8TWSYxEh85WctuFn7FPlgbUzX4DBA=
|
||||
github.com/equinox-io/equinox v1.2.0/go.mod h1:6s3HJB0PYUNgs0mxmI8fHdfVl3TQ25ieA/PVfr+eyVo=
|
||||
github.com/facebookgo/ensure v0.0.0-20160127193407-b4ab57deab51 h1:0JZ+dUmQeA8IIVUMzysrX4/AKuQwWhV2dYQuPZdvdSQ=
|
||||
|
@ -66,180 +63,103 @@ github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 h1:JWuenKqqX8nojt
|
|||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052/go.mod h1:UbMTZqLaRiH3MsBH8va0n7s1pQYcu3uTb8G4tygF4Zg=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 h1:E2s37DuLxFhQDg5gKsWoLBOB0n+ZW8s599zru8FJ2/Y=
|
||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870/go.mod h1:5tD+neXqOorC30/tWg0LCSkrqj/AR6gu8yY8/fpw1q0=
|
||||
github.com/fatih/color v1.6.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ=
|
||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10 h1:YO10pIIBftO/kkTFdWhctH96grJ7qiy7bMdiZcIvPKs=
|
||||
github.com/getsentry/raven-go v0.0.0-20180517221441-ed7bcb39ff10/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/gliderlabs/ssh v0.0.0-20191009160644-63518b5243e0 h1:gF8ngtda767ddth2SH0YSAhswhz6qUkvyI9EZFYCWJA=
|
||||
github.com/gliderlabs/ssh v0.0.0-20191009160644-63518b5243e0/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||
github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
|
||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4=
|
||||
github.com/go-toolsmith/astcopy v1.0.0/go.mod h1:vrgyG+5Bxrnz4MZWPF+pI4R8h3qKRjjyvV/DSez4WVQ=
|
||||
github.com/go-toolsmith/astequal v0.0.0-20180903214952-dcb477bfacd6/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY=
|
||||
github.com/go-toolsmith/astfmt v0.0.0-20180903215011-8f8ee99c3086/go.mod h1:mP93XdblcopXwlyN4X4uodxXQhldPGZbcEJIimQHrkg=
|
||||
github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw=
|
||||
github.com/go-toolsmith/astinfo v0.0.0-20180906194353-9809ff7efb21/go.mod h1:dDStQCHtmZpYOmjRP/8gHHnCCch3Zz3oEgCdZVdtweU=
|
||||
github.com/go-toolsmith/astp v0.0.0-20180903215135-0af7e3c24f30/go.mod h1:SV2ur98SGypH1UjcPpCatrV5hPazG6+IfNHbkDXBRrk=
|
||||
github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI=
|
||||
github.com/go-toolsmith/pkgload v0.0.0-20181119091011-e9e65178eee8/go.mod h1:WoMrjiy4zvdS+Bg6z9jZH82QXwkcgCBX6nOfnmdaHks=
|
||||
github.com/go-toolsmith/pkgload v1.0.0/go.mod h1:5eFArkbO80v7Z0kdngIxsRXRMTaX4Ilcwuh3clNrQJc=
|
||||
github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8=
|
||||
github.com/go-toolsmith/typep v1.0.0/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU=
|
||||
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
|
||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4=
|
||||
github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk=
|
||||
github.com/golangci/errcheck v0.0.0-20181223084120-ef45e06d44b6/go.mod h1:DbHgvLiFKX1Sh2T1w8Q/h4NAI8MHIpzCdnBUDTXU3I0=
|
||||
github.com/golangci/go-misc v0.0.0-20180628070357-927a3d87b613/go.mod h1:SyvUF2NxV+sN8upjjeVYr5W7tyxaT1JVtvhKhOn2ii8=
|
||||
github.com/golangci/go-tools v0.0.0-20190318055746-e32c54105b7c/go.mod h1:unzUULGw35sjyOYjUt0jMTXqHlZPpPc6e+xfO4cd6mM=
|
||||
github.com/golangci/goconst v0.0.0-20180610141641-041c5f2b40f3/go.mod h1:JXrF4TWy4tXYn62/9x8Wm/K/dm06p8tCKwFRDPZG/1o=
|
||||
github.com/golangci/gocyclo v0.0.0-20180528134321-2becd97e67ee/go.mod h1:ozx7R9SIwqmqf5pRP90DhR2Oay2UIjGuKheCBCNwAYU=
|
||||
github.com/golangci/gofmt v0.0.0-20181222123516-0b8337e80d98/go.mod h1:9qCChq59u/eW8im404Q2WWTrnBUQKjpNYKMbU4M7EFU=
|
||||
github.com/golangci/golangci-lint v1.17.2-0.20190910081718-bad04bb7378f/go.mod h1:kaqo8l0OZKYPtjNmG4z4HrWLgcYNIJ9B9q3LWri9uLg=
|
||||
github.com/golangci/gosec v0.0.0-20190211064107-66fb7fc33547/go.mod h1:0qUabqiIQgfmlAmulqxyiGkkyF6/tOGSnY2cnPVwrzU=
|
||||
github.com/golangci/ineffassign v0.0.0-20190609212857-42439a7714cc/go.mod h1:e5tpTHCfVze+7EpLEozzMB3eafxo2KT5veNg1k6byQU=
|
||||
github.com/golangci/lint-1 v0.0.0-20190420132249-ee948d087217/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg=
|
||||
github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o=
|
||||
github.com/golangci/misspell v0.0.0-20180809174111-950f5d19e770/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA=
|
||||
github.com/golangci/prealloc v0.0.0-20180630174525-215b22d4de21/go.mod h1:tf5+bzsHdTM0bsB7+8mt0GUMvjCgwLpTapNZHU8AajI=
|
||||
github.com/golangci/revgrep v0.0.0-20180526074752-d9c87f5ffaf0/go.mod h1:qOQCunEYvmd/TLamH+7LlVccLvUH5kZNhbCgTHoBbp4=
|
||||
github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/certificate-transparency-go v1.1.0 h1:10MlrYzh5wfkToxWI4yJzffsxLfxcEDlOATMx/V9Kzw=
|
||||
github.com/google/certificate-transparency-go v1.1.0/go.mod h1:i+Q7XY+ArBveOUT36jiHGfuSK1fHICIg6sUkRxPAbCs=
|
||||
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
|
||||
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/monologue v0.0.0-20190606152607-4b11a32b5934/go.mod h1:6NTfaQoUpg5QmPsCUWLR3ig33FHrKXhTtWzF0DVdmuk=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/trillian v1.2.2-0.20190612132142-05461f4df60a/go.mod h1:YPmUVn5NGwgnDUgqlVyFGMTgaWlnSvH7W5p+NdOG8UA=
|
||||
github.com/google/trillian-examples v0.0.0-20190603134952-4e75ba15216c/go.mod h1:WgL3XZ3pA8/9cm7yxqWrZE6iZkESB2ItGxy5Fo6k2lk=
|
||||
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ=
|
||||
github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q=
|
||||
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
|
||||
github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 h1:MJG/KsmcqMwFAkh8mTnAwhyKoB+sTAnY4CACC110tbU=
|
||||
github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645/go.mod h1:6iZfnjpejD4L/4DwD7NryNaJyCQdzwWwH2MWhCA90Kw=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
|
||||
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
|
||||
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
|
||||
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/juju/ratelimit v1.0.1/go.mod h1:qapgC/Gy+xNh9UxzV13HGGl/6UXNN+ct+vwSgWNm/qk=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kisielk/gotool v0.0.0-20161130080628-0de1eaf82fa3/go.mod h1:jxZFDH7ILpTPQTk+E2s+z4CUas9lVNjIuKR4c5/zKgM=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.4.0/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/cpuid v0.0.0-20180405133222-e7e905edc00e/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
|
||||
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kshvakov/clickhouse v1.3.11 h1:dtzTJY0fCA+MWkLyuKZaNPkmSwdX4gh8+Klic9NB1Lw=
|
||||
github.com/kshvakov/clickhouse v1.3.11/go.mod h1:/SVBAcqF3u7rxQ9sTWCZwf8jzzvxiZGeQvtmSF2BBEc=
|
||||
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
|
||||
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
|
||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
github.com/letsencrypt/pkcs11key v2.0.1-0.20170608213348-396559074696+incompatible/go.mod h1:iGYXKqDXt0cpBthCHdr9ZdsQwyGlYFh/+8xa4WzIQ34=
|
||||
github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
|
||||
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
|
||||
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b h1:/BbY4n99iMazlr2igipph+hj0MwlZIWpcsP8Iy+na+s=
|
||||
github.com/mholt/caddy v0.0.0-20180807230124-d3b731e9255b/go.mod h1:Wb1PlT4DAYSqOEd03MsqkdkXnTxA8v9pKjdpxbqM1kY=
|
||||
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
|
||||
github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/pkcs11 v1.0.2/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
|
||||
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/miekg/dns v1.1.27 h1:aEH/kqUzUxGJ/UHcEKdJY+ugH6WEzsEBBSPa8zuy1aM=
|
||||
github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
|
||||
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mozilla/tls-observatory v0.0.0-20180409132520-8791a200eb40/go.mod h1:SrKMQvPiws7F7iqYp8/TX+IhxCYhzr6N/1yb8cwHsGk=
|
||||
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20160627004424-a22cb81b2ecd/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/nbutton23/zxcvbn-go v0.0.0-20171102151520-eafdab6b0663/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU=
|
||||
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
github.com/philhofer/fwd v1.0.0 h1:UbZqGr5Y38ApvM/V/jEljVxwocdweyH+vmYvRPBnbqQ=
|
||||
github.com/philhofer/fwd v1.0.0/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@ -255,191 +175,117 @@ github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLy
|
|||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/procfs v0.0.5 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
|
||||
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
|
||||
github.com/quasilyte/go-consistent v0.0.0-20190521200055-c6f3937de18c/go.mod h1:5STLWrekHfjyYwxBRVRXNOSewLJ3PWfDJd1VyTS21fI=
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5 h1:mZHayPoR0lNmnHyvtYjDeq0zlVHn9K/ZXoy17ylucdo=
|
||||
github.com/rifflock/lfshook v0.0.0-20180920164130-b9218ef580f5/go.mod h1:GEXHk5HgEKCvEIIrSpFI3ozzG5xOKA2DVlEX/gGnewM=
|
||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||
github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk=
|
||||
github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ=
|
||||
github.com/sirupsen/logrus v1.0.5/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE=
|
||||
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||
github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
|
||||
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/timakin/bodyclose v0.0.0-20190721030226-87058b9bfcec/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk=
|
||||
github.com/tinylib/msgp v1.1.0 h1:9fQd+ICuRIu/ue4vxJZu6/LzxN0HwMds2nq/0cFvxHU=
|
||||
github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ultraware/funlen v0.0.1/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA=
|
||||
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.2.0/go.mod h1:4vX61m6KN+xDduDNwXrhIAVZaZaZiQ1luJk8LWSxF3s=
|
||||
github.com/valyala/quicktemplate v1.1.1/go.mod h1:EH+4AkTd43SvgIbQHYu59/cJyxDoOVRUAfrukLPuGJ4=
|
||||
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
|
||||
github.com/xo/dburl v0.0.0-20191005012637-293c3298d6c0 h1:6DtWz8hNS4qbq0OCRPhdBMG9E2qKTSDKlwnP3dmZvuA=
|
||||
github.com/xo/dburl v0.0.0-20191005012637-293c3298d6c0/go.mod h1:A47W3pdWONaZmXuLZgfKLAVgUY0qvfTRM5vVDKS40S4=
|
||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/etcd v3.3.13+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
|
||||
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
|
||||
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
|
||||
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8=
|
||||
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4=
|
||||
golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/net v0.0.0-20170915142106-8351a756f30f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954 h1:JGZucVF/L/TotR719NbujzadOZ2AgnYlqphQGHDCKaU=
|
||||
golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 h1:p9xBe/w/OzkeYVKm234g55gMdD1nSIooTir5kV11kfA=
|
||||
golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0=
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be h1:QAcqgptGM8IQBC9K/RC4o+O9YmqEm0diQn9QmZw/0mU=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.0.0-20170915090833-1cbadb444a80/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2 h1:nq114VpM8lsSlP+lyUbANecYHYiFcSNFtqcBlxRV+gA=
|
||||
golang.org/x/sys v0.0.0-20191020212454-3e7259c5e7c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20170915040203-e531a2a1c15f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20181117154741-2ddaf7f79a09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190110163146-51295c7ec13a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190121143147-24cd39ecf745/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.5.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||
google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425 h1:VvQyQJN0tSuecqgcIxMWnnfG5kSmgy9KZR9sW3W5QeA=
|
||||
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c=
|
||||
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190605220351-eb0b1bdb6ae6/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s=
|
||||
google.golang.org/genproto v0.0.0-20191007204434-a023cd5227bd h1:84VQPzup3IpKLxuIAZjHMhVjJ8fZ4/i3yUnj3k6fUdw=
|
||||
google.golang.org/genproto v0.0.0-20191007204434-a023cd5227bd/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
|
||||
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
|
||||
google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s=
|
||||
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/cheggaaa/pb.v1 v1.0.28/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
|
||||
gopkg.in/coreos/go-oidc.v2 v2.1.0 h1:E8PjVFdj/SLDKB0hvb70KTbMbYVHjqztiQdSkIg8E+I=
|
||||
gopkg.in/coreos/go-oidc.v2 v2.1.0/go.mod h1:fYaTe2FS96wZZwR17YTDHwG+Mw6fmyqJNxN2eNCGPCI=
|
||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0 h1:0kXPskUMGAXXWJlP05ktEMOV0vmzFQUWw6d+aZJQU8A=
|
||||
gopkg.in/square/go-jose.v2 v2.4.0/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/urfave/cli.v2 v2.0.0-20180128181224-d604b6ffeee8 h1:/pLAskKF+d5SawboKd8GB8ew4ClHDbt2c3K9EBFeRGU=
|
||||
gopkg.in/urfave/cli.v2 v2.0.0-20180128181224-d604b6ffeee8/go.mod h1:cKXr3E0k4aosgycml1b5z33BVV6hai1Kh7uDgFOkbcs=
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc=
|
||||
mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4=
|
||||
mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY=
|
||||
sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0=
|
||||
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7 h1:CZoOFlTPbKfAShKYrMuUfYbnXexFT1rYRUX1SPnrdE4=
|
||||
zombiezen.com/go/capnproto2 v0.0.0-20180616160808-7cfd211c19c7/go.mod h1:TMGa8HWGJkXiq4nHe9Zu/JgRF5oUtg4XizFC+Vexbec=
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible h1:mwfXZniffG5mXokQGHUJWGnqIBggoPfT/CEwon9Yess=
|
||||
zombiezen.com/go/capnproto2 v2.18.0+incompatible/go.mod h1:XO5Pr2SbXgqZwn0m0Ru54QBqpOf4K5AYBO+8LAOBQEQ=
|
||||
|
|
|
@ -7,8 +7,8 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
@ -48,7 +48,7 @@ type MuxerConfig struct {
|
|||
// The minimum number of heartbeats to send before terminating the connection.
|
||||
MaxHeartbeats uint64
|
||||
// Logger to use
|
||||
Logger *log.Entry
|
||||
Logger logger.Service
|
||||
CompressionQuality CompressionSetting
|
||||
// Initial size for HTTP2 flow control windows
|
||||
DefaultWindowSize uint32
|
||||
|
@ -136,10 +136,10 @@ func Handshake(
|
|||
handshakeSetting := http2.Setting{ID: SettingMuxerMagic, Val: MuxerMagicEdge}
|
||||
compressionSetting := http2.Setting{ID: SettingCompression, Val: config.CompressionQuality.toH2Setting()}
|
||||
if CompressionIsSupported() {
|
||||
log.Debug("Compression is supported")
|
||||
config.Logger.Debug("muxer: Compression is supported")
|
||||
m.compressionQuality = config.CompressionQuality.getPreset()
|
||||
} else {
|
||||
log.Debug("Compression is not supported")
|
||||
config.Logger.Debug("muxer: Compression is not supported")
|
||||
compressionSetting = http2.Setting{ID: SettingCompression, Val: 0}
|
||||
}
|
||||
|
||||
|
@ -176,12 +176,12 @@ func Handshake(
|
|||
// Sanity check to enusre idelDuration is sane
|
||||
if idleDuration == 0 || idleDuration < defaultTimeout {
|
||||
idleDuration = defaultTimeout
|
||||
config.Logger.Warn("Minimum idle time has been adjusted to ", defaultTimeout)
|
||||
config.Logger.Infof("muxer: Minimum idle time has been adjusted to %d", defaultTimeout)
|
||||
}
|
||||
maxRetries := config.MaxHeartbeats
|
||||
if maxRetries == 0 {
|
||||
maxRetries = defaultRetries
|
||||
config.Logger.Warn("Minimum number of unacked heartbeats to send before closing the connection has been adjusted to ", maxRetries)
|
||||
config.Logger.Infof("muxer: Minimum number of unacked heartbeats to send before closing the connection has been adjusted to %d", maxRetries)
|
||||
}
|
||||
|
||||
compBytesBefore, compBytesAfter := NewAtomicCounter(0), NewAtomicCounter(0)
|
||||
|
@ -206,9 +206,9 @@ func Handshake(
|
|||
initialStreamWindow: m.config.DefaultWindowSize,
|
||||
streamWindowMax: m.config.MaxWindowSize,
|
||||
streamWriteBufferMaxLen: m.config.StreamWriteBufferMaxLen,
|
||||
r: m.r,
|
||||
metricsUpdater: m.muxMetricsUpdater,
|
||||
bytesRead: inBoundCounter,
|
||||
r: m.r,
|
||||
metricsUpdater: m.muxMetricsUpdater,
|
||||
bytesRead: inBoundCounter,
|
||||
}
|
||||
m.muxWriter = &MuxWriter{
|
||||
f: m.f,
|
||||
|
@ -321,24 +321,63 @@ func joinErrorsWithTimeout(errChan <-chan error, receiveCount int, timeout time.
|
|||
func (m *Muxer) Serve(ctx context.Context) error {
|
||||
errGroup, _ := errgroup.WithContext(ctx)
|
||||
errGroup.Go(func() error {
|
||||
err := m.muxReader.run(m.config.Logger)
|
||||
m.explicitShutdown.Fuse(false)
|
||||
m.r.Close()
|
||||
m.abort()
|
||||
return err
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
err := m.muxReader.run(m.config.Logger)
|
||||
m.explicitShutdown.Fuse(false)
|
||||
m.r.Close()
|
||||
m.abort()
|
||||
// don't block if parent goroutine quit early
|
||||
select {
|
||||
case ch <- err:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case err := <-ch:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
})
|
||||
|
||||
errGroup.Go(func() error {
|
||||
err := m.muxWriter.run(m.config.Logger)
|
||||
m.explicitShutdown.Fuse(false)
|
||||
m.w.Close()
|
||||
m.abort()
|
||||
return err
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
err := m.muxWriter.run(m.config.Logger)
|
||||
m.explicitShutdown.Fuse(false)
|
||||
m.w.Close()
|
||||
m.abort()
|
||||
// don't block if parent goroutine quit early
|
||||
select {
|
||||
case ch <- err:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case err := <-ch:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
})
|
||||
|
||||
errGroup.Go(func() error {
|
||||
err := m.muxMetricsUpdater.run(m.config.Logger)
|
||||
return err
|
||||
ch := make(chan error)
|
||||
go func() {
|
||||
err := m.muxMetricsUpdater.run(m.config.Logger)
|
||||
// don't block if parent goroutine quit early
|
||||
select {
|
||||
case ch <- err:
|
||||
default:
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case err := <-ch:
|
||||
return err
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
}
|
||||
})
|
||||
|
||||
err := errGroup.Wait()
|
||||
|
|
|
@ -15,8 +15,8 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
@ -28,7 +28,7 @@ const (
|
|||
|
||||
func TestMain(m *testing.M) {
|
||||
if os.Getenv("VERBOSE") == "1" {
|
||||
log.SetLevel(log.DebugLevel)
|
||||
//TODO: set log level
|
||||
}
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func NewDefaultMuxerPair(t assert.TestingT, testName string, f MuxedStreamFunc)
|
|||
Handler: f,
|
||||
IsClient: true,
|
||||
Name: "origin",
|
||||
Logger: log.NewEntry(log.New()),
|
||||
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
|
||||
DefaultWindowSize: (1 << 8) - 1,
|
||||
MaxWindowSize: (1 << 15) - 1,
|
||||
StreamWriteBufferMaxLen: 1024,
|
||||
|
@ -63,7 +63,7 @@ func NewDefaultMuxerPair(t assert.TestingT, testName string, f MuxedStreamFunc)
|
|||
Timeout: testHandshakeTimeout,
|
||||
IsClient: false,
|
||||
Name: "edge",
|
||||
Logger: log.NewEntry(log.New()),
|
||||
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
|
||||
DefaultWindowSize: (1 << 8) - 1,
|
||||
MaxWindowSize: (1 << 15) - 1,
|
||||
StreamWriteBufferMaxLen: 1024,
|
||||
|
@ -86,7 +86,7 @@ func NewCompressedMuxerPair(t assert.TestingT, testName string, quality Compress
|
|||
IsClient: true,
|
||||
Name: "origin",
|
||||
CompressionQuality: quality,
|
||||
Logger: log.NewEntry(log.New()),
|
||||
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
|
||||
HeartbeatInterval: defaultTimeout,
|
||||
MaxHeartbeats: defaultRetries,
|
||||
},
|
||||
|
@ -96,7 +96,7 @@ func NewCompressedMuxerPair(t assert.TestingT, testName string, quality Compress
|
|||
IsClient: false,
|
||||
Name: "edge",
|
||||
CompressionQuality: quality,
|
||||
Logger: log.NewEntry(log.New()),
|
||||
Logger: logger.NewOutputWriter(logger.NewMockWriteManager()),
|
||||
HeartbeatInterval: defaultTimeout,
|
||||
MaxHeartbeats: defaultRetries,
|
||||
},
|
||||
|
@ -301,6 +301,7 @@ func TestSingleStreamLargeResponseBody(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMultipleStreams(t *testing.T) {
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
f := MuxedStreamFunc(func(stream *MuxedStream) error {
|
||||
if len(stream.Headers) != 1 {
|
||||
t.Fatalf("expected %d headers, got %d", 1, len(stream.Headers))
|
||||
|
@ -308,13 +309,13 @@ func TestMultipleStreams(t *testing.T) {
|
|||
if stream.Headers[0].Name != "client-token" {
|
||||
t.Fatalf("expected header name %s, got %s", "client-token", stream.Headers[0].Name)
|
||||
}
|
||||
log.Debugf("Got request for stream %s", stream.Headers[0].Value)
|
||||
l.Debugf("Got request for stream %s", stream.Headers[0].Value)
|
||||
stream.WriteHeaders([]Header{
|
||||
{Name: "response-token", Value: stream.Headers[0].Value},
|
||||
})
|
||||
log.Debugf("Wrote headers for stream %s", stream.Headers[0].Value)
|
||||
l.Debugf("Wrote headers for stream %s", stream.Headers[0].Value)
|
||||
stream.Write([]byte("OK"))
|
||||
log.Debugf("Wrote body for stream %s", stream.Headers[0].Value)
|
||||
l.Debugf("Wrote body for stream %s", stream.Headers[0].Value)
|
||||
return nil
|
||||
})
|
||||
muxPair := NewDefaultMuxerPair(t, t.Name(), f)
|
||||
|
@ -332,7 +333,7 @@ func TestMultipleStreams(t *testing.T) {
|
|||
[]Header{{Name: "client-token", Value: tokenString}},
|
||||
nil,
|
||||
)
|
||||
log.Debugf("Got headers for stream %d", tokenId)
|
||||
l.Debugf("Got headers for stream %d", tokenId)
|
||||
if err != nil {
|
||||
errorsC <- err
|
||||
return
|
||||
|
@ -370,7 +371,7 @@ func TestMultipleStreams(t *testing.T) {
|
|||
testFail := false
|
||||
for err := range errorsC {
|
||||
testFail = true
|
||||
log.Error(err)
|
||||
l.Errorf("%s", err)
|
||||
}
|
||||
if testFail {
|
||||
t.Fatalf("TestMultipleStreams failed")
|
||||
|
@ -448,6 +449,8 @@ func TestMultipleStreamsFlowControl(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGracefulShutdown(t *testing.T) {
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
sendC := make(chan struct{})
|
||||
responseBuf := bytes.Repeat([]byte("Hello world"), 65536)
|
||||
|
||||
|
@ -456,17 +459,17 @@ func TestGracefulShutdown(t *testing.T) {
|
|||
{Name: "response-header", Value: "responseValue"},
|
||||
})
|
||||
<-sendC
|
||||
log.Debugf("Writing %d bytes", len(responseBuf))
|
||||
l.Debugf("Writing %d bytes", len(responseBuf))
|
||||
stream.Write(responseBuf)
|
||||
stream.CloseWrite()
|
||||
log.Debugf("Wrote %d bytes", len(responseBuf))
|
||||
l.Debugf("Wrote %d bytes", len(responseBuf))
|
||||
// Reading from the stream will block until the edge closes its end of the stream.
|
||||
// Otherwise, we'll close the whole connection before receiving the 'stream closed'
|
||||
// message from the edge.
|
||||
// Graceful shutdown works if you omit this, it just gives spurious errors for now -
|
||||
// TODO ignore errors when writing 'stream closed' and we're shutting down.
|
||||
stream.Read([]byte{0})
|
||||
log.Debugf("Handler ends")
|
||||
l.Debugf("Handler ends")
|
||||
return nil
|
||||
})
|
||||
muxPair := NewDefaultMuxerPair(t, t.Name(), f)
|
||||
|
@ -483,7 +486,7 @@ func TestGracefulShutdown(t *testing.T) {
|
|||
muxPair.EdgeMux.Shutdown()
|
||||
close(sendC)
|
||||
responseBody := make([]byte, len(responseBuf))
|
||||
log.Debugf("Waiting for %d bytes", len(responseBuf))
|
||||
l.Debugf("Waiting for %d bytes", len(responseBuf))
|
||||
n, err := io.ReadFull(stream, responseBody)
|
||||
if err != nil {
|
||||
t.Fatalf("error from (*MuxedStream).Read with %d bytes read: %s", n, err)
|
||||
|
@ -676,6 +679,7 @@ func AssertIfPipeReadable(t *testing.T, pipe io.ReadCloser) {
|
|||
}
|
||||
|
||||
func TestMultipleStreamsWithDictionaries(t *testing.T) {
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
for q := CompressionNone; q <= CompressionMax; q++ {
|
||||
htmlBody := `<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"` +
|
||||
|
@ -812,7 +816,7 @@ func TestMultipleStreamsWithDictionaries(t *testing.T) {
|
|||
testFail := false
|
||||
for err := range errorsC {
|
||||
testFail = true
|
||||
log.Error(err)
|
||||
l.Errorf("%s", err)
|
||||
}
|
||||
if testFail {
|
||||
t.Fatalf("TestMultipleStreams failed")
|
||||
|
@ -826,6 +830,8 @@ func TestMultipleStreamsWithDictionaries(t *testing.T) {
|
|||
}
|
||||
|
||||
func sampleSiteHandler(files map[string][]byte) MuxedStreamFunc {
|
||||
l := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
return func(stream *MuxedStream) error {
|
||||
var contentType string
|
||||
var pathHeader Header
|
||||
|
@ -853,13 +859,13 @@ func sampleSiteHandler(files map[string][]byte) MuxedStreamFunc {
|
|||
stream.WriteHeaders([]Header{
|
||||
Header{Name: "content-type", Value: contentType},
|
||||
})
|
||||
log.Debugf("Wrote headers for stream %s", pathHeader.Value)
|
||||
l.Debugf("Wrote headers for stream %s", pathHeader.Value)
|
||||
file, ok := files[pathHeader.Value]
|
||||
if !ok {
|
||||
return fmt.Errorf("%s content is not preloaded", pathHeader.Value)
|
||||
}
|
||||
stream.Write(file)
|
||||
log.Debugf("Wrote body for stream %s", pathHeader.Value)
|
||||
l.Debugf("Wrote body for stream %s", pathHeader.Value)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
163
h2mux/header.go
163
h2mux/header.go
|
@ -1,14 +1,15 @@
|
|||
package h2mux
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Header struct {
|
||||
|
@ -17,12 +18,31 @@ type Header struct {
|
|||
|
||||
var headerEncoding = base64.RawStdEncoding
|
||||
|
||||
// H2RequestHeadersToH1Request converts the HTTP/2 headers to an HTTP/1 Request
|
||||
// object. This includes conversion of the pseudo-headers into their closest
|
||||
const (
|
||||
RequestUserHeadersField = "cf-cloudflared-request-headers"
|
||||
ResponseUserHeadersField = "cf-cloudflared-response-headers"
|
||||
|
||||
ResponseMetaHeaderField = "cf-cloudflared-response-meta"
|
||||
ResponseSourceCloudflared = "cloudflared"
|
||||
ResponseSourceOrigin = "origin"
|
||||
|
||||
CFAccessTokenHeader = "cf-access-token"
|
||||
CFJumpDestinationHeader = "CF-Access-Jump-Destination"
|
||||
CFAccessClientIDHeader = "CF-Access-Client-Id"
|
||||
CFAccessClientSecretHeader = "CF-Access-Client-Secret"
|
||||
)
|
||||
|
||||
// H2RequestHeadersToH1Request converts the HTTP/2 headers coming from origintunneld
|
||||
// to an HTTP/1 Request object destined for the local origin web service.
|
||||
// This operation includes conversion of the pseudo-headers into their closest
|
||||
// HTTP/1 equivalents. See https://tools.ietf.org/html/rfc7540#section-8.1.2.3
|
||||
func H2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error {
|
||||
for _, header := range h2 {
|
||||
switch header.Name {
|
||||
if !IsControlHeader(header.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch strings.ToLower(header.Name) {
|
||||
case ":method":
|
||||
h1.Method = header.Value
|
||||
case ":scheme":
|
||||
|
@ -55,40 +75,100 @@ func H2RequestHeadersToH1Request(h2 []Header, h1 *http.Request) error {
|
|||
base := h1.URL.String()
|
||||
base = strings.TrimRight(base, "/")
|
||||
// But we know :path begins with '/', because we handled '*' above - see RFC7540
|
||||
url, err := url.Parse(base + header.Value)
|
||||
requestURL, err := url.Parse(base + header.Value)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("invalid path '%v'", header.Value))
|
||||
}
|
||||
h1.URL = url
|
||||
h1.URL = requestURL
|
||||
case "content-length":
|
||||
contentLength, err := strconv.ParseInt(header.Value, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unparseable content length")
|
||||
}
|
||||
h1.ContentLength = contentLength
|
||||
case RequestUserHeadersField:
|
||||
// Do not forward the serialized headers to the origin -- deserialize them, and ditch the serialized version
|
||||
// Find and parse user headers serialized into a single one
|
||||
userHeaders, err := ParseUserHeaders(RequestUserHeadersField, h2)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Unable to parse user headers")
|
||||
}
|
||||
for _, userHeader := range userHeaders {
|
||||
h1.Header.Add(http.CanonicalHeaderKey(userHeader.Name), userHeader.Value)
|
||||
}
|
||||
default:
|
||||
// All other control headers shall just be proxied transparently
|
||||
h1.Header.Add(http.CanonicalHeaderKey(header.Name), header.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func H1ResponseToH2ResponseHeaders(h1 *http.Response) (h2 []Header) {
|
||||
h2 = []Header{{Name: ":status", Value: fmt.Sprintf("%d", h1.StatusCode)}}
|
||||
for headerName, headerValues := range h1.Header {
|
||||
for _, headerValue := range headerValues {
|
||||
h2 = append(h2, Header{Name: strings.ToLower(headerName), Value: headerValue})
|
||||
func ParseUserHeaders(headerNameToParseFrom string, headers []Header) ([]Header, error) {
|
||||
for _, header := range headers {
|
||||
if header.Name == headerNameToParseFrom {
|
||||
return DeserializeHeaders(header.Value)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%v header not found", RequestUserHeadersField)
|
||||
}
|
||||
|
||||
func IsControlHeader(headerName string) bool {
|
||||
headerName = strings.ToLower(headerName)
|
||||
|
||||
return headerName == "content-length" ||
|
||||
headerName == "connection" || headerName == "upgrade" || // Websocket headers
|
||||
strings.HasPrefix(headerName, ":") ||
|
||||
strings.HasPrefix(headerName, "cf-")
|
||||
}
|
||||
|
||||
// IsWebsocketClientHeader returns true if the header name is required by the client to upgrade properly
|
||||
func IsWebsocketClientHeader(headerName string) bool {
|
||||
headerName = strings.ToLower(headerName)
|
||||
|
||||
return headerName == "sec-websocket-accept" ||
|
||||
headerName == "connection" ||
|
||||
headerName == "upgrade"
|
||||
}
|
||||
|
||||
func H1ResponseToH2ResponseHeaders(h1 *http.Response) (h2 []Header) {
|
||||
h2 = []Header{
|
||||
{Name: ":status", Value: strconv.Itoa(h1.StatusCode)},
|
||||
}
|
||||
userHeaders := http.Header{}
|
||||
for header, values := range h1.Header {
|
||||
for _, value := range values {
|
||||
if strings.ToLower(header) == "content-length" {
|
||||
// This header has meaning in HTTP/2 and will be used by the edge,
|
||||
// so it should be sent as an HTTP/2 response header.
|
||||
|
||||
// Since these are http2 headers, they're required to be lowercase
|
||||
h2 = append(h2, Header{Name: strings.ToLower(header), Value: value})
|
||||
} else if !IsControlHeader(header) || IsWebsocketClientHeader(header) {
|
||||
// User headers, on the other hand, must all be serialized so that
|
||||
// HTTP/2 header validation won't be applied to HTTP/1 header values
|
||||
if _, ok := userHeaders[header]; ok {
|
||||
userHeaders[header] = append(userHeaders[header], value)
|
||||
} else {
|
||||
userHeaders[header] = []string{value}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Perform user header serialization and set them in the single header
|
||||
h2 = append(h2, CreateSerializedHeaders(ResponseUserHeadersField, userHeaders)...)
|
||||
|
||||
return h2
|
||||
}
|
||||
|
||||
// Serialize HTTP1.x headers by base64-encoding each header name and value,
|
||||
// and then joining them in the format of [key:value;]
|
||||
func SerializeHeaders(h1 *http.Request) []byte {
|
||||
var serializedHeaders [][]byte
|
||||
for headerName, headerValues := range h1.Header {
|
||||
func SerializeHeaders(h1Headers http.Header) string {
|
||||
var serializedHeaders []string
|
||||
for headerName, headerValues := range h1Headers {
|
||||
for _, headerValue := range headerValues {
|
||||
encodedName := make([]byte, headerEncoding.EncodedLen(len(headerName)))
|
||||
headerEncoding.Encode(encodedName, []byte(headerName))
|
||||
|
@ -98,28 +178,28 @@ func SerializeHeaders(h1 *http.Request) []byte {
|
|||
|
||||
serializedHeaders = append(
|
||||
serializedHeaders,
|
||||
bytes.Join(
|
||||
[][]byte{encodedName, encodedValue},
|
||||
[]byte(":"),
|
||||
strings.Join(
|
||||
[]string{string(encodedName), string(encodedValue)},
|
||||
":",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return bytes.Join(serializedHeaders, []byte(";"))
|
||||
return strings.Join(serializedHeaders, ";")
|
||||
}
|
||||
|
||||
// Deserialize headers serialized by `SerializeHeader`
|
||||
func DeserializeHeaders(serializedHeaders []byte) (http.Header, error) {
|
||||
func DeserializeHeaders(serializedHeaders string) ([]Header, error) {
|
||||
const unableToDeserializeErr = "Unable to deserialize headers"
|
||||
|
||||
deserialized := http.Header{}
|
||||
for _, serializedPair := range bytes.Split(serializedHeaders, []byte(";")) {
|
||||
var deserialized []Header
|
||||
for _, serializedPair := range strings.Split(serializedHeaders, ";") {
|
||||
if len(serializedPair) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
serializedHeaderParts := bytes.Split(serializedPair, []byte(":"))
|
||||
serializedHeaderParts := strings.Split(serializedPair, ":")
|
||||
if len(serializedHeaderParts) != 2 {
|
||||
return nil, errors.New(unableToDeserializeErr)
|
||||
}
|
||||
|
@ -129,15 +209,46 @@ func DeserializeHeaders(serializedHeaders []byte) (http.Header, error) {
|
|||
deserializedName := make([]byte, headerEncoding.DecodedLen(len(serializedName)))
|
||||
deserializedValue := make([]byte, headerEncoding.DecodedLen(len(serializedValue)))
|
||||
|
||||
if _, err := headerEncoding.Decode(deserializedName, serializedName); err != nil {
|
||||
if _, err := headerEncoding.Decode(deserializedName, []byte(serializedName)); err != nil {
|
||||
return nil, errors.Wrap(err, unableToDeserializeErr)
|
||||
}
|
||||
if _, err := headerEncoding.Decode(deserializedValue, serializedValue); err != nil {
|
||||
if _, err := headerEncoding.Decode(deserializedValue, []byte(serializedValue)); err != nil {
|
||||
return nil, errors.Wrap(err, unableToDeserializeErr)
|
||||
}
|
||||
|
||||
deserialized.Add(string(deserializedName), string(deserializedValue))
|
||||
deserialized = append(deserialized, Header{
|
||||
Name: string(deserializedName),
|
||||
Value: string(deserializedValue),
|
||||
})
|
||||
}
|
||||
|
||||
return deserialized, nil
|
||||
}
|
||||
|
||||
func CreateSerializedHeaders(headersField string, headers ...http.Header) []Header {
|
||||
var serializedHeaderChunks []string
|
||||
for _, headerChunk := range headers {
|
||||
serializedHeaderChunks = append(serializedHeaderChunks, SerializeHeaders(headerChunk))
|
||||
}
|
||||
|
||||
return []Header{{
|
||||
headersField,
|
||||
strings.Join(serializedHeaderChunks, ";"),
|
||||
}}
|
||||
}
|
||||
|
||||
type ResponseMetaHeader struct {
|
||||
Source string `json:"src"`
|
||||
}
|
||||
|
||||
func CreateResponseMetaHeader(headerName, source string) Header {
|
||||
jsonResponseMetaHeader, err := json.Marshal(ResponseMetaHeader{Source: source})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return Header{
|
||||
Name: headerName,
|
||||
Value: string(jsonResponseMetaHeader),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"net/url"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
@ -15,29 +16,30 @@ import (
|
|||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type ByName []Header
|
||||
|
||||
func (a ByName) Len() int { return len(a) }
|
||||
func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
func (a ByName) Less(i, j int) bool {
|
||||
if a[i].Name == a[j].Name {
|
||||
return a[i].Value < a[j].Value
|
||||
}
|
||||
|
||||
return a[i].Name < a[j].Name
|
||||
}
|
||||
|
||||
func TestH2RequestHeadersToH1Request_RegularHeaders(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(
|
||||
[]Header{
|
||||
{
|
||||
Name: "Mock header 1",
|
||||
Value: "Mock value 1",
|
||||
},
|
||||
{
|
||||
Name: "Mock header 2",
|
||||
Value: "Mock value 2",
|
||||
},
|
||||
},
|
||||
request,
|
||||
)
|
||||
mockHeaders := http.Header{
|
||||
"Mock header 1": {"Mock value 1"},
|
||||
"Mock header 2": {"Mock value 2"},
|
||||
}
|
||||
|
||||
assert.Equal(t, http.Header{
|
||||
"Mock header 1": []string{"Mock value 1"},
|
||||
"Mock header 2": []string{"Mock value 2"},
|
||||
}, request.Header)
|
||||
headersConversionErr := H2RequestHeadersToH1Request(CreateSerializedHeaders(RequestUserHeadersField, mockHeaders), request)
|
||||
|
||||
assert.True(t, reflect.DeepEqual(mockHeaders, request.Header))
|
||||
assert.NoError(t, headersConversionErr)
|
||||
}
|
||||
|
||||
|
@ -46,12 +48,14 @@ func TestH2RequestHeadersToH1Request_NoHeaders(t *testing.T) {
|
|||
assert.NoError(t, err)
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(
|
||||
[]Header{},
|
||||
[]Header{{
|
||||
RequestUserHeadersField,
|
||||
SerializeHeaders(http.Header{}),
|
||||
}},
|
||||
request,
|
||||
)
|
||||
|
||||
assert.Equal(t, http.Header{}, request.Header)
|
||||
|
||||
assert.True(t, reflect.DeepEqual(http.Header{}, request.Header))
|
||||
assert.NoError(t, headersConversionErr)
|
||||
}
|
||||
|
||||
|
@ -59,19 +63,12 @@ func TestH2RequestHeadersToH1Request_InvalidHostPath(t *testing.T) {
|
|||
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(
|
||||
[]Header{
|
||||
{
|
||||
Name: ":path",
|
||||
Value: "//bad_path/",
|
||||
},
|
||||
{
|
||||
Name: "Mock header",
|
||||
Value: "Mock value",
|
||||
},
|
||||
},
|
||||
request,
|
||||
)
|
||||
mockRequestHeaders := []Header{
|
||||
{Name: ":path", Value: "//bad_path/"},
|
||||
{Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
|
||||
}
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
|
||||
|
||||
assert.Equal(t, http.Header{
|
||||
"Mock header": []string{"Mock value"},
|
||||
|
@ -86,19 +83,12 @@ func TestH2RequestHeadersToH1Request_HostPathWithQuery(t *testing.T) {
|
|||
request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(
|
||||
[]Header{
|
||||
{
|
||||
Name: ":path",
|
||||
Value: "/?query=mock%20value",
|
||||
},
|
||||
{
|
||||
Name: "Mock header",
|
||||
Value: "Mock value",
|
||||
},
|
||||
},
|
||||
request,
|
||||
)
|
||||
mockRequestHeaders := []Header{
|
||||
{Name: ":path", Value: "/?query=mock%20value"},
|
||||
{Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
|
||||
}
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
|
||||
|
||||
assert.Equal(t, http.Header{
|
||||
"Mock header": []string{"Mock value"},
|
||||
|
@ -113,19 +103,12 @@ func TestH2RequestHeadersToH1Request_HostPathWithURLEncoding(t *testing.T) {
|
|||
request, err := http.NewRequest(http.MethodGet, "http://example.com/", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(
|
||||
[]Header{
|
||||
{
|
||||
Name: ":path",
|
||||
Value: "/mock%20path",
|
||||
},
|
||||
{
|
||||
Name: "Mock header",
|
||||
Value: "Mock value",
|
||||
},
|
||||
},
|
||||
request,
|
||||
)
|
||||
mockRequestHeaders := []Header{
|
||||
{Name: ":path", Value: "/mock%20path"},
|
||||
{Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
|
||||
}
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
|
||||
|
||||
assert.Equal(t, http.Header{
|
||||
"Mock header": []string{"Mock value"},
|
||||
|
@ -276,19 +259,13 @@ func TestH2RequestHeadersToH1Request_WeirdURLs(t *testing.T) {
|
|||
|
||||
request, err := http.NewRequest(http.MethodGet, requestURL, nil)
|
||||
assert.NoError(t, err)
|
||||
headersConversionErr := H2RequestHeadersToH1Request(
|
||||
[]Header{
|
||||
{
|
||||
Name: ":path",
|
||||
Value: testCase.path,
|
||||
},
|
||||
{
|
||||
Name: "Mock header",
|
||||
Value: "Mock value",
|
||||
},
|
||||
},
|
||||
request,
|
||||
)
|
||||
|
||||
mockRequestHeaders := []Header{
|
||||
{Name: ":path", Value: testCase.path},
|
||||
{Name: RequestUserHeadersField, Value: SerializeHeaders(http.Header{"Mock header": {"Mock value"}})},
|
||||
}
|
||||
|
||||
headersConversionErr := H2RequestHeadersToH1Request(mockRequestHeaders, request)
|
||||
assert.NoError(t, headersConversionErr)
|
||||
|
||||
assert.Equal(t,
|
||||
|
@ -358,6 +335,7 @@ func TestH2RequestHeadersToH1Request_QuickCheck(t *testing.T) {
|
|||
{Name: ":scheme", Value: testScheme},
|
||||
{Name: ":authority", Value: expectedHostname},
|
||||
{Name: ":path", Value: testPath},
|
||||
{Name: RequestUserHeadersField, Value: ""},
|
||||
}
|
||||
h1, err := http.NewRequest("GET", testOrigin.url, nil)
|
||||
require.NoError(t, err)
|
||||
|
@ -439,11 +417,21 @@ func randomHTTP2Path(t *testing.T, rand *rand.Rand) string {
|
|||
return result
|
||||
}
|
||||
|
||||
func stdlibHeaderToH2muxHeader(headers http.Header) (h2muxHeaders []Header) {
|
||||
for name, values := range headers {
|
||||
for _, value := range values {
|
||||
h2muxHeaders = append(h2muxHeaders, Header{name, value})
|
||||
}
|
||||
}
|
||||
|
||||
return h2muxHeaders
|
||||
}
|
||||
|
||||
func TestSerializeHeaders(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
mockHeaders := map[string][]string{
|
||||
mockHeaders := http.Header{
|
||||
"Mock-Header-One": {"Mock header one value", "three"},
|
||||
"Mock-Header-Two-Long": {"Mock header two value\nlong"},
|
||||
":;": {":;", ";:"},
|
||||
|
@ -465,7 +453,7 @@ func TestSerializeHeaders(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
serializedHeaders := SerializeHeaders(request)
|
||||
serializedHeaders := SerializeHeaders(request.Header)
|
||||
|
||||
// Sanity check: the headers serialized to something that's not an empty string
|
||||
assert.NotEqual(t, "", serializedHeaders)
|
||||
|
@ -474,18 +462,24 @@ func TestSerializeHeaders(t *testing.T) {
|
|||
deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, len(mockHeaders), len(deserializedHeaders))
|
||||
for header, value := range deserializedHeaders {
|
||||
assert.NotEqual(t, "", value)
|
||||
assert.Equal(t, mockHeaders[header], value)
|
||||
}
|
||||
assert.Equal(t, 13, len(deserializedHeaders))
|
||||
h2muxExpectedHeaders := stdlibHeaderToH2muxHeader(mockHeaders)
|
||||
|
||||
sort.Sort(ByName(deserializedHeaders))
|
||||
sort.Sort(ByName(h2muxExpectedHeaders))
|
||||
|
||||
assert.True(
|
||||
t,
|
||||
reflect.DeepEqual(h2muxExpectedHeaders, deserializedHeaders),
|
||||
fmt.Sprintf("got = %#v, want = %#v\n", deserializedHeaders, h2muxExpectedHeaders),
|
||||
)
|
||||
}
|
||||
|
||||
func TestSerializeNoHeaders(t *testing.T) {
|
||||
request, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
assert.NoError(t, err)
|
||||
|
||||
serializedHeaders := SerializeHeaders(request)
|
||||
serializedHeaders := SerializeHeaders(request.Header)
|
||||
deserializedHeaders, err := DeserializeHeaders(serializedHeaders)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 0, len(deserializedHeaders))
|
||||
|
@ -502,7 +496,157 @@ func TestDeserializeMalformed(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, malformedValue := range malformedData {
|
||||
_, err = DeserializeHeaders([]byte(malformedValue))
|
||||
_, err = DeserializeHeaders(malformedValue)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHeaders(t *testing.T) {
|
||||
mockUserHeadersToSerialize := http.Header{
|
||||
"Mock-Header-One": {"1", "1.5"},
|
||||
"Mock-Header-Two": {"2"},
|
||||
"Mock-Header-Three": {"3"},
|
||||
}
|
||||
|
||||
mockHeaders := []Header{
|
||||
{Name: "One", Value: "1"},
|
||||
{Name: "Cf-Two", Value: "cf-value-1"},
|
||||
{Name: "Cf-Two", Value: "cf-value-2"},
|
||||
{Name: RequestUserHeadersField, Value: SerializeHeaders(mockUserHeadersToSerialize)},
|
||||
}
|
||||
|
||||
expectedHeaders := []Header{
|
||||
{Name: "Mock-Header-One", Value: "1"},
|
||||
{Name: "Mock-Header-One", Value: "1.5"},
|
||||
{Name: "Mock-Header-Two", Value: "2"},
|
||||
{Name: "Mock-Header-Three", Value: "3"},
|
||||
}
|
||||
parsedHeaders, err := ParseUserHeaders(RequestUserHeadersField, mockHeaders)
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, expectedHeaders, parsedHeaders)
|
||||
}
|
||||
|
||||
func TestParseHeadersNoSerializedHeader(t *testing.T) {
|
||||
mockHeaders := []Header{
|
||||
{Name: "One", Value: "1"},
|
||||
{Name: "Cf-Two", Value: "cf-value-1"},
|
||||
{Name: "Cf-Two", Value: "cf-value-2"},
|
||||
}
|
||||
|
||||
_, err := ParseUserHeaders(RequestUserHeadersField, mockHeaders)
|
||||
assert.EqualError(t, err, fmt.Sprintf("%s header not found", RequestUserHeadersField))
|
||||
}
|
||||
|
||||
func TestIsControlHeader(t *testing.T) {
|
||||
controlHeaders := []string{
|
||||
// Anything that begins with cf-
|
||||
"cf-sample-header",
|
||||
"CF-SAMPLE-HEADER",
|
||||
"Cf-Sample-Header",
|
||||
|
||||
// Any http2 pseudoheader
|
||||
":sample-pseudo-header",
|
||||
|
||||
// content-length is a special case, it has to be there
|
||||
// for some requests to work (per the HTTP2 spec)
|
||||
"content-length",
|
||||
}
|
||||
|
||||
for _, header := range controlHeaders {
|
||||
assert.True(t, IsControlHeader(header))
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNotControlHeader(t *testing.T) {
|
||||
notControlHeaders := []string{
|
||||
"Mock-header",
|
||||
"Another-sample-header",
|
||||
}
|
||||
|
||||
for _, header := range notControlHeaders {
|
||||
assert.False(t, IsControlHeader(header))
|
||||
}
|
||||
}
|
||||
|
||||
func TestH1ResponseToH2ResponseHeaders(t *testing.T) {
|
||||
mockHeaders := http.Header{
|
||||
"User-header-one": {""},
|
||||
"User-header-two": {"1", "2"},
|
||||
"cf-header": {"cf-value"},
|
||||
"Content-Length": {"123"},
|
||||
}
|
||||
mockResponse := http.Response{
|
||||
StatusCode: 200,
|
||||
Header: mockHeaders,
|
||||
}
|
||||
|
||||
headers := H1ResponseToH2ResponseHeaders(&mockResponse)
|
||||
|
||||
serializedHeadersIndex := -1
|
||||
for i, header := range headers {
|
||||
if header.Name == ResponseUserHeadersField {
|
||||
serializedHeadersIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
assert.NotEqual(t, -1, serializedHeadersIndex)
|
||||
actualControlHeaders := append(
|
||||
headers[:serializedHeadersIndex],
|
||||
headers[serializedHeadersIndex+1:]...,
|
||||
)
|
||||
expectedControlHeaders := []Header{
|
||||
{Name: ":status", Value: "200"},
|
||||
{Name: "content-length", Value: "123"},
|
||||
}
|
||||
|
||||
assert.ElementsMatch(t, expectedControlHeaders, actualControlHeaders)
|
||||
|
||||
actualUserHeaders, err := DeserializeHeaders(headers[serializedHeadersIndex].Value)
|
||||
expectedUserHeaders := []Header{
|
||||
{Name: "User-header-one", Value: ""},
|
||||
{Name: "User-header-two", Value: "1"},
|
||||
{Name: "User-header-two", Value: "2"},
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
assert.ElementsMatch(t, expectedUserHeaders, actualUserHeaders)
|
||||
}
|
||||
|
||||
// The purpose of this test is to check that our code and the http.Header
|
||||
// implementation don't throw validation errors about header size
|
||||
func TestHeaderSize(t *testing.T) {
|
||||
largeValue := randSeq(5 * 1024 * 1024) // 5Mb
|
||||
largeHeaders := http.Header{
|
||||
"User-header": {largeValue},
|
||||
}
|
||||
mockResponse := http.Response{
|
||||
StatusCode: 200,
|
||||
Header: largeHeaders,
|
||||
}
|
||||
|
||||
serializedHeaders := H1ResponseToH2ResponseHeaders(&mockResponse)
|
||||
request, err := http.NewRequest(http.MethodGet, "https://example.com/", nil)
|
||||
assert.NoError(t, err)
|
||||
for _, header := range serializedHeaders {
|
||||
request.Header.Set(header.Name, header.Value)
|
||||
}
|
||||
|
||||
for _, header := range serializedHeaders {
|
||||
if header.Name != ResponseUserHeadersField {
|
||||
continue
|
||||
}
|
||||
|
||||
deserializedHeaders, err := DeserializeHeaders(header.Value)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, largeValue, deserializedHeaders[0].Value)
|
||||
}
|
||||
}
|
||||
|
||||
func randSeq(n int) string {
|
||||
randomizer := rand.New(rand.NewSource(17))
|
||||
var letters = []rune(":;,+/=abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letters[randomizer.Intn(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
|
|
@ -4,10 +4,9 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/golang-collections/collections/queue"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// data points used to compute average receive window and send window size
|
||||
|
@ -22,7 +21,7 @@ type muxMetricsUpdater interface {
|
|||
// metrics returns the latest metrics
|
||||
metrics() *MuxerMetrics
|
||||
// run is a blocking call to start the event loop
|
||||
run(logger *log.Entry) error
|
||||
run(logger logger.Service) error
|
||||
// updateRTTChan is called by muxReader to report new RTT measurements
|
||||
updateRTT(rtt *roundTripMeasurement)
|
||||
//updateReceiveWindowChan is called by muxReader and muxWriter when receiveWindow size is updated
|
||||
|
@ -139,34 +138,30 @@ func (updater *muxMetricsUpdaterImpl) metrics() *MuxerMetrics {
|
|||
return m
|
||||
}
|
||||
|
||||
func (updater *muxMetricsUpdaterImpl) run(parentLogger *log.Entry) error {
|
||||
logger := parentLogger.WithFields(log.Fields{
|
||||
"subsystem": "mux",
|
||||
"dir": "metrics",
|
||||
})
|
||||
defer logger.Debug("event loop finished")
|
||||
func (updater *muxMetricsUpdaterImpl) run(logger logger.Service) error {
|
||||
defer logger.Debug("mux - metrics: event loop finished")
|
||||
for {
|
||||
select {
|
||||
case <-updater.abortChan:
|
||||
logger.Infof("Stopping mux metrics updater")
|
||||
logger.Infof("mux - metrics: Stopping mux metrics updater")
|
||||
return nil
|
||||
case roundTripMeasurement := <-updater.updateRTTChan:
|
||||
go updater.rttData.update(roundTripMeasurement)
|
||||
logger.Debug("Update rtt")
|
||||
logger.Debug("mux - metrics: Update rtt")
|
||||
case receiveWindow := <-updater.updateReceiveWindowChan:
|
||||
go updater.receiveWindowData.update(receiveWindow)
|
||||
logger.Debug("Update receive window")
|
||||
logger.Debug("mux - metrics: Update receive window")
|
||||
case sendWindow := <-updater.updateSendWindowChan:
|
||||
go updater.sendWindowData.update(sendWindow)
|
||||
logger.Debug("Update send window")
|
||||
logger.Debug("mux - metrics: Update send window")
|
||||
case inBoundBytes := <-updater.updateInBoundBytesChan:
|
||||
// inBoundBytes is bytes/sec because the update interval is 1 sec
|
||||
go updater.inBoundRate.update(inBoundBytes)
|
||||
logger.Debugf("Inbound bytes %d", inBoundBytes)
|
||||
logger.Debugf("mux - metrics: Inbound bytes %d", inBoundBytes)
|
||||
case outBoundBytes := <-updater.updateOutBoundBytesChan:
|
||||
// outBoundBytes is bytes/sec because the update interval is 1 sec
|
||||
go updater.outBoundRate.update(outBoundBytes)
|
||||
logger.Debugf("Outbound bytes %d", outBoundBytes)
|
||||
logger.Debugf("mux - metrics: Outbound bytes %d", outBoundBytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
@ -91,7 +91,7 @@ func TestMuxMetricsUpdater(t *testing.T) {
|
|||
abortChan := make(chan struct{})
|
||||
compBefore, compAfter := NewAtomicCounter(0), NewAtomicCounter(0)
|
||||
m := newMuxMetricsUpdater(abortChan, compBefore, compAfter)
|
||||
logger := log.NewEntry(log.New())
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
go func() {
|
||||
errChan <- m.run(logger)
|
||||
|
|
|
@ -3,11 +3,12 @@ package h2mux
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"golang.org/x/net/http2"
|
||||
)
|
||||
|
||||
|
@ -67,12 +68,8 @@ func (r *MuxReader) Shutdown() <-chan struct{} {
|
|||
return done
|
||||
}
|
||||
|
||||
func (r *MuxReader) run(parentLogger *log.Entry) error {
|
||||
logger := parentLogger.WithFields(log.Fields{
|
||||
"subsystem": "mux",
|
||||
"dir": "read",
|
||||
})
|
||||
defer logger.Debug("event loop finished")
|
||||
func (r *MuxReader) run(logger logger.Service) error {
|
||||
defer logger.Debug("mux - read: event loop finished")
|
||||
|
||||
// routine to periodically update bytesRead
|
||||
go func() {
|
||||
|
@ -90,13 +87,13 @@ func (r *MuxReader) run(parentLogger *log.Entry) error {
|
|||
for {
|
||||
frame, err := r.f.ReadFrame()
|
||||
if err != nil {
|
||||
errLogger := logger.WithError(err)
|
||||
errorString := fmt.Sprintf("mux - read: %s", err)
|
||||
if errorDetail := r.f.ErrorDetail(); errorDetail != nil {
|
||||
errLogger = errLogger.WithField("errorDetail", errorDetail)
|
||||
errorString = fmt.Sprintf("%s: errorDetail: %s", errorString, errorDetail)
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case http2.StreamError:
|
||||
errLogger.Warn("stream error")
|
||||
logger.Infof("%s: stream error", errorString)
|
||||
// Ideally we wouldn't return here, since that aborts the muxer.
|
||||
// We should communicate the error to the relevant MuxedStream
|
||||
// data structure, so that callers of MuxedStream.Read() and
|
||||
|
@ -104,25 +101,25 @@ func (r *MuxReader) run(parentLogger *log.Entry) error {
|
|||
// and keep the muxer going.
|
||||
return r.streamError(e.StreamID, e.Code)
|
||||
case http2.ConnectionError:
|
||||
errLogger.Warn("connection error")
|
||||
logger.Infof("%s: stream error", errorString)
|
||||
return r.connectionError(err)
|
||||
default:
|
||||
if isConnectionClosedError(err) {
|
||||
if r.streams.Len() == 0 {
|
||||
// don't log the error here -- that would just be extra noise
|
||||
logger.Debug("shutting down")
|
||||
logger.Debug("mux - read: shutting down")
|
||||
return nil
|
||||
}
|
||||
errLogger.Warn("connection closed unexpectedly")
|
||||
logger.Infof("%s: connection closed unexpectedly", errorString)
|
||||
return err
|
||||
} else {
|
||||
errLogger.Warn("frame read error")
|
||||
logger.Infof("%s: frame read error", errorString)
|
||||
return r.connectionError(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
r.connActive.Signal()
|
||||
logger.WithField("data", frame).Debug("read frame")
|
||||
logger.Debugf("mux - read: read frame: data %v", frame)
|
||||
switch f := frame.(type) {
|
||||
case *http2.DataFrame:
|
||||
err = r.receiveFrameData(f, logger)
|
||||
|
@ -158,7 +155,7 @@ func (r *MuxReader) run(parentLogger *log.Entry) error {
|
|||
err = ErrUnexpectedFrameType
|
||||
}
|
||||
if err != nil {
|
||||
logger.WithField("data", frame).WithError(err).Debug("frame error")
|
||||
logger.Debugf("mux - read: read error: data %v", frame)
|
||||
return r.connectionError(err)
|
||||
}
|
||||
}
|
||||
|
@ -279,8 +276,7 @@ func (r *MuxReader) handleStream(stream *MuxedStream) {
|
|||
}
|
||||
|
||||
// Receives a data frame from a stream. A non-nil error is a connection error.
|
||||
func (r *MuxReader) receiveFrameData(frame *http2.DataFrame, parentLogger *log.Entry) error {
|
||||
logger := parentLogger.WithField("stream", frame.Header().StreamID)
|
||||
func (r *MuxReader) receiveFrameData(frame *http2.DataFrame, logger logger.Service) error {
|
||||
stream, err := r.getStreamForFrame(frame)
|
||||
if err != nil {
|
||||
return r.defaultStreamErrorHandler(err, frame.Header())
|
||||
|
@ -296,9 +292,9 @@ func (r *MuxReader) receiveFrameData(frame *http2.DataFrame, parentLogger *log.E
|
|||
if frame.Header().Flags.Has(http2.FlagDataEndStream) {
|
||||
if stream.receiveEOF() {
|
||||
r.streams.Delete(stream.streamID)
|
||||
logger.Debug("stream closed")
|
||||
logger.Debugf("mux - read: stream closed: streamID: %d", frame.Header().StreamID)
|
||||
} else {
|
||||
logger.Debug("shutdown receive side")
|
||||
logger.Debugf("mux - read: shutdown receive side: streamID: %d", frame.Header().StreamID)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"io"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"golang.org/x/net/http2"
|
||||
"golang.org/x/net/http2/hpack"
|
||||
)
|
||||
|
@ -72,12 +72,8 @@ func tsToPingData(ts int64) [8]byte {
|
|||
return pingData
|
||||
}
|
||||
|
||||
func (w *MuxWriter) run(parentLogger *log.Entry) error {
|
||||
logger := parentLogger.WithFields(log.Fields{
|
||||
"subsystem": "mux",
|
||||
"dir": "write",
|
||||
})
|
||||
defer logger.Debug("event loop finished")
|
||||
func (w *MuxWriter) run(logger logger.Service) error {
|
||||
defer logger.Debug("mux - write: event loop finished")
|
||||
|
||||
// routine to periodically communicate bytesWrote
|
||||
go func() {
|
||||
|
@ -95,17 +91,17 @@ func (w *MuxWriter) run(parentLogger *log.Entry) error {
|
|||
for {
|
||||
select {
|
||||
case <-w.abortChan:
|
||||
logger.Debug("aborting writer thread")
|
||||
logger.Debug("mux - write: aborting writer thread")
|
||||
return nil
|
||||
case errCode := <-w.goAwayChan:
|
||||
logger.Debug("sending GOAWAY code ", errCode)
|
||||
logger.Debugf("mux - write: sending GOAWAY code %v", errCode)
|
||||
err := w.f.WriteGoAway(w.streams.LastPeerStreamID(), errCode, []byte{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.idleTimer.MarkActive()
|
||||
case <-w.pingTimestamp.GetUpdateChan():
|
||||
logger.Debug("sending PING ACK")
|
||||
logger.Debug("mux - write: sending PING ACK")
|
||||
err := w.f.WritePing(true, tsToPingData(w.pingTimestamp.Get()))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -115,7 +111,7 @@ func (w *MuxWriter) run(parentLogger *log.Entry) error {
|
|||
if !w.idleTimer.Retry() {
|
||||
return ErrConnectionDropped
|
||||
}
|
||||
logger.Debug("sending PING")
|
||||
logger.Debug("mux - write: sending PING")
|
||||
err := w.f.WritePing(false, tsToPingData(time.Now().UnixNano()))
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -125,7 +121,7 @@ func (w *MuxWriter) run(parentLogger *log.Entry) error {
|
|||
w.idleTimer.MarkActive()
|
||||
case <-w.streamErrors.GetSignalChan():
|
||||
for streamID, errCode := range w.streamErrors.GetErrors() {
|
||||
logger.WithField("stream", streamID).WithField("code", errCode).Debug("resetting stream")
|
||||
logger.Debugf("mux - write: resetting stream with code: %v streamID: %d", errCode, streamID)
|
||||
err := w.f.WriteRSTStream(streamID, errCode)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -145,19 +141,17 @@ func (w *MuxWriter) run(parentLogger *log.Entry) error {
|
|||
if streamRequest.body != nil {
|
||||
go streamRequest.flushBody()
|
||||
}
|
||||
streamLogger := logger.WithField("stream", streamID)
|
||||
err := w.writeStreamData(streamRequest.stream, streamLogger)
|
||||
err := w.writeStreamData(streamRequest.stream, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
w.idleTimer.MarkActive()
|
||||
case streamID := <-w.readyStreamChan:
|
||||
streamLogger := logger.WithField("stream", streamID)
|
||||
stream, ok := w.streams.Get(streamID)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
err := w.writeStreamData(stream, streamLogger)
|
||||
err := w.writeStreamData(stream, logger)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -165,7 +159,7 @@ func (w *MuxWriter) run(parentLogger *log.Entry) error {
|
|||
case useDict := <-w.useDictChan:
|
||||
err := w.writeUseDictionary(useDict)
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("error writing use dictionary")
|
||||
logger.Errorf("mux - write: error writing use dictionary: %s", err)
|
||||
return err
|
||||
}
|
||||
w.idleTimer.MarkActive()
|
||||
|
@ -173,18 +167,18 @@ func (w *MuxWriter) run(parentLogger *log.Entry) error {
|
|||
}
|
||||
}
|
||||
|
||||
func (w *MuxWriter) writeStreamData(stream *MuxedStream, logger *log.Entry) error {
|
||||
logger.Debug("writable")
|
||||
func (w *MuxWriter) writeStreamData(stream *MuxedStream, logger logger.Service) error {
|
||||
logger.Debugf("mux - write: writable: streamID: %d", stream.streamID)
|
||||
chunk := stream.getChunk()
|
||||
w.metricsUpdater.updateReceiveWindow(stream.getReceiveWindow())
|
||||
w.metricsUpdater.updateSendWindow(stream.getSendWindow())
|
||||
if chunk.sendHeadersFrame() {
|
||||
err := w.writeHeaders(chunk.streamID, chunk.headers)
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("error writing headers")
|
||||
logger.Errorf("mux - write: error writing headers: %s: streamID: %d", err, stream.streamID)
|
||||
return err
|
||||
}
|
||||
logger.Debug("output headers")
|
||||
logger.Debugf("mux - write: output headers: streamID: %d", stream.streamID)
|
||||
}
|
||||
|
||||
if chunk.sendWindowUpdateFrame() {
|
||||
|
@ -195,22 +189,22 @@ func (w *MuxWriter) writeStreamData(stream *MuxedStream, logger *log.Entry) erro
|
|||
// window, unless the receiver treats this as a connection error"
|
||||
err := w.f.WriteWindowUpdate(chunk.streamID, chunk.windowUpdate)
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("error writing window update")
|
||||
logger.Errorf("mux - write: error writing window update: %s: streamID: %d", err, stream.streamID)
|
||||
return err
|
||||
}
|
||||
logger.Debugf("increment receive window by %d", chunk.windowUpdate)
|
||||
logger.Debugf("mux - write: increment receive window by %d streamID: %d", chunk.windowUpdate, stream.streamID)
|
||||
}
|
||||
|
||||
for chunk.sendDataFrame() {
|
||||
payload, sentEOF := chunk.nextDataFrame(int(w.maxFrameSize))
|
||||
err := w.f.WriteData(chunk.streamID, sentEOF, payload)
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("error writing data")
|
||||
logger.Errorf("mux - write: error writing data: %s: streamID: %d", err, stream.streamID)
|
||||
return err
|
||||
}
|
||||
// update the amount of data wrote
|
||||
w.bytesWrote.IncrementBy(uint64(len(payload)))
|
||||
logger.WithField("len", len(payload)).Debug("output data")
|
||||
logger.Debugf("mux - write: output data: %d: streamID: %d", len(payload), stream.streamID)
|
||||
|
||||
if sentEOF {
|
||||
if stream.readBuffer.Closed() {
|
||||
|
@ -218,15 +212,15 @@ func (w *MuxWriter) writeStreamData(stream *MuxedStream, logger *log.Entry) erro
|
|||
if !stream.gotReceiveEOF() {
|
||||
// the peer may send data that we no longer want to receive. Force them into the
|
||||
// closed state.
|
||||
logger.Debug("resetting stream")
|
||||
logger.Debugf("mux - write: resetting stream: streamID: %d", stream.streamID)
|
||||
w.f.WriteRSTStream(chunk.streamID, http2.ErrCodeNo)
|
||||
} else {
|
||||
// Half-open stream transitioned into closed
|
||||
logger.Debug("closing stream")
|
||||
logger.Debugf("mux - write: closing stream: streamID: %d", stream.streamID)
|
||||
}
|
||||
w.streams.Delete(chunk.streamID)
|
||||
} else {
|
||||
logger.Debug("closing stream write side")
|
||||
logger.Debugf("mux - write: closing stream write side: streamID: %d", stream.streamID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -250,28 +244,52 @@ func (w *MuxWriter) encodeHeaders(headers []Header) ([]byte, error) {
|
|||
// writeHeaders writes a block of encoded headers, splitting it into multiple frames if necessary.
|
||||
func (w *MuxWriter) writeHeaders(streamID uint32, headers []Header) error {
|
||||
encodedHeaders, err := w.encodeHeaders(headers)
|
||||
if err != nil {
|
||||
if err != nil || len(encodedHeaders) == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
blockSize := int(w.maxFrameSize)
|
||||
endHeaders := len(encodedHeaders) == 0
|
||||
for !endHeaders && err == nil {
|
||||
blockFragment := encodedHeaders
|
||||
if len(encodedHeaders) > blockSize {
|
||||
blockFragment = blockFragment[:blockSize]
|
||||
encodedHeaders = encodedHeaders[blockSize:]
|
||||
// Send CONTINUATION frame if the headers can't be fit into 1 frame
|
||||
err = w.f.WriteContinuation(streamID, endHeaders, blockFragment)
|
||||
} else {
|
||||
endHeaders = true
|
||||
err = w.f.WriteHeaders(http2.HeadersFrameParam{
|
||||
StreamID: streamID,
|
||||
EndHeaders: endHeaders,
|
||||
BlockFragment: blockFragment,
|
||||
})
|
||||
// CONTINUATION is unnecessary; the headers fit within the blockSize
|
||||
if len(encodedHeaders) < blockSize {
|
||||
return w.f.WriteHeaders(http2.HeadersFrameParam{
|
||||
StreamID: streamID,
|
||||
EndHeaders: true,
|
||||
BlockFragment: encodedHeaders,
|
||||
})
|
||||
}
|
||||
|
||||
choppedHeaders := chopEncodedHeaders(encodedHeaders, blockSize)
|
||||
// len(choppedHeaders) is at least 2
|
||||
if err := w.f.WriteHeaders(http2.HeadersFrameParam{StreamID: streamID, EndHeaders: false, BlockFragment: choppedHeaders[0]}); err != nil {
|
||||
return err
|
||||
}
|
||||
for i := 1; i < len(choppedHeaders)-1; i++ {
|
||||
if err := w.f.WriteContinuation(streamID, false, choppedHeaders[i]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
if err := w.f.WriteContinuation(streamID, true, choppedHeaders[len(choppedHeaders)-1]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Partition a slice of bytes into `len(slice) / blockSize` slices of length `blockSize`
|
||||
func chopEncodedHeaders(headers []byte, chunkSize int) [][]byte {
|
||||
var divided [][]byte
|
||||
|
||||
for i := 0; i < len(headers); i += chunkSize {
|
||||
end := i + chunkSize
|
||||
|
||||
if end > len(headers) {
|
||||
end = len(headers)
|
||||
}
|
||||
|
||||
divided = append(divided, headers[i:end])
|
||||
}
|
||||
|
||||
return divided
|
||||
}
|
||||
|
||||
func (w *MuxWriter) writeUseDictionary(dictRequest useDictRequest) error {
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
package h2mux
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestChopEncodedHeaders(t *testing.T) {
|
||||
mockEncodedHeaders := make([]byte, 5)
|
||||
for i := range mockEncodedHeaders {
|
||||
mockEncodedHeaders[i] = byte(i)
|
||||
}
|
||||
chopped := chopEncodedHeaders(mockEncodedHeaders, 4)
|
||||
|
||||
assert.Equal(t, 2, len(chopped))
|
||||
assert.Equal(t, []byte{0, 1, 2, 3}, chopped[0])
|
||||
assert.Equal(t, []byte{4}, chopped[1])
|
||||
}
|
||||
|
||||
func TestChopEncodedEmptyHeaders(t *testing.T) {
|
||||
mockEncodedHeaders := make([]byte, 0)
|
||||
chopped := chopEncodedHeaders(mockEncodedHeaders, 3)
|
||||
|
||||
assert.Equal(t, 0, len(chopped))
|
||||
}
|
|
@ -80,7 +80,7 @@ ghost.init({
|
|||
<link rel="alternate" type="application/rss+xml" title="Cloudflare Blog" href="https://blog.cloudflare.com/rss/" />
|
||||
<meta name="msvalidate.01" content="CF295E1604697F9CAD18B5A232E871F6" />
|
||||
<meta class="swiftype" name="language" data-type="string" content="en" />
|
||||
<script src="https://s3-us-west-1.amazonaws.com/cf-ghost-assets-hotfix/js/index.js"></script>
|
||||
<script src="https://blog.cloudflare.com/assets/js/index.js"></script>
|
||||
<script type="text/javascript" src="//cdn.bizible.com/scripts/bizible.js" async=""></script>
|
||||
<script>
|
||||
var trackRecruitingLink = function(role, url) {
|
||||
|
@ -128,7 +128,7 @@ HTMLAttrToAdd.setAttribute("lang", "en");
|
|||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
<link href="https://s3-us-west-1.amazonaws.com/cf-ghost-assets-hotfix/css/screen.css" rel="stylesheet">
|
||||
<link href="https://blog.cloudflare.com/assets/css/screen.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.8.1/themes/prism.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
|
|
|
@ -113,7 +113,7 @@ ghost.init({
|
|||
<link rel="alternate" type="application/rss+xml" title="Cloudflare Blog" href="https://blog.cloudflare.com/rss/" />
|
||||
<meta name="msvalidate.01" content="CF295E1604697F9CAD18B5A232E871F6" />
|
||||
<meta class="swiftype" name="language" data-type="string" content="en" />
|
||||
<script src="https://s3-us-west-1.amazonaws.com/cf-ghost-assets-hotfix/js/index.js"></script>
|
||||
<script src="https://blog.cloudflare.com/assets/js/index.js"></script>
|
||||
<script type="text/javascript" src="//cdn.bizible.com/scripts/bizible.js" async=""></script>
|
||||
<script>
|
||||
var trackRecruitingLink = function(role, url) {
|
||||
|
@ -161,7 +161,7 @@ HTMLAttrToAdd.setAttribute("lang", "en");
|
|||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
<link href="https://s3-us-west-1.amazonaws.com/cf-ghost-assets-hotfix/css/screen.css" rel="stylesheet">
|
||||
<link href="https://blog.cloudflare.com/assets/css/screen.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.8.1/themes/prism.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
|
|
|
@ -109,7 +109,7 @@ ghost.init({
|
|||
<link rel="alternate" type="application/rss+xml" title="Cloudflare Blog" href="https://blog.cloudflare.com/rss/" />
|
||||
<meta name="msvalidate.01" content="CF295E1604697F9CAD18B5A232E871F6" />
|
||||
<meta class="swiftype" name="language" data-type="string" content="en" />
|
||||
<script src="https://s3-us-west-1.amazonaws.com/cf-ghost-assets-hotfix/js/index.js"></script>
|
||||
<script src="https://blog.cloudflare.com/assets/js/index.js"></script>
|
||||
<script type="text/javascript" src="//cdn.bizible.com/scripts/bizible.js" async=""></script>
|
||||
<script>
|
||||
var trackRecruitingLink = function(role, url) {
|
||||
|
@ -157,7 +157,7 @@ HTMLAttrToAdd.setAttribute("lang", "en");
|
|||
overflow-y: scroll;
|
||||
}
|
||||
</style>
|
||||
<link href="https://s3-us-west-1.amazonaws.com/cf-ghost-assets-hotfix/css/screen.css" rel="stylesheet">
|
||||
<link href="https://blog.cloudflare.com/assets/css/screen.css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.8.1/themes/prism.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
|
|
|
@ -12,8 +12,8 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tlsconfig"
|
||||
)
|
||||
|
@ -56,7 +56,7 @@ const indexTemplate = `
|
|||
<path class="st2" d="M93.6 12.8h-.5c-.1 0-.2.1-.3.2l-.7 2.4c-.3 1-.2 2 .3 2.6.5.6 1.2 1 2.1 1.1l3.7.2c.1 0 .2.1.3.1.1.1.1.2 0 .3-.1.2-.2.3-.4.3l-3.8.2c-2.1.1-4.3 1.8-5.1 3.8l-.2.9c-.1.1 0 .3.2.3h13.2c.2 0 .3-.1.3-.3.2-.8.4-1.7.4-2.6 0-5.2-4.3-9.5-9.5-9.5"/>
|
||||
<path class="st3" d="M104.4 30.8c-.5 0-.9-.4-.9-.9s.4-.9.9-.9.9.4.9.9-.4.9-.9.9m0-1.6c-.4 0-.7.3-.7.7 0 .4.3.7.7.7.4 0 .7-.3.7-.7 0-.4-.3-.7-.7-.7m.4 1.2h-.2l-.2-.3h-.2v.3h-.2v-.9h.5c.2 0 .3.1.3.3 0 .1-.1.2-.2.3l.2.3zm-.3-.5c.1 0 .1 0 .1-.1s-.1-.1-.1-.1h-.3v.3h.3zM14.8 29H17v6h3.8v1.9h-6zM23.1 32.9c0-2.3 1.8-4.1 4.3-4.1s4.2 1.8 4.2 4.1-1.8 4.1-4.3 4.1c-2.4 0-4.2-1.8-4.2-4.1m6.3 0c0-1.2-.8-2.2-2-2.2s-2 1-2 2.1.8 2.1 2 2.1c1.2.2 2-.8 2-2M34.3 33.4V29h2.2v4.4c0 1.1.6 1.7 1.5 1.7s1.5-.5 1.5-1.6V29h2.2v4.4c0 2.6-1.5 3.7-3.7 3.7-2.3-.1-3.7-1.2-3.7-3.7M45 29h3.1c2.8 0 4.5 1.6 4.5 3.9s-1.7 4-4.5 4h-3V29zm3.1 5.9c1.3 0 2.2-.7 2.2-2s-.9-2-2.2-2h-.9v4h.9zM55.7 29H62v1.9h-4.1v1.3h3.7V34h-3.7v2.9h-2.2zM65.1 29h2.2v6h3.8v1.9h-6zM76.8 28.9H79l3.4 8H80l-.6-1.4h-3.1l-.6 1.4h-2.3l3.4-8zm2 4.9l-.9-2.2-.9 2.2h1.8zM85.2 29h3.7c1.2 0 2 .3 2.6.9.5.5.7 1.1.7 1.8 0 1.2-.6 2-1.6 2.4l1.9 2.8H90l-1.6-2.4h-1v2.4h-2.2V29zm3.6 3.8c.7 0 1.2-.4 1.2-.9 0-.6-.5-.9-1.2-.9h-1.4v1.9h1.4zM95.3 29h6.4v1.8h-4.2V32h3.8v1.8h-3.8V35h4.3v1.9h-6.5zM10 33.9c-.3.7-1 1.2-1.8 1.2-1.2 0-2-1-2-2.1s.8-2.1 2-2.1c.9 0 1.6.6 1.9 1.3h2.3c-.4-1.9-2-3.3-4.2-3.3-2.4 0-4.3 1.8-4.3 4.1s1.8 4.1 4.2 4.1c2.1 0 3.7-1.4 4.2-3.2H10z"/>
|
||||
</svg>
|
||||
<h1 class="f4 f2-ns mt5 fw5">Congrats! You created your first tunnel!</h1>
|
||||
<h1 class="f4 f2-ns mt5 fw5">Congrats! You created a tunnel!</h1>
|
||||
<p class="f6 f5-m f4-l measure lh-copy fw3">
|
||||
Argo Tunnel exposes locally running applications to the internet by
|
||||
running an encrypted, virtual tunnel from your laptop or server to
|
||||
|
@ -91,7 +91,7 @@ const indexTemplate = `
|
|||
</html>
|
||||
`
|
||||
|
||||
func StartHelloWorldServer(logger *logrus.Logger, listener net.Listener, shutdownC <-chan struct{}) error {
|
||||
func StartHelloWorldServer(logger logger.Service, listener net.Listener, shutdownC <-chan struct{}) error {
|
||||
logger.Infof("Starting Hello World server at %s", listener.Addr())
|
||||
serverName := defaultServerName
|
||||
if hostname, err := os.Hostname(); err == nil {
|
||||
|
@ -148,7 +148,7 @@ func uptimeHandler(startTime time.Time) http.HandlerFunc {
|
|||
}
|
||||
|
||||
// This handler will echo message
|
||||
func websocketHandler(logger *logrus.Logger, upgrader websocket.Upgrader) http.HandlerFunc {
|
||||
func websocketHandler(logger logger.Service, upgrader websocket.Upgrader) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
|
@ -158,12 +158,12 @@ func websocketHandler(logger *logrus.Logger, upgrader websocket.Upgrader) http.H
|
|||
for {
|
||||
mt, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("websocket read message error")
|
||||
logger.Errorf("websocket read message error: %s", err)
|
||||
break
|
||||
}
|
||||
|
||||
if err := conn.WriteMessage(mt, message); err != nil {
|
||||
logger.WithError(err).Error("websocket write message error")
|
||||
logger.Errorf("websocket write message error: %s", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
85
log/log.go
85
log/log.go
|
@ -1,85 +0,0 @@
|
|||
// this forks the logrus json formatter to rename msg -> message as that's the
|
||||
// expected field. Ideally the logger should make it easier for us.
|
||||
package log
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultTimestampFormat = time.RFC3339Nano
|
||||
)
|
||||
|
||||
type JSONFormatter struct {
|
||||
// TimestampFormat sets the format used for marshaling timestamps.
|
||||
TimestampFormat string
|
||||
}
|
||||
|
||||
func CreateLogger() *logrus.Logger {
|
||||
logger := logrus.New()
|
||||
logger.Out = colorable.NewColorableStderr()
|
||||
logger.Formatter = &logrus.TextFormatter{ForceColors: runtime.GOOS == "windows"}
|
||||
return logger
|
||||
}
|
||||
|
||||
func (f *JSONFormatter) Format(entry *logrus.Entry) ([]byte, error) {
|
||||
data := make(logrus.Fields, len(entry.Data)+3)
|
||||
for k, v := range entry.Data {
|
||||
switch v := v.(type) {
|
||||
case error:
|
||||
// Otherwise errors are ignored by `encoding/json`
|
||||
// https://github.com/sirupsen/logrus/issues/137
|
||||
data[k] = v.Error()
|
||||
default:
|
||||
data[k] = v
|
||||
}
|
||||
}
|
||||
prefixFieldClashes(data)
|
||||
|
||||
timestampFormat := f.TimestampFormat
|
||||
if timestampFormat == "" {
|
||||
timestampFormat = DefaultTimestampFormat
|
||||
}
|
||||
|
||||
data["time"] = entry.Time.Format(timestampFormat)
|
||||
data["message"] = entry.Message
|
||||
data["level"] = entry.Level.String()
|
||||
|
||||
serialized, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err)
|
||||
}
|
||||
return append(serialized, '\n'), nil
|
||||
}
|
||||
|
||||
// This is to not silently overwrite `time`, `msg` and `level` fields when
|
||||
// dumping it. If this code wasn't there doing:
|
||||
//
|
||||
// logrus.WithField("level", 1).Info("hello")
|
||||
//
|
||||
// Would just silently drop the user provided level. Instead with this code
|
||||
// it'll logged as:
|
||||
//
|
||||
// {"level": "info", "fields.level": 1, "msg": "hello", "time": "..."}
|
||||
//
|
||||
// It's not exported because it's still using Data in an opinionated way. It's to
|
||||
// avoid code duplication between the two default formatters.
|
||||
func prefixFieldClashes(data logrus.Fields) {
|
||||
if t, ok := data["time"]; ok {
|
||||
data["fields.time"] = t
|
||||
}
|
||||
|
||||
if m, ok := data["msg"]; ok {
|
||||
data["fields.msg"] = m
|
||||
}
|
||||
|
||||
if l, ok := data["level"]; ok {
|
||||
data["fields.level"] = l
|
||||
}
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alecthomas/units"
|
||||
)
|
||||
|
||||
// Option is to encaspulate actions that will be called by Parse and run later to build an Options struct
|
||||
type Option func(*Options) error
|
||||
|
||||
// Options is use to set logging configuration data
|
||||
type Options struct {
|
||||
logFileDirectory string
|
||||
maxFileSize units.Base2Bytes
|
||||
maxFileCount uint
|
||||
terminalOutputDisabled bool
|
||||
supportedFileLevels []Level
|
||||
supportedTerminalLevels []Level
|
||||
}
|
||||
|
||||
// DisableTerminal stops terminal output for the logger
|
||||
func DisableTerminal(disable bool) Option {
|
||||
return func(c *Options) error {
|
||||
c.terminalOutputDisabled = disable
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// File sets a custom file to log events
|
||||
func File(path string, size units.Base2Bytes, count uint) Option {
|
||||
return func(c *Options) error {
|
||||
c.logFileDirectory = path
|
||||
c.maxFileSize = size
|
||||
c.maxFileCount = count
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultFile configures the log options will the defaults
|
||||
func DefaultFile(directoryPath string) Option {
|
||||
return func(c *Options) error {
|
||||
size, err := units.ParseBase2Bytes("1MB")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.logFileDirectory = directoryPath
|
||||
c.maxFileSize = size
|
||||
c.maxFileCount = 5
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedFileLevels sets the supported logging levels for the log file
|
||||
func SupportedFileLevels(supported []Level) Option {
|
||||
return func(c *Options) error {
|
||||
c.supportedFileLevels = supported
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// SupportedTerminalevels sets the supported logging levels for the terminal output
|
||||
func SupportedTerminalevels(supported []Level) Option {
|
||||
return func(c *Options) error {
|
||||
c.supportedTerminalLevels = supported
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// LogLevelString sets the supported logging levels from a command line flag
|
||||
func LogLevelString(level string) Option {
|
||||
return func(c *Options) error {
|
||||
supported, err := ParseLevelString(level)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.supportedFileLevels = supported
|
||||
c.supportedTerminalLevels = supported
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Parse builds the Options struct so the caller knows what actions should be run
|
||||
func Parse(opts ...Option) (*Options, error) {
|
||||
options := &Options{}
|
||||
for _, opt := range opts {
|
||||
if err := opt(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return options, nil
|
||||
}
|
||||
|
||||
// New setups a new logger based on the options.
|
||||
// The default behavior is to write to standard out
|
||||
func New(opts ...Option) (Service, error) {
|
||||
config, err := Parse(opts...)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
l := NewOutputWriter(SharedWriteManager)
|
||||
if config.logFileDirectory != "" {
|
||||
l.Add(NewFileRollingWriter(config.logFileDirectory,
|
||||
"cloudflared",
|
||||
int64(config.maxFileSize),
|
||||
config.maxFileCount),
|
||||
NewDefaultFormatter(time.RFC3339Nano), config.supportedFileLevels...)
|
||||
}
|
||||
|
||||
if !config.terminalOutputDisabled {
|
||||
if len(config.supportedTerminalLevels) == 0 {
|
||||
l.Add(os.Stdout, NewTerminalFormatter(""), InfoLevel)
|
||||
l.Add(os.Stderr, NewTerminalFormatter(""), 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...)
|
||||
}
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// ParseLevelString returns the expected log levels based on the cmd flag
|
||||
func ParseLevelString(lvl string) ([]Level, error) {
|
||||
switch strings.ToLower(lvl) {
|
||||
case "fatal":
|
||||
return []Level{FatalLevel}, nil
|
||||
case "error":
|
||||
return []Level{FatalLevel, ErrorLevel}, nil
|
||||
case "info", "warn":
|
||||
return []Level{FatalLevel, ErrorLevel, InfoLevel}, nil
|
||||
case "debug":
|
||||
return []Level{FatalLevel, ErrorLevel, InfoLevel, DebugLevel}, nil
|
||||
}
|
||||
return []Level{}, fmt.Errorf("not a valid log level: %q", lvl)
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// FileRollingWriter maintains a set of log files numbered in order
|
||||
// to keep a subset of log data to ensure it doesn't grow pass defined limits
|
||||
type FileRollingWriter struct {
|
||||
baseFileName string
|
||||
directory string
|
||||
maxFileSize int64
|
||||
maxFileCount uint
|
||||
fileHandle *os.File
|
||||
}
|
||||
|
||||
// NewFileRollingWriter creates a new rolling file writer.
|
||||
// directory is the working directory for the files
|
||||
// baseFileName is the log file name. This writer appends .log to the name for the file name
|
||||
// maxFileSize is the size in bytes of how large each file can be. Not a hard limit, general limit based after each write
|
||||
// maxFileCount is the number of rolled files to keep.
|
||||
func NewFileRollingWriter(directory, baseFileName string, maxFileSize int64, maxFileCount uint) *FileRollingWriter {
|
||||
return &FileRollingWriter{
|
||||
directory: directory,
|
||||
baseFileName: baseFileName,
|
||||
maxFileSize: maxFileSize,
|
||||
maxFileCount: maxFileCount,
|
||||
}
|
||||
}
|
||||
|
||||
// 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, 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 {
|
||||
return 0, err
|
||||
}
|
||||
w.fileHandle = h
|
||||
}
|
||||
|
||||
// get size for rolling check
|
||||
info, err := w.fileHandle.Stat()
|
||||
if err != nil {
|
||||
// failed to stat the file. Close the file handle and attempt to open a new handle on the next write
|
||||
w.Close()
|
||||
w.fileHandle = nil
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// write to the file
|
||||
written, err := w.fileHandle.Write(p)
|
||||
|
||||
// check if the file needs to be rolled
|
||||
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)
|
||||
}
|
||||
|
||||
return written, err
|
||||
}
|
||||
|
||||
// Close closes the file handle if it is open
|
||||
func (w *FileRollingWriter) Close() {
|
||||
if w.fileHandle != nil {
|
||||
w.fileHandle.Close()
|
||||
w.fileHandle = nil
|
||||
}
|
||||
}
|
||||
|
||||
// rename is how the files are rolled. It works recursively to move the base log file to the rolled ones
|
||||
// e.g. cloudflared.log -> cloudflared-1.log,
|
||||
// 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, 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 {
|
||||
os.Remove(destinationPath)
|
||||
}
|
||||
|
||||
// if the rolled path already exist, rename it to cloudflared-2.log, then do this one.
|
||||
// recursive call since the oldest one needs to be renamed, before the newer ones can be moved
|
||||
if exists(destinationPath) {
|
||||
w.rename(destinationPath, index+1)
|
||||
}
|
||||
|
||||
os.Rename(sourcePath, destinationPath)
|
||||
}
|
||||
|
||||
// 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 {
|
||||
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isDirectory(path string) bool {
|
||||
if path == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return fileInfo.IsDir()
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFileWrite(t *testing.T) {
|
||||
fileName := "test_file"
|
||||
fileLog := fileName + ".log"
|
||||
testData := []byte(string("hello Dalton, how are you doing?"))
|
||||
defer func() {
|
||||
os.Remove(fileLog)
|
||||
}()
|
||||
|
||||
w := NewFileRollingWriter("", 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(fileLog)
|
||||
assert.FileExists(t, fileLog, "file doesn't exist at expected path")
|
||||
assert.Equal(t, d, testData, "expected data in file to match test data")
|
||||
}
|
||||
|
||||
func TestRolling(t *testing.T) {
|
||||
dirName := "testdir"
|
||||
err := os.Mkdir(dirName, 0755)
|
||||
assert.NoError(t, err)
|
||||
|
||||
fileName := "test_file"
|
||||
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(dirName, fileName, 1000, 2)
|
||||
defer w.Close()
|
||||
|
||||
for i := 99; i >= 1; i-- {
|
||||
testData := []byte(fmt.Sprintf("%d bottles of beer on the wall...", i))
|
||||
w.Write(testData)
|
||||
}
|
||||
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(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")
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/acmacalister/skittles"
|
||||
)
|
||||
|
||||
// Level of logging
|
||||
type Level int
|
||||
|
||||
const (
|
||||
// InfoLevel is for standard log messages
|
||||
InfoLevel Level = iota
|
||||
|
||||
// DebugLevel is for messages that are intended for purposes debugging only
|
||||
DebugLevel
|
||||
|
||||
// ErrorLevel is for error message to indicte something has gone wrong
|
||||
ErrorLevel
|
||||
|
||||
// FatalLevel is for error message that log and kill the program with an os.exit(1)
|
||||
FatalLevel
|
||||
)
|
||||
|
||||
// Formatter is the base interface for formatting logging messages before writing them out
|
||||
type Formatter interface {
|
||||
Timestamp(Level, time.Time) string // format the timestamp string
|
||||
Content(Level, string) string // format content string (color for terminal, etc)
|
||||
}
|
||||
|
||||
// DefaultFormatter writes a simple structure timestamp and the message per log line
|
||||
type DefaultFormatter struct {
|
||||
format string
|
||||
}
|
||||
|
||||
// NewDefaultFormatter creates the standard log formatter
|
||||
// format is the time format to use for timestamp formatting
|
||||
func NewDefaultFormatter(format string) Formatter {
|
||||
return &DefaultFormatter{
|
||||
format: format,
|
||||
}
|
||||
}
|
||||
|
||||
// Timestamp formats a log line timestamp with a brackets around them
|
||||
func (f *DefaultFormatter) Timestamp(l Level, d time.Time) string {
|
||||
if f.format == "" {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("[%s]: ", d.Format(f.format))
|
||||
}
|
||||
|
||||
// Content just writes the log line straight to the sources
|
||||
func (f *DefaultFormatter) Content(l Level, c string) string {
|
||||
return c
|
||||
}
|
||||
|
||||
// TerminalFormatter is setup for colored output
|
||||
type TerminalFormatter struct {
|
||||
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,
|
||||
supportsColor: supportsColor,
|
||||
}
|
||||
}
|
||||
|
||||
// Timestamp returns the log level with a matching color to the log type
|
||||
func (f *TerminalFormatter) Timestamp(l Level, d time.Time) string {
|
||||
t := ""
|
||||
switch l {
|
||||
case InfoLevel:
|
||||
t = f.output("[INFO] ", skittles.Cyan)
|
||||
case ErrorLevel:
|
||||
t = f.output("[ERROR] ", skittles.Red)
|
||||
case DebugLevel:
|
||||
t = f.output("[DEBUG] ", skittles.Yellow)
|
||||
case FatalLevel:
|
||||
t = f.output("[FATAL] ", skittles.Red)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// Content just writes the log line straight to the sources
|
||||
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
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package logger
|
||||
|
||||
import "sync"
|
||||
|
||||
// SharedWriteManager is a package level variable to allows multiple loggers to use the same write manager.
|
||||
// This is useful when multiple loggers will write to the same file to ensure they don't clobber each other.
|
||||
var SharedWriteManager = NewWriteManager()
|
||||
|
||||
type writeData struct {
|
||||
writeFunc func([]byte)
|
||||
data []byte
|
||||
}
|
||||
|
||||
// WriteManager is a logging service that handles managing multiple writing streams
|
||||
type WriteManager struct {
|
||||
shutdown chan struct{}
|
||||
writeChan chan writeData
|
||||
writers map[string]Service
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewWriteManager creates a write manager that implements OutputManager
|
||||
func NewWriteManager() OutputManager {
|
||||
m := &WriteManager{
|
||||
shutdown: make(chan struct{}),
|
||||
writeChan: make(chan writeData, 1000),
|
||||
}
|
||||
|
||||
go m.run()
|
||||
return m
|
||||
}
|
||||
|
||||
// Append adds a message to the writer runloop
|
||||
func (m *WriteManager) Append(data []byte, callback func([]byte)) {
|
||||
m.wg.Add(1)
|
||||
m.writeChan <- writeData{data: data, writeFunc: callback}
|
||||
}
|
||||
|
||||
// Shutdown stops the sync manager service
|
||||
func (m *WriteManager) Shutdown() {
|
||||
m.wg.Wait()
|
||||
close(m.shutdown)
|
||||
close(m.writeChan)
|
||||
}
|
||||
|
||||
// run is the main runloop that schedules log messages
|
||||
func (m *WriteManager) run() {
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-m.writeChan:
|
||||
if ok {
|
||||
event.writeFunc(event.data)
|
||||
m.wg.Done()
|
||||
}
|
||||
case <-m.shutdown:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWriteManger(t *testing.T) {
|
||||
testData := []byte(string("hello Austin, how are you doing?"))
|
||||
waitChan := make(chan []byte)
|
||||
m := NewWriteManager()
|
||||
m.Append(testData, func(b []byte) {
|
||||
waitChan <- b
|
||||
})
|
||||
resp := <-waitChan
|
||||
assert.Equal(t, testData, resp)
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package logger
|
||||
|
||||
// MockWriteManager does nothing and is provided for testing purposes
|
||||
type MockWriteManager struct {
|
||||
}
|
||||
|
||||
// NewMockWriteManager creates an OutputManager that does nothing for testing purposes
|
||||
func NewMockWriteManager() OutputManager {
|
||||
return &MockWriteManager{}
|
||||
}
|
||||
|
||||
// Append is a mock stub
|
||||
func (m *MockWriteManager) Append(data []byte, callback func([]byte)) {
|
||||
}
|
||||
|
||||
// Shutdown is a mock stub
|
||||
func (m *MockWriteManager) Shutdown() {
|
||||
}
|
|
@ -0,0 +1,132 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"time"
|
||||
)
|
||||
|
||||
// provided for testing
|
||||
var osExit = os.Exit
|
||||
|
||||
// OutputManager is used to sync data of Output
|
||||
type OutputManager interface {
|
||||
Append([]byte, func([]byte))
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// Service is the logging service that is either a group or single log writer
|
||||
type Service interface {
|
||||
Error(message string)
|
||||
Info(message string)
|
||||
Debug(message string)
|
||||
Fatal(message string)
|
||||
|
||||
Errorf(format string, args ...interface{})
|
||||
Infof(format string, args ...interface{})
|
||||
Debugf(format string, args ...interface{})
|
||||
Fatalf(format string, args ...interface{})
|
||||
}
|
||||
|
||||
type sourceGroup struct {
|
||||
writer io.Writer
|
||||
formatter Formatter
|
||||
levelsSupported []Level
|
||||
}
|
||||
|
||||
// OutputWriter is the standard logging implementation
|
||||
type OutputWriter struct {
|
||||
groups []sourceGroup
|
||||
syncWriter OutputManager
|
||||
}
|
||||
|
||||
// NewOutputWriter create a new logger
|
||||
func NewOutputWriter(syncWriter OutputManager) *OutputWriter {
|
||||
return &OutputWriter{
|
||||
syncWriter: syncWriter,
|
||||
groups: make([]sourceGroup, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Add a writer and formatter to output to
|
||||
func (s *OutputWriter) Add(writer io.Writer, formatter Formatter, levels ...Level) {
|
||||
s.groups = append(s.groups, sourceGroup{writer: writer, formatter: formatter, levelsSupported: levels})
|
||||
}
|
||||
|
||||
// Error writes an error to the logging sources
|
||||
func (s *OutputWriter) Error(message string) {
|
||||
s.output(ErrorLevel, message)
|
||||
}
|
||||
|
||||
// Info writes an info string to the logging sources
|
||||
func (s *OutputWriter) Info(message string) {
|
||||
s.output(InfoLevel, message)
|
||||
}
|
||||
|
||||
// Debug writes a debug string to the logging sources
|
||||
func (s *OutputWriter) Debug(message string) {
|
||||
s.output(DebugLevel, message)
|
||||
}
|
||||
|
||||
// Fatal writes a error string to the logging sources and runs does an os.exit()
|
||||
func (s *OutputWriter) Fatal(message string) {
|
||||
s.output(FatalLevel, message)
|
||||
s.syncWriter.Shutdown() // waits for the pending logging to finish
|
||||
osExit(1)
|
||||
}
|
||||
|
||||
// Errorf writes a formatted error to the logging sources
|
||||
func (s *OutputWriter) Errorf(format string, args ...interface{}) {
|
||||
s.output(ErrorLevel, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Infof writes a formatted info statement to the logging sources
|
||||
func (s *OutputWriter) Infof(format string, args ...interface{}) {
|
||||
s.output(InfoLevel, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Debugf writes a formatted debug statement to the logging sources
|
||||
func (s *OutputWriter) Debugf(format string, args ...interface{}) {
|
||||
s.output(DebugLevel, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Fatalf writes a writes a formatted error statement and runs does an os.exit()
|
||||
func (s *OutputWriter) Fatalf(format string, args ...interface{}) {
|
||||
s.output(FatalLevel, fmt.Sprintf(format, args...))
|
||||
s.syncWriter.Shutdown() // waits for the pending logging to finish
|
||||
osExit(1)
|
||||
}
|
||||
|
||||
// output does the actual write to the sync manager
|
||||
func (s *OutputWriter) output(l Level, content string) {
|
||||
for _, group := range s.groups {
|
||||
if isSupported(group, l) {
|
||||
logLine := fmt.Sprintf("%s%s\n", group.formatter.Timestamp(l, time.Now()),
|
||||
group.formatter.Content(l, content))
|
||||
s.append(group, []byte(logLine))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *OutputWriter) append(group sourceGroup, logLine []byte) {
|
||||
s.syncWriter.Append(logLine, func(b []byte) {
|
||||
group.writer.Write(b)
|
||||
})
|
||||
}
|
||||
|
||||
// isSupported checks if the log level is supported
|
||||
func isSupported(group sourceGroup, l Level) bool {
|
||||
for _, level := range group.levelsSupported {
|
||||
if l == level {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Write implements io.Writer to support SetOutput of the log package
|
||||
func (s *OutputWriter) Write(p []byte) (n int, err error) {
|
||||
s.Info(string(p))
|
||||
return len(p), nil
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
package logger
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLogLevel(t *testing.T) {
|
||||
timeFormat := "2006-01-02"
|
||||
f := NewDefaultFormatter(timeFormat)
|
||||
m := NewWriteManager()
|
||||
|
||||
var testBuffer bytes.Buffer
|
||||
logger := NewOutputWriter(m)
|
||||
logger.Add(&testBuffer, f, InfoLevel, DebugLevel)
|
||||
|
||||
testTime := f.Timestamp(InfoLevel, time.Now())
|
||||
|
||||
testInfo := "hello Dalton, how are you doing?"
|
||||
logger.Info(testInfo)
|
||||
|
||||
tesErr := "hello Austin, how did it break today?"
|
||||
logger.Error(tesErr)
|
||||
|
||||
testDebug := "hello Bill, who are you?"
|
||||
logger.Debug(testDebug)
|
||||
|
||||
m.Shutdown()
|
||||
|
||||
lines := strings.Split(testBuffer.String(), "\n")
|
||||
assert.Len(t, lines, 3, "only expected two strings in the buffer")
|
||||
|
||||
infoLine := lines[0]
|
||||
debugLine := lines[1]
|
||||
|
||||
compareInfo := fmt.Sprintf("%s%s", testTime, testInfo)
|
||||
assert.Equal(t, compareInfo, infoLine, "expect the strings to match")
|
||||
|
||||
compareDebug := fmt.Sprintf("%s%s", testTime, testDebug)
|
||||
assert.Equal(t, compareDebug, debugLine, "expect the strings to match")
|
||||
}
|
||||
|
||||
func TestOutputWrite(t *testing.T) {
|
||||
timeFormat := "2006-01-02"
|
||||
f := NewDefaultFormatter(timeFormat)
|
||||
m := NewWriteManager()
|
||||
|
||||
var testBuffer bytes.Buffer
|
||||
logger := NewOutputWriter(m)
|
||||
logger.Add(&testBuffer, f, InfoLevel)
|
||||
|
||||
testData := "hello Bob Bork, how are you doing?"
|
||||
logger.Info(testData)
|
||||
testTime := f.Timestamp(InfoLevel, time.Now())
|
||||
|
||||
m.Shutdown()
|
||||
|
||||
scanner := bufio.NewScanner(&testBuffer)
|
||||
scanner.Scan()
|
||||
line := scanner.Text()
|
||||
assert.NoError(t, scanner.Err())
|
||||
|
||||
compareLine := fmt.Sprintf("%s%s", testTime, testData)
|
||||
assert.Equal(t, compareLine, line, "expect the strings to match")
|
||||
}
|
||||
|
||||
func TestFatalWrite(t *testing.T) {
|
||||
timeFormat := "2006-01-02"
|
||||
f := NewDefaultFormatter(timeFormat)
|
||||
m := NewWriteManager()
|
||||
|
||||
var testBuffer bytes.Buffer
|
||||
logger := NewOutputWriter(m)
|
||||
logger.Add(&testBuffer, f, FatalLevel)
|
||||
|
||||
oldOsExit := osExit
|
||||
defer func() { osExit = oldOsExit }()
|
||||
|
||||
var got int
|
||||
myExit := func(code int) {
|
||||
got = code
|
||||
}
|
||||
|
||||
osExit = myExit
|
||||
|
||||
testData := "so long y'all"
|
||||
logger.Fatal(testData)
|
||||
testTime := f.Timestamp(FatalLevel, time.Now())
|
||||
|
||||
scanner := bufio.NewScanner(&testBuffer)
|
||||
scanner.Scan()
|
||||
line := scanner.Text()
|
||||
assert.NoError(t, scanner.Err())
|
||||
|
||||
compareLine := fmt.Sprintf("%s%s", testTime, testData)
|
||||
assert.Equal(t, compareLine, line, "expect the strings to match")
|
||||
assert.Equal(t, got, 1, "exit code should be one for a fatal log")
|
||||
}
|
|
@ -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
|
|
@ -12,9 +12,9 @@ import (
|
|||
|
||||
"golang.org/x/net/trace"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -22,7 +22,7 @@ const (
|
|||
startupTime = time.Millisecond * 500
|
||||
)
|
||||
|
||||
func ServeMetrics(l net.Listener, shutdownC <-chan struct{}, logger *logrus.Logger) (err error) {
|
||||
func ServeMetrics(l net.Listener, shutdownC <-chan struct{}, logger logger.Service) (err error) {
|
||||
var wg sync.WaitGroup
|
||||
// Metrics port is privileged, so no need for further access control
|
||||
trace.AuthRequest = func(*http.Request) (bool, bool) { return true, true }
|
||||
|
@ -43,7 +43,7 @@ func ServeMetrics(l net.Listener, shutdownC <-chan struct{}, logger *logrus.Logg
|
|||
defer wg.Done()
|
||||
err = server.Serve(l)
|
||||
}()
|
||||
logger.WithField("addr", l.Addr()).Info("Starting metrics server")
|
||||
logger.Infof("Starting metrics server on %s", fmt.Sprintf("%v/metrics", l.Addr()))
|
||||
// server.Serve will hang if server.Shutdown is called before the server is
|
||||
// fully started up. So add artificial delay.
|
||||
time.Sleep(startupTime)
|
||||
|
@ -58,7 +58,7 @@ func ServeMetrics(l net.Listener, shutdownC <-chan struct{}, logger *logrus.Logg
|
|||
logger.Info("Metrics server stopped")
|
||||
return nil
|
||||
}
|
||||
logger.WithError(err).Error("Metrics server quit with error")
|
||||
logger.Errorf("Metrics server quit with error: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package origin
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ReconnectSignal struct {
|
||||
// wait this many seconds before re-establish the connection
|
||||
Delay time.Duration
|
||||
}
|
||||
|
||||
// Error allows us to use ReconnectSignal as a special error to force connection abort
|
||||
func (r *ReconnectSignal) Error() string {
|
||||
return "reconnect signal"
|
||||
}
|
||||
|
||||
func (r *ReconnectSignal) DelayBeforeReconnect() {
|
||||
if r.Delay > 0 {
|
||||
time.Sleep(r.Delay)
|
||||
}
|
||||
}
|
|
@ -9,11 +9,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/cloudflare/cloudflared/buffer"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/edgediscovery"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/signal"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
@ -55,13 +56,20 @@ type Supervisor struct {
|
|||
nextConnectedIndex int
|
||||
nextConnectedSignal chan struct{}
|
||||
|
||||
logger *logrus.Entry
|
||||
logger logger.Service
|
||||
|
||||
jwtLock *sync.RWMutex
|
||||
jwtLock sync.RWMutex
|
||||
jwt []byte
|
||||
|
||||
eventDigestLock *sync.RWMutex
|
||||
eventDigestLock sync.RWMutex
|
||||
eventDigest []byte
|
||||
|
||||
connDigestLock sync.RWMutex
|
||||
connDigest map[uint8][]byte
|
||||
|
||||
bufferPool *buffer.Pool
|
||||
|
||||
namedTunnel *NamedTunnelConfig
|
||||
}
|
||||
|
||||
type resolveResult struct {
|
||||
|
@ -74,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
|
||||
|
@ -88,20 +96,21 @@ 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),
|
||||
tunnelsConnecting: map[int]chan struct{}{},
|
||||
logger: config.Logger.WithField("subsystem", "supervisor"),
|
||||
jwtLock: &sync.RWMutex{},
|
||||
eventDigestLock: &sync.RWMutex{},
|
||||
logger: config.Logger,
|
||||
connDigest: make(map[uint8][]byte),
|
||||
bufferPool: buffer.NewPool(512 * 1024),
|
||||
namedTunnel: namedTunnel,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal) error {
|
||||
func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal, reconnectCh chan ReconnectSignal) error {
|
||||
logger := s.config.Logger
|
||||
if err := s.initialize(ctx, connectedSignal); err != nil {
|
||||
if err := s.initialize(ctx, connectedSignal, reconnectCh); err != nil {
|
||||
return err
|
||||
}
|
||||
var tunnelsWaiting []int
|
||||
|
@ -117,7 +126,7 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal) er
|
|||
if timer, err := s.refreshAuth(ctx, refreshAuthBackoff, s.authenticate); err == nil {
|
||||
refreshAuthBackoffTimer = timer
|
||||
} else {
|
||||
logger.WithError(err).Errorf("initial refreshAuth failed, retrying in %v", refreshAuthRetryDuration)
|
||||
logger.Errorf("supervisor: initial refreshAuth failed, retrying in %v: %s", refreshAuthRetryDuration, err)
|
||||
refreshAuthBackoffTimer = time.After(refreshAuthRetryDuration)
|
||||
}
|
||||
}
|
||||
|
@ -136,7 +145,7 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal) er
|
|||
case tunnelError := <-s.tunnelErrors:
|
||||
tunnelsActive--
|
||||
if tunnelError.err != nil {
|
||||
logger.WithError(tunnelError.err).Warn("Tunnel disconnected due to error")
|
||||
logger.Infof("supervisor: Tunnel disconnected due to error: %s", tunnelError.err)
|
||||
tunnelsWaiting = append(tunnelsWaiting, tunnelError.index)
|
||||
s.waitForNextTunnel(tunnelError.index)
|
||||
|
||||
|
@ -151,7 +160,7 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal) er
|
|||
case <-backoffTimer:
|
||||
backoffTimer = nil
|
||||
for _, index := range tunnelsWaiting {
|
||||
go s.startTunnel(ctx, index, s.newConnectedTunnelSignal(index))
|
||||
go s.startTunnel(ctx, index, s.newConnectedTunnelSignal(index), reconnectCh)
|
||||
}
|
||||
tunnelsActive += len(tunnelsWaiting)
|
||||
tunnelsWaiting = nil
|
||||
|
@ -159,7 +168,7 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal) er
|
|||
case <-refreshAuthBackoffTimer:
|
||||
newTimer, err := s.refreshAuth(ctx, refreshAuthBackoff, s.authenticate)
|
||||
if err != nil {
|
||||
logger.WithError(err).Error("Authentication failed")
|
||||
logger.Errorf("supervisor: Authentication failed: %s", err)
|
||||
// Permanent failure. Leave the `select` without setting the
|
||||
// channel to be non-null, so we'll never hit this case of the `select` again.
|
||||
continue
|
||||
|
@ -176,26 +185,26 @@ func (s *Supervisor) Run(ctx context.Context, connectedSignal *signal.Signal) er
|
|||
s.lastResolve = time.Now()
|
||||
s.resolverC = nil
|
||||
if result.err == nil {
|
||||
logger.Debug("Service discovery refresh complete")
|
||||
logger.Debug("supervisor: Service discovery refresh complete")
|
||||
} else {
|
||||
logger.WithError(result.err).Error("Service discovery error")
|
||||
logger.Errorf("supervisor: Service discovery error: %s", result.err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Returns nil if initialization succeeded, else the initialization error.
|
||||
func (s *Supervisor) initialize(ctx context.Context, connectedSignal *signal.Signal) error {
|
||||
func (s *Supervisor) initialize(ctx context.Context, connectedSignal *signal.Signal, reconnectCh chan ReconnectSignal) error {
|
||||
logger := s.logger
|
||||
|
||||
s.lastResolve = time.Now()
|
||||
availableAddrs := int(s.edgeIPs.AvailableAddrs())
|
||||
if s.config.HAConnections > availableAddrs {
|
||||
logger.Warnf("You requested %d HA connections but I can give you at most %d.", s.config.HAConnections, availableAddrs)
|
||||
logger.Infof("You requested %d HA connections but I can give you at most %d.", s.config.HAConnections, availableAddrs)
|
||||
s.config.HAConnections = availableAddrs
|
||||
}
|
||||
|
||||
go s.startFirstTunnel(ctx, connectedSignal)
|
||||
go s.startFirstTunnel(ctx, connectedSignal, reconnectCh)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
<-s.tunnelErrors
|
||||
|
@ -207,7 +216,7 @@ func (s *Supervisor) initialize(ctx context.Context, connectedSignal *signal.Sig
|
|||
// At least one successful connection, so start the rest
|
||||
for i := 1; i < s.config.HAConnections; i++ {
|
||||
ch := signal.New(make(chan struct{}))
|
||||
go s.startTunnel(ctx, i, ch)
|
||||
go s.startTunnel(ctx, i, ch, reconnectCh)
|
||||
time.Sleep(registrationInterval)
|
||||
}
|
||||
return nil
|
||||
|
@ -215,7 +224,7 @@ func (s *Supervisor) initialize(ctx context.Context, connectedSignal *signal.Sig
|
|||
|
||||
// startTunnel starts the first tunnel connection. The resulting error will be sent on
|
||||
// s.tunnelErrors. It will send a signal via connectedSignal if registration succeed
|
||||
func (s *Supervisor) startFirstTunnel(ctx context.Context, connectedSignal *signal.Signal) {
|
||||
func (s *Supervisor) startFirstTunnel(ctx context.Context, connectedSignal *signal.Signal, reconnectCh chan ReconnectSignal) {
|
||||
var (
|
||||
addr *net.TCPAddr
|
||||
err error
|
||||
|
@ -230,7 +239,7 @@ func (s *Supervisor) startFirstTunnel(ctx context.Context, connectedSignal *sign
|
|||
return
|
||||
}
|
||||
|
||||
err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID)
|
||||
err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID, s.bufferPool, reconnectCh)
|
||||
// If the first tunnel disconnects, keep restarting it.
|
||||
edgeErrors := 0
|
||||
for s.unusedIPs() {
|
||||
|
@ -253,13 +262,13 @@ func (s *Supervisor) startFirstTunnel(ctx context.Context, connectedSignal *sign
|
|||
return
|
||||
}
|
||||
}
|
||||
err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID)
|
||||
err = ServeTunnelLoop(ctx, s, s.config, addr, thisConnID, connectedSignal, s.cloudflaredUUID, s.bufferPool, reconnectCh)
|
||||
}
|
||||
}
|
||||
|
||||
// startTunnel starts a new tunnel connection. The resulting error will be sent on
|
||||
// s.tunnelErrors.
|
||||
func (s *Supervisor) startTunnel(ctx context.Context, index int, connectedSignal *signal.Signal) {
|
||||
func (s *Supervisor) startTunnel(ctx context.Context, index int, connectedSignal *signal.Signal, reconnectCh chan ReconnectSignal) {
|
||||
var (
|
||||
addr *net.TCPAddr
|
||||
err error
|
||||
|
@ -268,11 +277,11 @@ func (s *Supervisor) startTunnel(ctx context.Context, index int, connectedSignal
|
|||
s.tunnelErrors <- tunnelError{index: index, addr: addr, err: err}
|
||||
}()
|
||||
|
||||
addr, err = s.edgeIPs.GetAddr(index)
|
||||
addr, err = s.edgeIPs.GetDifferentAddr(index)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
err = ServeTunnelLoop(ctx, s, s.config, addr, uint8(index), connectedSignal, s.cloudflaredUUID)
|
||||
err = ServeTunnelLoop(ctx, s, s.config, addr, uint8(index), connectedSignal, s.cloudflaredUUID, s.bufferPool, reconnectCh)
|
||||
}
|
||||
|
||||
func (s *Supervisor) newConnectedTunnelSignal(index int) *signal.Signal {
|
||||
|
@ -328,17 +337,33 @@ func (s *Supervisor) SetEventDigest(eventDigest []byte) {
|
|||
s.eventDigest = eventDigest
|
||||
}
|
||||
|
||||
func (s *Supervisor) ConnDigest(connID uint8) ([]byte, error) {
|
||||
s.connDigestLock.RLock()
|
||||
defer s.connDigestLock.RUnlock()
|
||||
digest, ok := s.connDigest[connID]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no connection digest for connection %v", connID)
|
||||
}
|
||||
return digest, nil
|
||||
}
|
||||
|
||||
func (s *Supervisor) SetConnDigest(connID uint8, connDigest []byte) {
|
||||
s.connDigestLock.Lock()
|
||||
defer s.connDigestLock.Unlock()
|
||||
s.connDigest[connID] = connDigest
|
||||
}
|
||||
|
||||
func (s *Supervisor) refreshAuth(
|
||||
ctx context.Context,
|
||||
backoff *BackoffHandler,
|
||||
authenticate func(ctx context.Context, numPreviousAttempts int) (tunnelpogs.AuthOutcome, error),
|
||||
) (retryTimer <-chan time.Time, err error) {
|
||||
logger := s.config.Logger.WithField("subsystem", subsystemRefreshAuth)
|
||||
logger := s.config.Logger
|
||||
authOutcome, err := authenticate(ctx, backoff.Retries())
|
||||
if err != nil {
|
||||
s.config.Metrics.authFail.WithLabelValues(err.Error()).Inc()
|
||||
if duration, ok := backoff.GetBackoffDuration(ctx); ok {
|
||||
logger.WithError(err).Warnf("Retrying in %v", duration)
|
||||
logger.Debugf("refresh_auth: Retrying in %v: %s", duration, err)
|
||||
return backoff.BackoffTimer(), nil
|
||||
}
|
||||
return nil, err
|
||||
|
@ -354,13 +379,13 @@ func (s *Supervisor) refreshAuth(
|
|||
case tunnelpogs.AuthUnknown:
|
||||
duration := outcome.RefreshAfter()
|
||||
s.config.Metrics.authFail.WithLabelValues(outcome.Error()).Inc()
|
||||
logger.WithError(outcome).Warnf("Retrying in %v", duration)
|
||||
logger.Debugf("refresh_auth: Retrying in %v: %s", duration, outcome)
|
||||
return timeAfter(duration), nil
|
||||
case tunnelpogs.AuthFail:
|
||||
s.config.Metrics.authFail.WithLabelValues(outcome.Error()).Inc()
|
||||
return nil, outcome
|
||||
default:
|
||||
err := fmt.Errorf("Unexpected outcome type %T", authOutcome)
|
||||
err := fmt.Errorf("refresh_auth: Unexpected outcome type %T", authOutcome)
|
||||
s.config.Metrics.authFail.WithLabelValues(err.Error()).Inc()
|
||||
return nil, err
|
||||
}
|
||||
|
@ -383,7 +408,6 @@ func (s *Supervisor) authenticate(ctx context.Context, numPreviousAttempts int)
|
|||
return nil // noop
|
||||
})
|
||||
muxerConfig := s.config.muxerConfig(handler)
|
||||
muxerConfig.Logger = muxerConfig.Logger.WithField("subsystem", subsystemRefreshAuth)
|
||||
muxer, err := h2mux.Handshake(edgeConn, edgeConn, muxerConfig, s.config.Metrics.activeStreams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -395,7 +419,7 @@ func (s *Supervisor) authenticate(ctx context.Context, numPreviousAttempts int)
|
|||
<-muxer.Shutdown()
|
||||
}()
|
||||
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, s.logger.WithField("subsystem", subsystemRefreshAuth), openStreamTimeout)
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, s.logger, openStreamTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -9,13 +9,13 @@ import (
|
|||
|
||||
"github.com/google/uuid"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs"
|
||||
)
|
||||
|
||||
func testConfig(logger *logrus.Logger) *TunnelConfig {
|
||||
func testConfig(logger logger.Service) *TunnelConfig {
|
||||
metrics := TunnelMetrics{}
|
||||
|
||||
metrics.authSuccess = prometheus.NewCounter(
|
||||
|
@ -40,8 +40,7 @@ func testConfig(logger *logrus.Logger) *TunnelConfig {
|
|||
}
|
||||
|
||||
func TestRefreshAuthBackoff(t *testing.T) {
|
||||
logger := logrus.New()
|
||||
logger.Level = logrus.ErrorLevel
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
var wait time.Duration
|
||||
timeAfter = func(d time.Duration) <-chan time.Time {
|
||||
|
@ -49,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()
|
||||
}
|
||||
|
@ -85,8 +84,7 @@ func TestRefreshAuthBackoff(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRefreshAuthSuccess(t *testing.T) {
|
||||
logger := logrus.New()
|
||||
logger.Level = logrus.ErrorLevel
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
var wait time.Duration
|
||||
timeAfter = func(d time.Duration) <-chan time.Time {
|
||||
|
@ -94,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()
|
||||
}
|
||||
|
@ -114,8 +112,7 @@ func TestRefreshAuthSuccess(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRefreshAuthUnknown(t *testing.T) {
|
||||
logger := logrus.New()
|
||||
logger.Level = logrus.ErrorLevel
|
||||
logger := logger.NewOutputWriter(logger.NewMockWriteManager())
|
||||
|
||||
var wait time.Duration
|
||||
timeAfter = func(d time.Duration) <-chan time.Time {
|
||||
|
@ -123,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()
|
||||
}
|
||||
|
@ -143,10 +140,9 @@ func TestRefreshAuthUnknown(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRefreshAuthFail(t *testing.T) {
|
||||
logger := logrus.New()
|
||||
logger.Level = logrus.ErrorLevel
|
||||
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()
|
||||
}
|
||||
|
|
217
origin/tunnel.go
217
origin/tunnel.go
|
@ -17,15 +17,16 @@ import (
|
|||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/sync/errgroup"
|
||||
|
||||
"github.com/cloudflare/cloudflared/buffer"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/buildinfo"
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/signal"
|
||||
"github.com/cloudflare/cloudflared/streamhandler"
|
||||
"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"
|
||||
|
@ -38,6 +39,8 @@ const (
|
|||
lbProbeUserAgentPrefix = "Mozilla/5.0 (compatible; Cloudflare-Traffic-Manager/1.0; +https://www.cloudflare.com/traffic-manager/;"
|
||||
TagHeaderNamePrefix = "Cf-Warp-Tag-"
|
||||
DuplicateConnectionError = "EDUPCONN"
|
||||
FeatureSerializedHeaders = "serialized_headers"
|
||||
FeatureQuickReconnects = "quick_reconnects"
|
||||
)
|
||||
|
||||
type registerRPCName string
|
||||
|
@ -65,7 +68,8 @@ type TunnelConfig struct {
|
|||
IsAutoupdated bool
|
||||
IsFreeTunnel bool
|
||||
LBPool string
|
||||
Logger *log.Logger
|
||||
Logger logger.Service
|
||||
TransportLogger logger.Service
|
||||
MaxHeartbeats uint64
|
||||
Metrics *TunnelMetrics
|
||||
MetricsUpdateFreq time.Duration
|
||||
|
@ -76,7 +80,6 @@ type TunnelConfig struct {
|
|||
RunFromTerminal bool
|
||||
Tags []tunnelpogs.Tag
|
||||
TlsConfig *tls.Config
|
||||
TransportLogger *log.Logger
|
||||
UseDeclarativeTunnel bool
|
||||
WSGI bool
|
||||
// OriginUrl may not be used if a user specifies a unix socket.
|
||||
|
@ -84,6 +87,8 @@ type TunnelConfig struct {
|
|||
|
||||
// feature-flag to use new edge reconnect tokens
|
||||
UseReconnectToken bool
|
||||
// feature-flag for using ConnectionDigest
|
||||
UseQuickReconnects bool
|
||||
}
|
||||
|
||||
// ReconnectTunnelCredentialManager is invoked by functions in this file to
|
||||
|
@ -92,6 +97,8 @@ type ReconnectTunnelCredentialManager interface {
|
|||
ReconnectToken() ([]byte, error)
|
||||
EventDigest() ([]byte, error)
|
||||
SetEventDigest(eventDigest []byte)
|
||||
ConnDigest(connID uint8) ([]byte, error)
|
||||
SetConnDigest(connID uint8, connDigest []byte)
|
||||
}
|
||||
|
||||
type dupConnRegisterTunnelError struct{}
|
||||
|
@ -137,7 +144,7 @@ func (c *TunnelConfig) muxerConfig(handler h2mux.MuxedStreamHandler) h2mux.Muxer
|
|||
IsClient: true,
|
||||
HeartbeatInterval: c.HeartbeatInterval,
|
||||
MaxHeartbeats: c.MaxHeartbeats,
|
||||
Logger: c.TransportLogger.WithFields(log.Fields{}),
|
||||
Logger: c.TransportLogger,
|
||||
CompressionQuality: h2mux.CompressionSetting(c.CompressionQuality),
|
||||
}
|
||||
}
|
||||
|
@ -160,15 +167,29 @@ func (c *TunnelConfig) RegistrationOptions(connectionID uint8, OriginLocalIP str
|
|||
RunFromTerminal: c.RunFromTerminal,
|
||||
CompressionQuality: c.CompressionQuality,
|
||||
UUID: uuid.String(),
|
||||
Features: c.SupportedFeatures(),
|
||||
}
|
||||
}
|
||||
|
||||
func StartTunnelDaemon(ctx context.Context, config *TunnelConfig, connectedSignal *signal.Signal, cloudflaredID uuid.UUID) error {
|
||||
s, err := NewSupervisor(config, cloudflaredID)
|
||||
func (c *TunnelConfig) SupportedFeatures() []string {
|
||||
basic := []string{FeatureSerializedHeaders}
|
||||
if c.UseQuickReconnects {
|
||||
basic = append(basic, FeatureQuickReconnects)
|
||||
}
|
||||
return basic
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return s.Run(ctx, connectedSignal)
|
||||
return s.Run(ctx, connectedSignal, reconnectCh)
|
||||
}
|
||||
|
||||
func ServeTunnelLoop(ctx context.Context,
|
||||
|
@ -177,9 +198,10 @@ 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 {
|
||||
connectionLogger := config.Logger.WithField("connectionID", connectionID)
|
||||
config.Metrics.incrementHaConnections()
|
||||
defer config.Metrics.decrementHaConnections()
|
||||
backoff := BackoffHandler{MaxRetries: config.Retries}
|
||||
|
@ -196,15 +218,17 @@ func ServeTunnelLoop(ctx context.Context,
|
|||
ctx,
|
||||
credentialManager,
|
||||
config,
|
||||
connectionLogger,
|
||||
config.Logger,
|
||||
addr, connectionID,
|
||||
connectedFuse,
|
||||
&backoff,
|
||||
u,
|
||||
cloudflaredUUID,
|
||||
bufferPool,
|
||||
reconnectCh,
|
||||
)
|
||||
if recoverable {
|
||||
if duration, ok := backoff.GetBackoffDuration(ctx); ok {
|
||||
connectionLogger.Infof("Retrying in %s seconds", duration)
|
||||
config.Logger.Infof("Retrying in %s seconds: connectionID: %d", duration, connectionID)
|
||||
backoff.Backoff(ctx)
|
||||
continue
|
||||
}
|
||||
|
@ -217,12 +241,14 @@ func ServeTunnel(
|
|||
ctx context.Context,
|
||||
credentialManager ReconnectTunnelCredentialManager,
|
||||
config *TunnelConfig,
|
||||
logger *log.Entry,
|
||||
logger logger.Service,
|
||||
addr *net.TCPAddr,
|
||||
connectionID uint8,
|
||||
connectedFuse *h2mux.BooleanFuse,
|
||||
backoff *BackoffHandler,
|
||||
u uuid.UUID,
|
||||
cloudflaredUUID uuid.UUID,
|
||||
bufferPool *buffer.Pool,
|
||||
reconnectCh chan ReconnectSignal,
|
||||
) (err error, recoverable bool) {
|
||||
// Treat panics as recoverable errors
|
||||
defer func() {
|
||||
|
@ -243,16 +269,15 @@ func ServeTunnel(
|
|||
tags["ha"] = connectionTag
|
||||
|
||||
// Returns error from parsing the origin URL or handshake errors
|
||||
handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr, connectionID)
|
||||
handler, originLocalIP, err := NewTunnelHandler(ctx, config, addr, connectionID, bufferPool)
|
||||
if err != nil {
|
||||
errLog := logger.WithError(err)
|
||||
switch err.(type) {
|
||||
case connection.DialError:
|
||||
errLog.Error("Unable to dial edge")
|
||||
logger.Errorf("Unable to dial edge: %s connectionID: %d", err, connectionID)
|
||||
case h2mux.MuxerHandshakeError:
|
||||
errLog.Error("Handshake failed with edge server")
|
||||
logger.Errorf("Handshake failed with edge server: %s connectionID: %d", err, connectionID)
|
||||
default:
|
||||
errLog.Error("Tunnel creation failure")
|
||||
logger.Errorf("Tunnel creation failure: %s connectionID: %d", err, connectionID)
|
||||
return err, false
|
||||
}
|
||||
return err, true
|
||||
|
@ -273,17 +298,25 @@ func ServeTunnel(
|
|||
eventDigest, eventDigestErr := credentialManager.EventDigest()
|
||||
// if we have both credentials, we can reconnect
|
||||
if tokenErr == nil && eventDigestErr == nil {
|
||||
return ReconnectTunnel(serveCtx, token, eventDigest, handler.muxer, config, logger, connectionID, originLocalIP, u)
|
||||
var connDigest []byte
|
||||
|
||||
// check if we can use Quick Reconnects
|
||||
if config.UseQuickReconnects {
|
||||
if digest, connDigestErr := credentialManager.ConnDigest(connectionID); connDigestErr == nil {
|
||||
connDigest = digest
|
||||
}
|
||||
}
|
||||
return ReconnectTunnel(serveCtx, token, eventDigest, connDigest, handler.muxer, config, logger, connectionID, originLocalIP, cloudflaredUUID, credentialManager)
|
||||
}
|
||||
// log errors and proceed to RegisterTunnel
|
||||
if tokenErr != nil {
|
||||
logger.WithError(tokenErr).Error("Couldn't get reconnect token")
|
||||
logger.Errorf("Couldn't get reconnect token: %s", tokenErr)
|
||||
}
|
||||
if eventDigestErr != nil {
|
||||
logger.WithError(eventDigestErr).Error("Couldn't get event digest")
|
||||
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 {
|
||||
|
@ -292,7 +325,10 @@ func ServeTunnel(
|
|||
select {
|
||||
case <-serveCtx.Done():
|
||||
// UnregisterTunnel blocks until the RPC call returns
|
||||
err := UnregisterTunnel(handler.muxer, config.GracePeriod, config.TransportLogger)
|
||||
var err error
|
||||
if connectedFuse.Value() {
|
||||
err = UnregisterTunnel(handler.muxer, config.GracePeriod, config.TransportLogger)
|
||||
}
|
||||
handler.muxer.Shutdown()
|
||||
return err
|
||||
case <-updateMetricsTickC:
|
||||
|
@ -301,6 +337,17 @@ func ServeTunnel(
|
|||
}
|
||||
})
|
||||
|
||||
errGroup.Go(func() error {
|
||||
for {
|
||||
select {
|
||||
case reconnect := <-reconnectCh:
|
||||
return &reconnect
|
||||
case <-serveCtx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
errGroup.Go(func() error {
|
||||
// All routines should stop when muxer finish serving. When muxer is shutdown
|
||||
// gracefully, it doesn't return an error, so we need to return errMuxerShutdown
|
||||
|
@ -321,7 +368,7 @@ func ServeTunnel(
|
|||
logger.Info("Already connected to this server, selecting a different one")
|
||||
return err, true
|
||||
case serverRegisterTunnelError:
|
||||
logger.WithError(castedErr.cause).Error("Register tunnel error from server side")
|
||||
logger.Errorf("Register tunnel error from server side: %s", castedErr.cause)
|
||||
// Don't send registration error return from server to Sentry. They are
|
||||
// logged on server side
|
||||
if incidents := config.IncidentLookup.ActiveIncidents(); len(incidents) > 0 {
|
||||
|
@ -329,13 +376,17 @@ func ServeTunnel(
|
|||
}
|
||||
return castedErr.cause, !castedErr.permanent
|
||||
case clientRegisterTunnelError:
|
||||
logger.WithError(castedErr.cause).Error("Register tunnel error on client side")
|
||||
logger.Errorf("Register tunnel error on client side: %s", castedErr.cause)
|
||||
return err, true
|
||||
case muxerShutdownError:
|
||||
logger.Infof("Muxer shutdown")
|
||||
logger.Info("Muxer shutdown")
|
||||
return err, true
|
||||
case *ReconnectSignal:
|
||||
logger.Infof("Restarting due to reconnect signal in %d seconds", castedErr.Delay)
|
||||
castedErr.DelayBeforeReconnect()
|
||||
return err, true
|
||||
default:
|
||||
logger.WithError(err).Error("Serve tunnel error")
|
||||
logger.Errorf("Serve tunnel error: %s", err)
|
||||
return err, true
|
||||
}
|
||||
}
|
||||
|
@ -347,13 +398,13 @@ func RegisterTunnel(
|
|||
credentialManager ReconnectTunnelCredentialManager,
|
||||
muxer *h2mux.Muxer,
|
||||
config *TunnelConfig,
|
||||
logger *log.Entry,
|
||||
logger logger.Service,
|
||||
connectionID uint8,
|
||||
originLocalIP string,
|
||||
uuid uuid.UUID,
|
||||
) error {
|
||||
config.TransportLogger.Debug("initiating RPC stream to register")
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, config.TransportLogger.WithField("subsystem", "rpc-register"), openStreamTimeout)
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, config.TransportLogger, openStreamTimeout)
|
||||
if err != nil {
|
||||
// RPC stream open error
|
||||
return newClientRegisterTunnelError(err, config.Metrics.rpcFail, register)
|
||||
|
@ -375,22 +426,23 @@ func RegisterTunnel(
|
|||
return processRegisterTunnelError(registrationErr, config.Metrics, register)
|
||||
}
|
||||
credentialManager.SetEventDigest(registration.EventDigest)
|
||||
return processRegistrationSuccess(config, logger, connectionID, registration, register)
|
||||
return processRegistrationSuccess(config, logger, connectionID, registration, register, credentialManager)
|
||||
}
|
||||
|
||||
func ReconnectTunnel(
|
||||
ctx context.Context,
|
||||
token []byte,
|
||||
eventDigest []byte,
|
||||
eventDigest, connDigest []byte,
|
||||
muxer *h2mux.Muxer,
|
||||
config *TunnelConfig,
|
||||
logger *log.Entry,
|
||||
logger logger.Service,
|
||||
connectionID uint8,
|
||||
originLocalIP string,
|
||||
uuid uuid.UUID,
|
||||
credentialManager ReconnectTunnelCredentialManager,
|
||||
) error {
|
||||
config.TransportLogger.Debug("initiating RPC stream to reconnect")
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, config.TransportLogger.WithField("subsystem", "rpc-reconnect"), openStreamTimeout)
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, config.TransportLogger, openStreamTimeout)
|
||||
if err != nil {
|
||||
// RPC stream open error
|
||||
return newClientRegisterTunnelError(err, config.Metrics.rpcFail, reconnect)
|
||||
|
@ -405,6 +457,7 @@ func ReconnectTunnel(
|
|||
ctx,
|
||||
token,
|
||||
eventDigest,
|
||||
connDigest,
|
||||
config.Hostname,
|
||||
config.RegistrationOptions(connectionID, originLocalIP, uuid),
|
||||
)
|
||||
|
@ -412,10 +465,17 @@ func ReconnectTunnel(
|
|||
// ReconnectTunnel RPC failure
|
||||
return processRegisterTunnelError(registrationErr, config.Metrics, reconnect)
|
||||
}
|
||||
return processRegistrationSuccess(config, logger, connectionID, registration, reconnect)
|
||||
return processRegistrationSuccess(config, logger, connectionID, registration, reconnect, credentialManager)
|
||||
}
|
||||
|
||||
func processRegistrationSuccess(config *TunnelConfig, logger *log.Entry, connectionID uint8, registration *tunnelpogs.TunnelRegistration, name registerRPCName) error {
|
||||
func processRegistrationSuccess(
|
||||
config *TunnelConfig,
|
||||
logger logger.Service,
|
||||
connectionID uint8,
|
||||
registration *tunnelpogs.TunnelRegistration,
|
||||
name registerRPCName,
|
||||
credentialManager ReconnectTunnelCredentialManager,
|
||||
) error {
|
||||
for _, logLine := range registration.LogLines {
|
||||
logger.Info(logLine)
|
||||
}
|
||||
|
@ -429,14 +489,15 @@ func processRegistrationSuccess(config *TunnelConfig, logger *log.Entry, connect
|
|||
if isTrialTunnel := config.Hostname == ""; isTrialTunnel {
|
||||
if url, err := url.Parse(registration.Url); err == nil {
|
||||
for _, line := range asciiBox(trialZoneMsg(url.String()), 2) {
|
||||
logger.Infoln(line)
|
||||
logger.Info(line)
|
||||
}
|
||||
} else {
|
||||
logger.Errorln("Failed to connect tunnel, please try again.")
|
||||
logger.Error("Failed to connect tunnel, please try again.")
|
||||
return fmt.Errorf("empty URL in response from Cloudflare edge")
|
||||
}
|
||||
}
|
||||
|
||||
credentialManager.SetConnDigest(connectionID, registration.ConnDigest)
|
||||
config.Metrics.userHostnamesCounts.WithLabelValues(registration.Url).Inc()
|
||||
|
||||
logger.Infof("Route propagating, it may take up to 1 minute for your new route to become functional")
|
||||
|
@ -456,14 +517,16 @@ func processRegisterTunnelError(err tunnelpogs.TunnelRegistrationError, metrics
|
|||
}
|
||||
}
|
||||
|
||||
func UnregisterTunnel(muxer *h2mux.Muxer, gracePeriod time.Duration, logger *log.Logger) error {
|
||||
func UnregisterTunnel(muxer *h2mux.Muxer, gracePeriod time.Duration, logger logger.Service) error {
|
||||
logger.Debug("initiating RPC stream to unregister")
|
||||
ctx := context.Background()
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, logger.WithField("subsystem", "rpc-unregister"), openStreamTimeout)
|
||||
tunnelServer, err := connection.NewRPCClient(ctx, muxer, logger, openStreamTimeout)
|
||||
if err != nil {
|
||||
// RPC stream open error
|
||||
return err
|
||||
}
|
||||
defer tunnelServer.Close()
|
||||
|
||||
// gracePeriod is encoded in int64 using capnproto
|
||||
return tunnelServer.UnregisterTunnel(ctx, gracePeriod.Nanoseconds())
|
||||
}
|
||||
|
@ -472,16 +535,16 @@ func LogServerInfo(
|
|||
promise tunnelrpc.ServerInfo_Promise,
|
||||
connectionID uint8,
|
||||
metrics *TunnelMetrics,
|
||||
logger *log.Entry,
|
||||
logger logger.Service,
|
||||
) {
|
||||
serverInfoMessage, err := promise.Struct()
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("Failed to retrieve server information")
|
||||
logger.Errorf("Failed to retrieve server information: %s", err)
|
||||
return
|
||||
}
|
||||
serverInfo, err := tunnelpogs.UnmarshalServerInfo(serverInfoMessage)
|
||||
if err != nil {
|
||||
logger.WithError(err).Warn("Failed to retrieve server information")
|
||||
logger.Errorf("Failed to retrieve server information: %s", err)
|
||||
return
|
||||
}
|
||||
logger.Infof("Connected to %s", serverInfo.LocationName)
|
||||
|
@ -498,8 +561,10 @@ type TunnelHandler struct {
|
|||
metrics *TunnelMetrics
|
||||
// connectionID is only used by metrics, and prometheus requires labels to be string
|
||||
connectionID string
|
||||
logger *log.Logger
|
||||
logger logger.Service
|
||||
noChunkedEncoding bool
|
||||
|
||||
bufferPool *buffer.Pool
|
||||
}
|
||||
|
||||
// NewTunnelHandler returns a TunnelHandler, origin LAN IP and error
|
||||
|
@ -507,6 +572,7 @@ func NewTunnelHandler(ctx context.Context,
|
|||
config *TunnelConfig,
|
||||
addr *net.TCPAddr,
|
||||
connectionID uint8,
|
||||
bufferPool *buffer.Pool,
|
||||
) (*TunnelHandler, string, error) {
|
||||
originURL, err := validation.ValidateUrl(config.OriginUrl)
|
||||
if err != nil {
|
||||
|
@ -522,6 +588,7 @@ func NewTunnelHandler(ctx context.Context,
|
|||
connectionID: uint8ToString(connectionID),
|
||||
logger: config.Logger,
|
||||
noChunkedEncoding: config.NoChunkedEncoding,
|
||||
bufferPool: bufferPool,
|
||||
}
|
||||
if h.httpClient == nil {
|
||||
h.httpClient = http.DefaultTransport
|
||||
|
@ -552,12 +619,12 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
|
|||
|
||||
req, reqErr := h.createRequest(stream)
|
||||
if reqErr != nil {
|
||||
h.logError(stream, reqErr)
|
||||
h.writeErrorResponse(stream, reqErr)
|
||||
return reqErr
|
||||
}
|
||||
|
||||
cfRay := streamhandler.FindCfRayHeader(req)
|
||||
lbProbe := streamhandler.IsLBProbeRequest(req)
|
||||
cfRay := findCfRayHeader(req)
|
||||
lbProbe := isLBProbeRequest(req)
|
||||
h.logRequest(req, cfRay, lbProbe)
|
||||
|
||||
var resp *http.Response
|
||||
|
@ -568,7 +635,7 @@ func (h *TunnelHandler) ServeStream(stream *h2mux.MuxedStream) error {
|
|||
resp, respErr = h.serveHTTP(stream, req)
|
||||
}
|
||||
if respErr != nil {
|
||||
h.logError(stream, respErr)
|
||||
h.writeErrorResponse(stream, respErr)
|
||||
return respErr
|
||||
}
|
||||
h.logResponseOk(resp, cfRay, lbProbe)
|
||||
|
@ -606,6 +673,7 @@ func (h *TunnelHandler) serveWebsocket(stream *h2mux.MuxedStream, req *http.Requ
|
|||
// Copy to/from stream to the undelying connection. Use the underlying
|
||||
// connection because cloudflared doesn't operate on the message themselves
|
||||
websocket.Stream(conn.UnderlyingConn(), stream)
|
||||
|
||||
return response, nil
|
||||
}
|
||||
|
||||
|
@ -633,7 +701,9 @@ func (h *TunnelHandler) serveHTTP(stream *h2mux.MuxedStream, req *http.Request)
|
|||
}
|
||||
defer response.Body.Close()
|
||||
|
||||
err = stream.WriteHeaders(h2mux.H1ResponseToH2ResponseHeaders(response))
|
||||
headers := h2mux.H1ResponseToH2ResponseHeaders(response)
|
||||
headers = append(headers, h2mux.CreateResponseMetaHeader(h2mux.ResponseMetaHeaderField, h2mux.ResponseSourceOrigin))
|
||||
err = stream.WriteHeaders(headers)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Error writing response header")
|
||||
}
|
||||
|
@ -642,7 +712,9 @@ func (h *TunnelHandler) serveHTTP(stream *h2mux.MuxedStream, req *http.Request)
|
|||
} else {
|
||||
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
|
||||
// compression generates dictionary on first write
|
||||
io.CopyBuffer(stream, response.Body, make([]byte, 512*1024))
|
||||
buf := h.bufferPool.Get()
|
||||
defer h.bufferPool.Put(buf)
|
||||
io.CopyBuffer(stream, response.Body, buf)
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
@ -666,49 +738,50 @@ func (h *TunnelHandler) isEventStream(response *http.Response) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func (h *TunnelHandler) logError(stream *h2mux.MuxedStream, err error) {
|
||||
h.logger.WithError(err).Error("HTTP request error")
|
||||
stream.WriteHeaders([]h2mux.Header{{Name: ":status", Value: "502"}})
|
||||
func (h *TunnelHandler) writeErrorResponse(stream *h2mux.MuxedStream, err error) {
|
||||
h.logger.Errorf("HTTP request error: %s", err)
|
||||
stream.WriteHeaders([]h2mux.Header{
|
||||
{Name: ":status", Value: "502"},
|
||||
h2mux.CreateResponseMetaHeader(h2mux.ResponseMetaHeaderField, h2mux.ResponseSourceCloudflared),
|
||||
})
|
||||
stream.Write([]byte("502 Bad Gateway"))
|
||||
h.metrics.incrementResponses(h.connectionID, "502")
|
||||
}
|
||||
|
||||
func (h *TunnelHandler) logRequest(req *http.Request, cfRay string, lbProbe bool) {
|
||||
logger := log.NewEntry(h.logger)
|
||||
logger := h.logger
|
||||
if cfRay != "" {
|
||||
logger = logger.WithField("CF-RAY", cfRay)
|
||||
logger.Debugf("%s %s %s", req.Method, req.URL, req.Proto)
|
||||
logger.Debugf("CF-RAY: %s %s %s %s", cfRay, req.Method, req.URL, req.Proto)
|
||||
} else if lbProbe {
|
||||
logger.Debugf("Load Balancer health check %s %s %s", req.Method, req.URL, req.Proto)
|
||||
logger.Debugf("CF-RAY: %s Load Balancer health check %s %s %s", cfRay, req.Method, req.URL, req.Proto)
|
||||
} else {
|
||||
logger.Warnf("All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", req.Method, req.URL, req.Proto)
|
||||
logger.Infof("CF-RAY: %s All requests should have a CF-RAY header. Please open a support ticket with Cloudflare. %s %s %s ", cfRay, req.Method, req.URL, req.Proto)
|
||||
}
|
||||
logger.Debugf("Request Headers %+v", req.Header)
|
||||
logger.Debugf("CF-RAY: %s Request Headers %+v", cfRay, req.Header)
|
||||
|
||||
if contentLen := req.ContentLength; contentLen == -1 {
|
||||
logger.Debugf("Request Content length unknown")
|
||||
logger.Debugf("CF-RAY: %s Request Content length unknown", cfRay)
|
||||
} else {
|
||||
logger.Debugf("Request content length %d", contentLen)
|
||||
logger.Debugf("CF-RAY: %s Request content length %d", cfRay, contentLen)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TunnelHandler) logResponseOk(r *http.Response, cfRay string, lbProbe bool) {
|
||||
h.metrics.incrementResponses(h.connectionID, "200")
|
||||
logger := log.NewEntry(h.logger)
|
||||
logger := h.logger
|
||||
if cfRay != "" {
|
||||
logger = logger.WithField("CF-RAY", cfRay)
|
||||
logger.Debugf("%s", r.Status)
|
||||
logger.Debugf("CF-RAY: %s %s", cfRay, r.Status)
|
||||
} else if lbProbe {
|
||||
logger.Debugf("Response to Load Balancer health check %s", r.Status)
|
||||
} else {
|
||||
logger.Infof("%s", r.Status)
|
||||
}
|
||||
logger.Debugf("Response Headers %+v", r.Header)
|
||||
logger.Debugf("CF-RAY: %s Response Headers %+v", cfRay, r.Header)
|
||||
|
||||
if contentLen := r.ContentLength; contentLen == -1 {
|
||||
logger.Debugf("Response content length unknown")
|
||||
logger.Debugf("CF-RAY: %s Response content length unknown", cfRay)
|
||||
} else {
|
||||
logger.Debugf("Response content length %d", contentLen)
|
||||
logger.Debugf("CF-RAY: %s Response content length %d", cfRay, contentLen)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -765,3 +838,11 @@ func activeIncidentsMsg(incidents []Incident) string {
|
|||
return preamble + " " + strings.Join(incidentStrings, "; ")
|
||||
|
||||
}
|
||||
|
||||
func findCfRayHeader(h1 *http.Request) string {
|
||||
return h1.Header.Get("Cf-Ray")
|
||||
}
|
||||
|
||||
func isLBProbeRequest(req *http.Request) bool {
|
||||
return strings.HasPrefix(req.UserAgent(), lbProbeUserAgentPrefix)
|
||||
}
|
||||
|
|
|
@ -1,235 +0,0 @@
|
|||
// Package client defines and implements interface to proxy to HTTP, websocket and hello world origins
|
||||
package originservice
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cloudflare/cloudflared/h2mux"
|
||||
"github.com/cloudflare/cloudflared/hello"
|
||||
"github.com/cloudflare/cloudflared/log"
|
||||
"github.com/cloudflare/cloudflared/websocket"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// OriginService is an interface to proxy requests to different type of origins
|
||||
type OriginService interface {
|
||||
Proxy(stream *h2mux.MuxedStream, req *http.Request) (resp *http.Response, err error)
|
||||
URL() *url.URL
|
||||
Summary() string
|
||||
Shutdown()
|
||||
}
|
||||
|
||||
// HTTPService talks to origin using HTTP/HTTPS
|
||||
type HTTPService struct {
|
||||
client http.RoundTripper
|
||||
originURL *url.URL
|
||||
chunkedEncoding bool
|
||||
}
|
||||
|
||||
func NewHTTPService(transport http.RoundTripper, url *url.URL, chunkedEncoding bool) OriginService {
|
||||
return &HTTPService{
|
||||
client: transport,
|
||||
originURL: url,
|
||||
chunkedEncoding: chunkedEncoding,
|
||||
}
|
||||
}
|
||||
|
||||
func (hc *HTTPService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
|
||||
// Support for WSGI Servers by switching transfer encoding from chunked to gzip/deflate
|
||||
if !hc.chunkedEncoding {
|
||||
req.TransferEncoding = []string{"gzip", "deflate"}
|
||||
cLength, err := strconv.Atoi(req.Header.Get("Content-Length"))
|
||||
if err == nil {
|
||||
req.ContentLength = int64(cLength)
|
||||
}
|
||||
}
|
||||
|
||||
// Request origin to keep connection alive to improve performance
|
||||
req.Header.Set("Connection", "keep-alive")
|
||||
|
||||
resp, err := hc.client.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error proxying request to HTTP origin")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = stream.WriteHeaders(h1ResponseToH2Response(resp))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error writing response header to HTTP origin")
|
||||
}
|
||||
if isEventStream(resp) {
|
||||
writeEventStream(stream, resp.Body)
|
||||
} else {
|
||||
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
|
||||
// compression generates dictionary on first write
|
||||
io.CopyBuffer(stream, resp.Body, make([]byte, 512*1024))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (hc *HTTPService) URL() *url.URL {
|
||||
return hc.originURL
|
||||
}
|
||||
|
||||
func (hc *HTTPService) Summary() string {
|
||||
return fmt.Sprintf("HTTP service listening on %s", hc.originURL)
|
||||
}
|
||||
|
||||
func (hc *HTTPService) Shutdown() {}
|
||||
|
||||
// WebsocketService talks to origin using WS/WSS
|
||||
type WebsocketService struct {
|
||||
tlsConfig *tls.Config
|
||||
originURL *url.URL
|
||||
shutdownC chan struct{}
|
||||
}
|
||||
|
||||
func NewWebSocketService(tlsConfig *tls.Config, url *url.URL) (OriginService, error) {
|
||||
listener, err := net.Listen("tcp", "127.0.0.1:")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot start Websocket Proxy Server")
|
||||
}
|
||||
shutdownC := make(chan struct{})
|
||||
go func() {
|
||||
websocket.StartProxyServer(log.CreateLogger(), listener, url.String(), shutdownC)
|
||||
}()
|
||||
return &WebsocketService{
|
||||
tlsConfig: tlsConfig,
|
||||
originURL: url,
|
||||
shutdownC: shutdownC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (wsc *WebsocketService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
|
||||
if !websocket.IsWebSocketUpgrade(req) {
|
||||
return nil, fmt.Errorf("request is not a websocket connection")
|
||||
}
|
||||
conn, response, err := websocket.ClientConnect(req, wsc.tlsConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer conn.Close()
|
||||
err = stream.WriteHeaders(h1ResponseToH2Response(response))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error writing response header to websocket origin")
|
||||
}
|
||||
// Copy to/from stream to the undelying connection. Use the underlying
|
||||
// connection because cloudflared doesn't operate on the message themselves
|
||||
websocket.Stream(conn.UnderlyingConn(), stream)
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (wsc *WebsocketService) URL() *url.URL {
|
||||
return wsc.originURL
|
||||
}
|
||||
|
||||
func (wsc *WebsocketService) Summary() string {
|
||||
return fmt.Sprintf("Websocket listening on %s", wsc.originURL)
|
||||
}
|
||||
|
||||
func (wsc *WebsocketService) Shutdown() {
|
||||
close(wsc.shutdownC)
|
||||
}
|
||||
|
||||
// HelloWorldService talks to the hello world example origin
|
||||
type HelloWorldService struct {
|
||||
client http.RoundTripper
|
||||
listener net.Listener
|
||||
originURL *url.URL
|
||||
shutdownC chan struct{}
|
||||
}
|
||||
|
||||
func NewHelloWorldService(transport http.RoundTripper) (OriginService, error) {
|
||||
listener, err := hello.CreateTLSListener("127.0.0.1:")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "cannot start Hello World Server")
|
||||
}
|
||||
shutdownC := make(chan struct{})
|
||||
go func() {
|
||||
hello.StartHelloWorldServer(log.CreateLogger(), listener, shutdownC)
|
||||
}()
|
||||
return &HelloWorldService{
|
||||
client: transport,
|
||||
listener: listener,
|
||||
originURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: listener.Addr().String(),
|
||||
},
|
||||
shutdownC: shutdownC,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (hwc *HelloWorldService) Proxy(stream *h2mux.MuxedStream, req *http.Request) (*http.Response, error) {
|
||||
// Request origin to keep connection alive to improve performance
|
||||
req.Header.Set("Connection", "keep-alive")
|
||||
resp, err := hwc.client.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error proxying request to Hello World origin")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = stream.WriteHeaders(h1ResponseToH2Response(resp))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error writing response header to Hello World origin")
|
||||
}
|
||||
|
||||
// Use CopyBuffer, because Copy only allocates a 32KiB buffer, and cross-stream
|
||||
// compression generates dictionary on first write
|
||||
io.CopyBuffer(stream, resp.Body, make([]byte, 512*1024))
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (hwc *HelloWorldService) URL() *url.URL {
|
||||
return hwc.originURL
|
||||
}
|
||||
|
||||
func (hwc *HelloWorldService) Summary() string {
|
||||
return fmt.Sprintf("Hello World service listening on %s", hwc.originURL)
|
||||
}
|
||||
|
||||
func (hwc *HelloWorldService) Shutdown() {
|
||||
hwc.listener.Close()
|
||||
}
|
||||
|
||||
func isEventStream(resp *http.Response) bool {
|
||||
// Check if content-type is text/event-stream. We need to check if the header value starts with text/event-stream
|
||||
// because text/event-stream; charset=UTF-8 is also valid
|
||||
// Ref: https://tools.ietf.org/html/rfc7231#section-3.1.1.1
|
||||
for _, contentType := range resp.Header["content-type"] {
|
||||
if strings.HasPrefix(strings.ToLower(contentType), "text/event-stream") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func writeEventStream(stream *h2mux.MuxedStream, respBody io.ReadCloser) {
|
||||
reader := bufio.NewReader(respBody)
|
||||
for {
|
||||
line, err := reader.ReadBytes('\n')
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
stream.Write(line)
|
||||
}
|
||||
}
|
||||
|
||||
func h1ResponseToH2Response(h1 *http.Response) (h2 []h2mux.Header) {
|
||||
h2 = []h2mux.Header{{Name: ":status", Value: fmt.Sprintf("%d", h1.StatusCode)}}
|
||||
for headerName, headerValues := range h1.Header {
|
||||
for _, headerValue := range headerValues {
|
||||
h2 = append(h2, h2mux.Header{Name: strings.ToLower(headerName), Value: headerValue})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,60 +0,0 @@
|
|||
package originservice
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestIsEventStream(t *testing.T) {
|
||||
tests := []struct {
|
||||
resp *http.Response
|
||||
isEventStream bool
|
||||
}{
|
||||
{
|
||||
resp: &http.Response{},
|
||||
isEventStream: false,
|
||||
},
|
||||
{
|
||||
// isEventStream checks all headers
|
||||
resp: &http.Response{
|
||||
Header: http.Header{
|
||||
"accept": []string{"text/html"},
|
||||
"content-type": []string{"text/event-stream"},
|
||||
},
|
||||
},
|
||||
isEventStream: true,
|
||||
},
|
||||
{
|
||||
// Content-Type and text/event-stream are case-insensitive. text/event-stream can be followed by OWS parameter
|
||||
resp: &http.Response{
|
||||
Header: http.Header{
|
||||
"content-type": []string{"Text/event-stream;charset=utf-8"},
|
||||
},
|
||||
},
|
||||
isEventStream: true,
|
||||
},
|
||||
{
|
||||
// Content-Type and text/event-stream are case-insensitive. text/event-stream can be followed by OWS parameter
|
||||
resp: &http.Response{
|
||||
Header: http.Header{
|
||||
"content-type": []string{"appication/json", "text/html", "Text/event-stream;charset=utf-8"},
|
||||
},
|
||||
},
|
||||
isEventStream: true,
|
||||
},
|
||||
{
|
||||
// Not an event stream because the content-type value doesn't start with text/event-stream
|
||||
resp: &http.Response{
|
||||
Header: http.Header{
|
||||
"content-type": []string{" text/event-stream"},
|
||||
},
|
||||
},
|
||||
isEventStream: false,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
assert.Equal(t, test.isEventStream, isEventStream(test.resp), "Header: %v", test.resp.Header)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue