diff --git a/.docker-images b/.docker-images new file mode 100644 index 00000000..9743ef78 --- /dev/null +++ b/.docker-images @@ -0,0 +1,8 @@ +images: + - name: cloudflared + dockerfile: Dockerfile + context: . + registries: + - name: docker.io/michael9127 + user: env:DOCKER_USER + password: env:DOCKER_PASSWORD diff --git a/.gitignore b/.gitignore index 2de5db2f..65164567 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ guide/public \#*\# cscope.* cloudflared +cloudflared.pkg cloudflared.exe !cmd/cloudflared/ .DS_Store diff --git a/.mac_resources/scripts/postinstall b/.mac_resources/scripts/postinstall new file mode 100755 index 00000000..deb4ecbf --- /dev/null +++ b/.mac_resources/scripts/postinstall @@ -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 \ No newline at end of file diff --git a/.mac_resources/uninstall.sh b/.mac_resources/uninstall.sh new file mode 100644 index 00000000..28563df9 --- /dev/null +++ b/.mac_resources/uninstall.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +/usr/local/bin/cloudflared service uninstall +rm /usr/local/bin/cloudflared +pkgutil --forget com.cloudflare.cloudflared \ No newline at end of file diff --git a/.teamcity/build-macos.sh b/.teamcity/build-macos.sh new file mode 100755 index 00000000..9ea3e1f4 --- /dev/null +++ b/.teamcity/build-macos.sh @@ -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 diff --git a/.teamcity/update-homebrew.sh b/.teamcity/update-homebrew.sh new file mode 100755 index 00000000..6d606d9c --- /dev/null +++ b/.teamcity/update-homebrew.sh @@ -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 <= 1 { diff --git a/carrier/carrier_test.go b/carrier/carrier_test.go index 379faadf..e5b4dc6f 100644 --- a/carrier/carrier_test.go +++ b/carrier/carrier_test.go @@ -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) } diff --git a/carrier/websocket.go b/carrier/websocket.go new file mode 100644 index 00000000..d86cc29a --- /dev/null +++ b/carrier/websocket.go @@ -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) +} diff --git a/certutil/certutil.go b/certutil/certutil.go new file mode 100644 index 00000000..e3b48c62 --- /dev/null +++ b/certutil/certutil.go @@ -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 +} diff --git a/certutil/certutil_test.go b/certutil/certutil_test.go new file mode 100644 index 00000000..26b13f5d --- /dev/null +++ b/certutil/certutil_test.go @@ -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) +} diff --git a/certutil/test-argo-tunnel-cert-json.pem b/certutil/test-argo-tunnel-cert-json.pem new file mode 100644 index 00000000..6755cff4 --- /dev/null +++ b/certutil/test-argo-tunnel-cert-json.pem @@ -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----- diff --git a/certutil/test-argo-tunnel-cert.pem b/certutil/test-argo-tunnel-cert.pem new file mode 100644 index 00000000..1a3397ac --- /dev/null +++ b/certutil/test-argo-tunnel-cert.pem @@ -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----- diff --git a/certutil/test-cert-no-key.pem b/certutil/test-cert-no-key.pem new file mode 100644 index 00000000..aae69fc9 --- /dev/null +++ b/certutil/test-cert-no-key.pem @@ -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----- diff --git a/certutil/test-cert-two-certificates.pem b/certutil/test-cert-two-certificates.pem new file mode 100644 index 00000000..214e2f8e --- /dev/null +++ b/certutil/test-cert-two-certificates.pem @@ -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----- diff --git a/certutil/test-cert-unknown-block.pem b/certutil/test-cert-unknown-block.pem new file mode 100644 index 00000000..f7180851 --- /dev/null +++ b/certutil/test-cert-unknown-block.pem @@ -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----- diff --git a/certutil/test-cert.pem b/certutil/test-cert.pem new file mode 100644 index 00000000..4d1c9f89 --- /dev/null +++ b/certutil/test-cert.pem @@ -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----- diff --git a/cfsetup.yaml b/cfsetup.yaml index 8e620ec2..ab73167d 100644 --- a/cfsetup.yaml +++ b/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: diff --git a/cmd/cloudflared/access/carrier.go b/cmd/cloudflared/access/carrier.go index 881e0c29..29e9b385 100644 --- a/cmd/cloudflared/access/carrier.go +++ b/cmd/cloudflared/access/carrier.go @@ -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 { diff --git a/cmd/cloudflared/access/cmd.go b/cmd/cloudflared/access/cmd.go index f1c01fe5..02c8e820 100644 --- a/cmd/cloudflared/access/cmd.go +++ b/cmd/cloudflared/access/cmd.go @@ -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 ", - 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 ", 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] [...]", 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=", 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 to fetch one.") + logger.Info("You don't have an Access token set. Please run access token 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") } diff --git a/cmd/cloudflared/app_forward_service.go b/cmd/cloudflared/app_forward_service.go new file mode 100644 index 00000000..f815b5c7 --- /dev/null +++ b/cmd/cloudflared/app_forward_service.go @@ -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) +} diff --git a/cmd/cloudflared/app_resolver_service.go b/cmd/cloudflared/app_resolver_service.go new file mode 100644 index 00000000..1ba19f17 --- /dev/null +++ b/cmd/cloudflared/app_resolver_service.go @@ -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() +} diff --git a/cmd/cloudflared/app_service.go b/cmd/cloudflared/app_service.go new file mode 100644 index 00000000..d2c0b0c3 --- /dev/null +++ b/cmd/cloudflared/app_service.go @@ -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()) + } + } +} diff --git a/cmd/cloudflared/buildinfo/build_info.go b/cmd/cloudflared/buildinfo/build_info.go index 80481716..aedface1 100644 --- a/cmd/cloudflared/buildinfo/build_info.go +++ b/cmd/cloudflared/buildinfo/build_info.go @@ -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) } diff --git a/cmd/cloudflared/cliutil/errors.go b/cmd/cloudflared/cliutil/errors.go new file mode 100644 index 00000000..afbb6fdb --- /dev/null +++ b/cmd/cloudflared/cliutil/errors.go @@ -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) +} diff --git a/cmd/cloudflared/config/configuration.go b/cmd/cloudflared/config/configuration.go index cb497dc2..ed72f3c0 100644 --- a/cmd/cloudflared/config/configuration.go +++ b/cmd/cloudflared/config/configuration.go @@ -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) { diff --git a/cmd/cloudflared/config/manager.go b/cmd/cloudflared/config/manager.go new file mode 100644 index 00000000..a286ab48 --- /dev/null +++ b/cmd/cloudflared/config/manager.go @@ -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) +} diff --git a/cmd/cloudflared/config/manager_test.go b/cmd/cloudflared/config/manager_test.go new file mode 100644 index 00000000..7121fa54 --- /dev/null +++ b/cmd/cloudflared/config/manager_test.go @@ -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") +} diff --git a/cmd/cloudflared/config/model.go b/cmd/cloudflared/config/model.go new file mode 100644 index 00000000..e1c40233 --- /dev/null +++ b/cmd/cloudflared/config/model.go @@ -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"} +} diff --git a/cmd/cloudflared/linux_service.go b/cmd/cloudflared/linux_service.go index fede6b23..99ec9c27 100644 --- a/cmd/cloudflared/linux_service.go +++ b/cmd/cloudflared/linux_service.go @@ -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"} { diff --git a/cmd/cloudflared/macos_service.go b/cmd/cloudflared/macos_service.go index d4fd3f20..2c280dfa 100644 --- a/cmd/cloudflared/macos_service.go +++ b/cmd/cloudflared/macos_service.go @@ -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 ThrottleInterval - 20 + 5 `, 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 } diff --git a/cmd/cloudflared/main.go b/cmd/cloudflared/main.go index 3c9e3659..0dcd746c 100644 --- a/cmd/cloudflared/main.go +++ b/cmd/cloudflared/main.go @@ -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 +} diff --git a/cmd/cloudflared/service_template.go b/cmd/cloudflared/service_template.go index 13d56b75..5d997f82 100644 --- a/cmd/cloudflared/service_template.go +++ b/cmd/cloudflared/service_template.go @@ -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 diff --git a/cmd/cloudflared/token/token.go b/cmd/cloudflared/token/token.go index 80d0a935..56c51776 100644 --- a/cmd/cloudflared/token/token.go +++ b/cmd/cloudflared/token/token.go @@ -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 } diff --git a/cmd/cloudflared/transfer/transfer.go b/cmd/cloudflared/transfer/transfer.go index dbe5536a..52829b8c 100644 --- a/cmd/cloudflared/transfer/transfer.go +++ b/cmd/cloudflared/transfer/transfer.go @@ -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 diff --git a/cmd/cloudflared/tunnel/cmd.go b/cmd/cloudflared/tunnel/cmd.go index 62c6c1c4..f3b8476d 100644 --- a/cmd/cloudflared/tunnel/cmd.go +++ b/cmd/cloudflared/tunnel/cmd.go @@ -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`) + } + } } } diff --git a/cmd/cloudflared/tunnel/configuration.go b/cmd/cloudflared/tunnel/configuration.go index 4c38aaaf..274eafa0 100644 --- a/cmd/cloudflared/tunnel/configuration.go +++ b/cmd/cloudflared/tunnel/configuration.go @@ -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())) } diff --git a/cmd/cloudflared/tunnel/logger.go b/cmd/cloudflared/tunnel/logger.go deleted file mode 100644 index 164de01b..00000000 --- a/cmd/cloudflared/tunnel/logger.go +++ /dev/null @@ -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 -} diff --git a/cmd/cloudflared/tunnel/login.go b/cmd/cloudflared/tunnel/login.go index 1eb94936..3a927259 100644 --- a/cmd/cloudflared/tunnel/login.go +++ b/cmd/cloudflared/tunnel/login.go @@ -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 diff --git a/cmd/cloudflared/tunnel/server.go b/cmd/cloudflared/tunnel/server.go index 3d089f41..bb6598e3 100644 --- a/cmd/cloudflared/tunnel/server.go +++ b/cmd/cloudflared/tunnel/server.go @@ -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 diff --git a/cmd/cloudflared/tunnel/signal.go b/cmd/cloudflared/tunnel/signal.go index afc26ccf..41a92d69 100644 --- a/cmd/cloudflared/tunnel/signal.go +++ b/cmd/cloudflared/tunnel/signal.go @@ -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) diff --git a/cmd/cloudflared/tunnel/signal_test.go b/cmd/cloudflared/tunnel/signal_test.go index 942706c2..0f5e735f 100644 --- a/cmd/cloudflared/tunnel/signal_test.go +++ b/cmd/cloudflared/tunnel/signal_test.go @@ -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) diff --git a/cmd/cloudflared/tunnel/subcommands.go b/cmd/cloudflared/tunnel/subcommands.go new file mode 100644 index 00000000..b49882bd --- /dev/null +++ b/cmd/cloudflared/tunnel/subcommands.go @@ -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}) +} diff --git a/cmd/cloudflared/tunnel/subcommands_test.go b/cmd/cloudflared/tunnel/subcommands_test.go new file mode 100644 index 00000000..36b86daf --- /dev/null +++ b/cmd/cloudflared/tunnel/subcommands_test.go @@ -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) +} diff --git a/cmd/cloudflared/updater/update.go b/cmd/cloudflared/updater/update.go index 96cdc95b..6457a4ac 100644 --- a/cmd/cloudflared/updater/update.go +++ b/cmd/cloudflared/updater/update.go @@ -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 +} diff --git a/cmd/cloudflared/updater/update_test.go b/cmd/cloudflared/updater/update_test.go index 218b22b4..d4ad93ee 100644 --- a/cmd/cloudflared/updater/update_test.go +++ b/cmd/cloudflared/updater/update_test.go @@ -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() { diff --git a/cmd/cloudflared/windows_service.go b/cmd/cloudflared/windows_service.go index 1c25699d..a78b4fdb 100644 --- a/cmd/cloudflared/windows_service.go +++ b/cmd/cloudflared/windows_service.go @@ -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 { diff --git a/connection/connection.go b/connection/connection.go deleted file mode 100644 index b898403f..00000000 --- a/connection/connection.go +++ /dev/null @@ -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() -} diff --git a/connection/manager.go b/connection/manager.go deleted file mode 100644 index eeac2e01..00000000 --- a/connection/manager.go +++ /dev/null @@ -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, - } -} diff --git a/connection/manager_test.go b/connection/manager_test.go deleted file mode 100644 index 465fc951..00000000 --- a/connection/manager_test.go +++ /dev/null @@ -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()) -} diff --git a/connection/rpc.go b/connection/rpc.go index 9c10c334..8c125d92 100644 --- a/connection/rpc.go +++ b/connection/rpc.go @@ -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) diff --git a/dbconnect/cmd.go b/dbconnect/cmd.go index e24fec4d..7f55f1ee 100644 --- a/dbconnect/cmd.go +++ b/dbconnect/cmd.go @@ -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. diff --git a/dbconnect/proxy.go b/dbconnect/proxy.go index 2a1a53ad..fbdfb0b9 100644 --- a/dbconnect/proxy.go +++ b/dbconnect/proxy.go @@ -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. diff --git a/edgediscovery/allregions/discovery.go b/edgediscovery/allregions/discovery.go index b18e3df6..8640f494 100644 --- a/edgediscovery/allregions/discovery.go +++ b/edgediscovery/allregions/discovery.go @@ -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) } diff --git a/edgediscovery/allregions/discovery_test.go b/edgediscovery/allregions/discovery_test.go index 0ead20bc..b2be4350 100644 --- a/edgediscovery/allregions/discovery_test.go +++ b/edgediscovery/allregions/discovery_test.go @@ -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 { diff --git a/edgediscovery/allregions/region.go b/edgediscovery/allregions/region.go index 31fb7311..aee3fd93 100644 --- a/edgediscovery/allregions/region.go +++ b/edgediscovery/allregions/region.go @@ -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) } diff --git a/edgediscovery/allregions/regions.go b/edgediscovery/allregions/regions.go index 8a883523..bacc75a1 100644 --- a/edgediscovery/allregions/regions.go +++ b/edgediscovery/allregions/regions.go @@ -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. diff --git a/edgediscovery/allregions/regions_test.go b/edgediscovery/allregions/regions_test.go index 6c88f281..8abdf950 100644 --- a/edgediscovery/allregions/regions_test.go +++ b/edgediscovery/allregions/regions_test.go @@ -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) diff --git a/edgediscovery/edgediscovery.go b/edgediscovery/edgediscovery.go index abf83a66..c4db7249 100644 --- a/edgediscovery/edgediscovery.go +++ b/edgediscovery/edgediscovery.go @@ -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) } diff --git a/edgediscovery/edgediscovery_test.go b/edgediscovery/edgediscovery_test.go index e25dc1bc..3439861a 100644 --- a/edgediscovery/edgediscovery_test.go +++ b/edgediscovery/edgediscovery_test.go @@ -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 diff --git a/github_release.py b/github_release.py new file mode 100755 index 00000000..6ced8032 --- /dev/null +++ b/github_release.py @@ -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() diff --git a/go.mod b/go.mod index bc4f4046..a311c606 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index ae7fcad3..ec31440a 100644 --- a/go.sum +++ b/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= diff --git a/h2mux/h2mux.go b/h2mux/h2mux.go index 2b6defde..a9af47bb 100644 --- a/h2mux/h2mux.go +++ b/h2mux/h2mux.go @@ -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() diff --git a/h2mux/h2mux_test.go b/h2mux/h2mux_test.go index b7995232..5f6b8b5b 100644 --- a/h2mux/h2mux_test.go +++ b/h2mux/h2mux_test.go @@ -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 := ` 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 { diff --git a/h2mux/muxwriter_test.go b/h2mux/muxwriter_test.go new file mode 100644 index 00000000..07e23bdc --- /dev/null +++ b/h2mux/muxwriter_test.go @@ -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)) +} diff --git a/h2mux/sample/index.html b/h2mux/sample/index.html index 6388b6c2..fe91d668 100644 --- a/h2mux/sample/index.html +++ b/h2mux/sample/index.html @@ -80,7 +80,7 @@ ghost.init({ - + + +