
4726 changed files with 1763680 additions and 0 deletions
@ -0,0 +1,403 @@
|
||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. |
||||
|
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "code.cfops.it/go/brotli" |
||||
packages = ["."] |
||||
revision = "18c9f6c67e3dfc12e0ddaca748d2887f97a7ac28" |
||||
|
||||
[[projects]] |
||||
name = "github.com/BurntSushi/toml" |
||||
packages = ["."] |
||||
revision = "b26d9c308763d68093482582cea63d69be07a0f0" |
||||
version = "v0.3.0" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/beorn7/perks" |
||||
packages = ["quantile"] |
||||
revision = "3a771d992973f24aa725d07868b467d1ddfceafb" |
||||
|
||||
[[projects]] |
||||
name = "github.com/certifi/gocertifi" |
||||
packages = ["."] |
||||
revision = "deb3ae2ef2610fde3330947281941c562861188b" |
||||
version = "2018.01.18" |
||||
|
||||
[[projects]] |
||||
name = "github.com/coredns/coredns" |
||||
packages = [ |
||||
"core/dnsserver", |
||||
"coremain", |
||||
"pb", |
||||
"plugin", |
||||
"plugin/cache", |
||||
"plugin/cache/freq", |
||||
"plugin/etcd/msg", |
||||
"plugin/metrics", |
||||
"plugin/metrics/vars", |
||||
"plugin/pkg/cache", |
||||
"plugin/pkg/dnstest", |
||||
"plugin/pkg/dnsutil", |
||||
"plugin/pkg/edns", |
||||
"plugin/pkg/fuzz", |
||||
"plugin/pkg/log", |
||||
"plugin/pkg/nonwriter", |
||||
"plugin/pkg/rcode", |
||||
"plugin/pkg/response", |
||||
"plugin/pkg/trace", |
||||
"plugin/pkg/uniq", |
||||
"plugin/test", |
||||
"request" |
||||
] |
||||
revision = "f78f30231df90da6184d5f811ecf9c06b0160c2b" |
||||
version = "v1.1.4" |
||||
|
||||
[[projects]] |
||||
name = "github.com/coreos/go-systemd" |
||||
packages = ["daemon"] |
||||
revision = "39ca1b05acc7ad1220e09f133283b8859a8b71ab" |
||||
version = "v17" |
||||
|
||||
[[projects]] |
||||
name = "github.com/davecgh/go-spew" |
||||
packages = ["spew"] |
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76" |
||||
version = "v1.1.0" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/equinox-io/equinox" |
||||
packages = [ |
||||
".", |
||||
"internal/go-update", |
||||
"internal/go-update/internal/binarydist", |
||||
"internal/go-update/internal/osext", |
||||
"internal/osext", |
||||
"proto" |
||||
] |
||||
revision = "f24972fa72facf59d05c91c848b65eac38815915" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/facebookgo/grace" |
||||
packages = ["gracenet"] |
||||
revision = "75cf19382434e82df4dd84953f566b8ad23d6e9e" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/flynn/go-shlex" |
||||
packages = ["."] |
||||
revision = "3f9db97f856818214da2e1057f8ad84803971cff" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/getsentry/raven-go" |
||||
packages = ["."] |
||||
revision = "ed7bcb39ff10f39ab08e317ce16df282845852fa" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/golang-collections/collections" |
||||
packages = ["queue"] |
||||
revision = "604e922904d35e97f98a774db7881f049cd8d970" |
||||
|
||||
[[projects]] |
||||
name = "github.com/golang/protobuf" |
||||
packages = [ |
||||
"proto", |
||||
"ptypes", |
||||
"ptypes/any", |
||||
"ptypes/duration", |
||||
"ptypes/timestamp" |
||||
] |
||||
revision = "b4deda0973fb4c70b50d226b1af49f3da59f5265" |
||||
version = "v1.1.0" |
||||
|
||||
[[projects]] |
||||
name = "github.com/google/uuid" |
||||
packages = ["."] |
||||
revision = "064e2069ce9c359c118179501254f67d7d37ba24" |
||||
version = "0.2" |
||||
|
||||
[[projects]] |
||||
name = "github.com/gorilla/websocket" |
||||
packages = ["."] |
||||
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b" |
||||
version = "v1.2.0" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/grpc-ecosystem/grpc-opentracing" |
||||
packages = ["go/otgrpc"] |
||||
revision = "8e809c8a86450a29b90dcc9efbf062d0fe6d9746" |
||||
|
||||
[[projects]] |
||||
name = "github.com/mattn/go-colorable" |
||||
packages = ["."] |
||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" |
||||
version = "v0.0.9" |
||||
|
||||
[[projects]] |
||||
name = "github.com/mattn/go-isatty" |
||||
packages = ["."] |
||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" |
||||
version = "v0.0.3" |
||||
|
||||
[[projects]] |
||||
name = "github.com/matttproud/golang_protobuf_extensions" |
||||
packages = ["pbutil"] |
||||
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c" |
||||
version = "v1.0.1" |
||||
|
||||
[[projects]] |
||||
name = "github.com/mholt/caddy" |
||||
packages = [ |
||||
".", |
||||
"caddyfile", |
||||
"telemetry" |
||||
] |
||||
revision = "1f7b5abc80679fb71ee0e04ed98cbe284b1fc181" |
||||
version = "v0.11.0" |
||||
|
||||
[[projects]] |
||||
name = "github.com/miekg/dns" |
||||
packages = ["."] |
||||
revision = "5a2b9fab83ff0f8bfc99684bd5f43a37abe560f1" |
||||
version = "v1.0.8" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/mitchellh/go-homedir" |
||||
packages = ["."] |
||||
revision = "3864e76763d94a6df2f9960b16a20a33da9f9a66" |
||||
|
||||
[[projects]] |
||||
name = "github.com/opentracing/opentracing-go" |
||||
packages = [ |
||||
".", |
||||
"ext", |
||||
"log" |
||||
] |
||||
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38" |
||||
version = "v1.0.2" |
||||
|
||||
[[projects]] |
||||
name = "github.com/pkg/errors" |
||||
packages = ["."] |
||||
revision = "645ef00459ed84a119197bfb8d8205042c6df63d" |
||||
version = "v0.8.0" |
||||
|
||||
[[projects]] |
||||
name = "github.com/pmezard/go-difflib" |
||||
packages = ["difflib"] |
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2" |
||||
version = "v1.0.0" |
||||
|
||||
[[projects]] |
||||
name = "github.com/prometheus/client_golang" |
||||
packages = [ |
||||
"prometheus", |
||||
"prometheus/promhttp" |
||||
] |
||||
revision = "967789050ba94deca04a5e84cce8ad472ce313c1" |
||||
version = "v0.9.0-pre1" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/prometheus/client_model" |
||||
packages = ["go"] |
||||
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/prometheus/common" |
||||
packages = [ |
||||
"expfmt", |
||||
"internal/bitbucket.org/ww/goautoneg", |
||||
"model" |
||||
] |
||||
revision = "7600349dcfe1abd18d72d3a1770870d9800a7801" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "github.com/prometheus/procfs" |
||||
packages = [ |
||||
".", |
||||
"internal/util", |
||||
"nfs", |
||||
"xfs" |
||||
] |
||||
revision = "ae68e2d4c00fed4943b5f6698d504a5fe083da8a" |
||||
|
||||
[[projects]] |
||||
name = "github.com/rifflock/lfshook" |
||||
packages = ["."] |
||||
revision = "bf539943797a1f34c1f502d07de419b5238ae6c6" |
||||
version = "v2.3" |
||||
|
||||
[[projects]] |
||||
name = "github.com/sirupsen/logrus" |
||||
packages = ["."] |
||||
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc" |
||||
version = "v1.0.5" |
||||
|
||||
[[projects]] |
||||
name = "github.com/stretchr/testify" |
||||
packages = ["assert"] |
||||
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" |
||||
version = "v1.2.2" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "golang.org/x/crypto" |
||||
packages = [ |
||||
"ed25519", |
||||
"ed25519/internal/edwards25519", |
||||
"ssh/terminal" |
||||
] |
||||
revision = "a49355c7e3f8fe157a85be2f77e6e269a0f89602" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "golang.org/x/net" |
||||
packages = [ |
||||
"bpf", |
||||
"context", |
||||
"http/httpguts", |
||||
"http2", |
||||
"http2/hpack", |
||||
"idna", |
||||
"internal/iana", |
||||
"internal/socket", |
||||
"internal/timeseries", |
||||
"ipv4", |
||||
"ipv6", |
||||
"trace", |
||||
"websocket" |
||||
] |
||||
revision = "32a936f46389aa10549d60bd7833e54b01685d09" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "golang.org/x/sync" |
||||
packages = ["errgroup"] |
||||
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "golang.org/x/sys" |
||||
packages = [ |
||||
"unix", |
||||
"windows", |
||||
"windows/registry", |
||||
"windows/svc", |
||||
"windows/svc/eventlog", |
||||
"windows/svc/mgr" |
||||
] |
||||
revision = "ce36f3865eeb42541ce3f87f32f8462c5687befa" |
||||
|
||||
[[projects]] |
||||
name = "golang.org/x/text" |
||||
packages = [ |
||||
"collate", |
||||
"collate/build", |
||||
"internal/colltab", |
||||
"internal/gen", |
||||
"internal/tag", |
||||
"internal/triegen", |
||||
"internal/ucd", |
||||
"language", |
||||
"secure/bidirule", |
||||
"transform", |
||||
"unicode/bidi", |
||||
"unicode/cldr", |
||||
"unicode/norm", |
||||
"unicode/rangetable" |
||||
] |
||||
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0" |
||||
version = "v0.3.0" |
||||
|
||||
[[projects]] |
||||
branch = "master" |
||||
name = "google.golang.org/genproto" |
||||
packages = ["googleapis/rpc/status"] |
||||
revision = "ff3583edef7de132f219f0efc00e097cabcc0ec0" |
||||
|
||||
[[projects]] |
||||
name = "google.golang.org/grpc" |
||||
packages = [ |
||||
".", |
||||
"balancer", |
||||
"balancer/base", |
||||
"balancer/roundrobin", |
||||
"codes", |
||||
"connectivity", |
||||
"credentials", |
||||
"encoding", |
||||
"encoding/proto", |
||||
"grpclog", |
||||
"internal", |
||||
"internal/backoff", |
||||
"internal/channelz", |
||||
"internal/grpcrand", |
||||
"keepalive", |
||||
"metadata", |
||||
"naming", |
||||
"peer", |
||||
"resolver", |
||||
"resolver/dns", |
||||
"resolver/passthrough", |
||||
"stats", |
||||
"status", |
||||
"tap", |
||||
"transport" |
||||
] |
||||
revision = "168a6198bcb0ef175f7dacec0b8691fc141dc9b8" |
||||
version = "v1.13.0" |
||||
|
||||
[[projects]] |
||||
branch = "altsrc-parse-durations" |
||||
name = "gopkg.in/urfave/cli.v2" |
||||
packages = [ |
||||
".", |
||||
"altsrc" |
||||
] |
||||
revision = "d604b6ffeee878fbf084fd2761466b6649989cee" |
||||
source = "https://github.com/cbranch/cli" |
||||
|
||||
[[projects]] |
||||
name = "gopkg.in/yaml.v2" |
||||
packages = ["."] |
||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183" |
||||
version = "v2.2.1" |
||||
|
||||
[[projects]] |
||||
name = "zombiezen.com/go/capnproto2" |
||||
packages = [ |
||||
".", |
||||
"encoding/text", |
||||
"internal/fulfiller", |
||||
"internal/nodemap", |
||||
"internal/packed", |
||||
"internal/queue", |
||||
"internal/schema", |
||||
"internal/strquote", |
||||
"pogs", |
||||
"rpc", |
||||
"rpc/internal/refcount", |
||||
"schemas", |
||||
"server", |
||||
"std/capnp/rpc" |
||||
] |
||||
revision = "7cfd211c19c7f5783c695f3654efa46f0df259c3" |
||||
source = "https://github.com/zombiezen/go-capnproto2" |
||||
version = "v2.17.1" |
||||
|
||||
[solve-meta] |
||||
analyzer-name = "dep" |
||||
analyzer-version = 1 |
||||
inputs-digest = "42fdf43f93aac410675bb8134097b51c90c110dc4c77595b8d2fb7c7876bd3d2" |
||||
solver-name = "gps-cdcl" |
||||
solver-version = 1 |
@ -0,0 +1,45 @@
|
||||
[[constraint]] |
||||
name = "github.com/facebookgo/grace" |
||||
branch = "master" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/getsentry/raven-go" |
||||
branch = "master" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/pkg/errors" |
||||
version = "0.8.0" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/prometheus/client_golang" |
||||
version = "0.9.0-pre1" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/sirupsen/logrus" |
||||
version = "1.0.3" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/stretchr/testify" |
||||
version = "1.2.1" |
||||
|
||||
[[constraint]] |
||||
name = "golang.org/x/net" |
||||
branch = "master" |
||||
|
||||
[[constraint]] |
||||
name = "golang.org/x/sync" |
||||
branch = "master" |
||||
|
||||
[[constraint]] |
||||
name = "gopkg.in/urfave/cli.v2" |
||||
source = "https://github.com/cbranch/cli" |
||||
branch = "altsrc-parse-durations" |
||||
|
||||
[[constraint]] |
||||
name = "zombiezen.com/go/capnproto2" |
||||
source = "https://github.com/zombiezen/go-capnproto2" |
||||
|
||||
[[constraint]] |
||||
name = "github.com/gorilla/websocket" |
||||
version = "1.2.0" |
||||
|
@ -0,0 +1,320 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"crypto/x509" |
||||
"encoding/hex" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"math/rand" |
||||
"net" |
||||
"net/http" |
||||
"os" |
||||
"path/filepath" |
||||
"runtime" |
||||
"strings" |
||||
"time" |
||||
|
||||
"github.com/cloudflare/cloudflared/origin" |
||||
"github.com/cloudflare/cloudflared/tlsconfig" |
||||
tunnelpogs "github.com/cloudflare/cloudflared/tunnelrpc/pogs" |
||||
"github.com/cloudflare/cloudflared/validation" |
||||
|
||||
"github.com/sirupsen/logrus" |
||||
"gopkg.in/urfave/cli.v2" |
||||
"gopkg.in/urfave/cli.v2/altsrc" |
||||
|
||||
"github.com/mitchellh/go-homedir" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
var ( |
||||
defaultConfigFiles = []string{"config.yml", "config.yaml"} |
||||
|
||||
// Launchd doesn't set root env variables, so there is default
|
||||
// Windows default config dir was ~/cloudflare-warp in documentation; let's keep it compatible
|
||||
defaultConfigDirs = []string{"~/.cloudflared", "~/.cloudflare-warp", "~/cloudflare-warp", "/usr/local/etc/cloudflared", "/etc/cloudflared"} |
||||
) |
||||
|
||||
const defaultCredentialFile = "cert.pem" |
||||
|
||||
func fileExists(path string) (bool, error) { |
||||
f, err := os.Open(path) |
||||
if err != nil { |
||||
if os.IsNotExist(err) { |
||||
// ignore missing files
|
||||
return false, nil |
||||
} |
||||
return false, err |
||||
} |
||||
f.Close() |
||||
return true, nil |
||||
} |
||||
|
||||
// returns the first path that contains a cert.pem file. If none of the defaultConfigDirs
|
||||
// (differs by OS for legacy reasons) contains a cert.pem file, return empty string
|
||||
func findDefaultOriginCertPath() string { |
||||
for _, defaultConfigDir := range defaultConfigDirs { |
||||
originCertPath, _ := homedir.Expand(filepath.Join(defaultConfigDir, defaultCredentialFile)) |
||||
if ok, _ := fileExists(originCertPath); ok { |
||||
return originCertPath |
||||
} |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// returns the first path that contains a config file. If none of the combination of
|
||||
// defaultConfigDirs (differs by OS for legacy reasons) and defaultConfigFiles
|
||||
// contains a config file, return empty string
|
||||
func findDefaultConfigPath() string { |
||||
for _, configDir := range defaultConfigDirs { |
||||
for _, configFile := range defaultConfigFiles { |
||||
dirPath, err := homedir.Expand(configDir) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
path := filepath.Join(dirPath, configFile) |
||||
if ok, _ := fileExists(path); ok { |
||||
return path |
||||
} |
||||
} |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
func findInputSourceContext(context *cli.Context) (altsrc.InputSourceContext, error) { |
||||
if context.String("config") != "" { |
||||
return altsrc.NewYamlSourceFromFile(context.String("config")) |
||||
} |
||||
return nil, nil |
||||
} |
||||
|
||||
func generateRandomClientID() string { |
||||
r := rand.New(rand.NewSource(time.Now().UnixNano())) |
||||
id := make([]byte, 32) |
||||
r.Read(id) |
||||
return hex.EncodeToString(id) |
||||
} |
||||
|
||||
func enoughOptionsSet(c *cli.Context) bool { |
||||
// For cloudflared to work, the user needs to at least provide a hostname,
|
||||
// or runs as stand alone DNS proxy .
|
||||
// When using sudo, use -E flag to preserve env vars
|
||||
if c.NumFlags() == 0 && c.NArg() == 0 && os.Getenv("TUNNEL_HOSTNAME") == "" && os.Getenv("TUNNEL_DNS") == "" { |
||||
if isRunningFromTerminal() { |
||||
logger.Errorf("No arguments were provided. You need to at least specify the hostname for this tunnel. See %s", quickStartUrl) |
||||
logger.Infof("If you want to run Argo Tunnel client as a stand alone DNS proxy, run with --proxy-dns option or set TUNNEL_DNS environment variable.") |
||||
} else { |
||||
logger.Errorf("You need to specify all the options in a configuration file, or use environment variables. See %s and %s", serviceUrl, argumentsUrl) |
||||
logger.Infof("If you want to run Argo Tunnel client as a stand alone DNS proxy, specify proxy-dns option in the configuration file, or set TUNNEL_DNS environment variable.") |
||||
} |
||||
cli.ShowAppHelp(c) |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func handleDeprecatedOptions(c *cli.Context) 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 |
||||
} |
||||
|
||||
// validate url. It can be either from --url or argument
|
||||
func validateUrl(c *cli.Context) (string, error) { |
||||
var url = c.String("url") |
||||
if c.NArg() > 0 { |
||||
if c.IsSet("url") { |
||||
return "", errors.New("Specified origin urls using both --url and argument. Decide which one you want, I can only support one.") |
||||
} |
||||
url = c.Args().Get(0) |
||||
} |
||||
validUrl, err := validation.ValidateUrl(url) |
||||
return validUrl, err |
||||
} |
||||
|
||||
func logClientOptions(c *cli.Context) { |
||||
flags := make(map[string]interface{}) |
||||
for _, flag := range c.LocalFlagNames() { |
||||
flags[flag] = c.Generic(flag) |
||||
} |
||||
if len(flags) > 0 { |
||||
logger.Infof("Flags %v", flags) |
||||
} |
||||
|
||||
envs := make(map[string]string) |
||||
// Find env variables for Argo Tunnel
|
||||
for _, env := range os.Environ() { |
||||
// All Argo Tunnel env variables start with TUNNEL_
|
||||
if strings.Contains(env, "TUNNEL_") { |
||||
vars := strings.Split(env, "=") |
||||
if len(vars) == 2 { |
||||
envs[vars[0]] = vars[1] |
||||
} |
||||
} |
||||
} |
||||
if len(envs) > 0 { |
||||
logger.Infof("Environmental variables %v", envs) |
||||
} |
||||
} |
||||
|
||||
func dnsProxyStandAlone(c *cli.Context) bool { |
||||
return c.IsSet("proxy-dns") && (!c.IsSet("hostname") && !c.IsSet("tag") && !c.IsSet("hello-world")) |
||||
} |
||||
|
||||
func getOriginCert(c *cli.Context) ([]byte, error) { |
||||
if c.String("origincert") == "" { |
||||
logger.Warnf("Cannot determine default origin certificate path. No file %s in %v", defaultCredentialFile, defaultConfigDirs) |
||||
if isRunningFromTerminal() { |
||||
logger.Errorf("You need to specify the origin certificate path with --origincert option, or set TUNNEL_ORIGIN_CERT environment variable. See %s for more information.", argumentsUrl) |
||||
return nil, fmt.Errorf("Client didn't specify origincert path when running from terminal") |
||||
} 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") |
||||
} |
||||
} |
||||
// Check that the user has acquired a certificate using the login command
|
||||
originCertPath, err := homedir.Expand(c.String("origincert")) |
||||
if err != nil { |
||||
logger.WithError(err).Errorf("Cannot resolve path %s", c.String("origincert")) |
||||
return nil, fmt.Errorf("Cannot resolve path %s", c.String("origincert")) |
||||
} |
||||
ok, err := fileExists(originCertPath) |
||||
if err != nil { |
||||
logger.Errorf("Cannot check if origin cert exists at path %s", c.String("origincert")) |
||||
return nil, fmt.Errorf("Cannot check if origin cert exists at path %s", c.String("origincert")) |
||||
} |
||||
if !ok { |
||||
logger.Errorf(`Cannot find a valid certificate for your origin at the path: |
||||
|
||||
%s |
||||
|
||||
If the path above is wrong, specify the path with the -origincert option. |
||||
If you don't have a certificate signed by Cloudflare, run the command: |
||||
|
||||
%s login |
||||
`, originCertPath, os.Args[0]) |
||||
return nil, fmt.Errorf("Cannot find a valid certificate at the path %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) |
||||
return nil, fmt.Errorf("Cannot read %s to load origin certificate", originCertPath) |
||||
} |
||||
return originCert, nil |
||||
} |
||||
|
||||
func prepareTunnelConfig(c *cli.Context, buildInfo *origin.BuildInfo, logger, protoLogger *logrus.Logger) (*origin.TunnelConfig, error) { |
||||
hostname, err := validation.ValidateHostname(c.String("hostname")) |
||||
if err != nil { |
||||
logger.WithError(err).Error("Invalid hostname") |
||||
return nil, errors.Wrap(err, "Invalid hostname") |
||||
} |
||||
clientID := c.String("id") |
||||
if !c.IsSet("id") { |
||||
clientID = generateRandomClientID() |
||||
} |
||||
|
||||
tags, err := NewTagSliceFromCLI(c.StringSlice("tag")) |
||||
if err != nil { |
||||
logger.WithError(err).Error("Tag parse failure") |
||||
return nil, errors.Wrap(err, "Tag parse failure") |
||||
} |
||||
|
||||
tags = append(tags, tunnelpogs.Tag{Name: "ID", Value: clientID}) |
||||
|
||||
url, err := validateUrl(c) |
||||
if err != nil { |
||||
logger.WithError(err).Error("Error validating url") |
||||
return nil, errors.Wrap(err, "Error validating url") |
||||
} |
||||
logger.Infof("Proxying tunnel requests to %s", url) |
||||
|
||||
originCert, err := getOriginCert(c) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, "Error getting origin cert") |
||||
} |
||||
|
||||
originCertPool, err := loadCertPool(c, logger) |
||||
if err != nil { |
||||
logger.WithError(err).Error("Error loading cert pool") |
||||
return nil, errors.Wrap(err, "Error loading cert pool") |
||||
} |
||||
|
||||
tunnelMetrics := origin.NewTunnelMetrics() |
||||
httpTransport := &http.Transport{ |
||||
Proxy: http.ProxyFromEnvironment, |
||||
DialContext: (&net.Dialer{ |
||||
Timeout: c.Duration("proxy-connect-timeout"), |
||||
KeepAlive: c.Duration("proxy-tcp-keepalive"), |
||||
DualStack: !c.Bool("proxy-no-happy-eyeballs"), |
||||
}).DialContext, |
||||
MaxIdleConns: c.Int("proxy-keepalive-connections"), |
||||
IdleConnTimeout: c.Duration("proxy-keepalive-timeout"), |
||||
TLSHandshakeTimeout: c.Duration("proxy-tls-timeout"), |
||||
ExpectContinueTimeout: 1 * time.Second, |
||||
TLSClientConfig: &tls.Config{RootCAs: originCertPool, InsecureSkipVerify: c.IsSet("no-tls-verify")}, |
||||
} |
||||
|
||||
if !c.IsSet("hello-world") && c.IsSet("origin-server-name") { |
||||
httpTransport.TLSClientConfig.ServerName = c.String("origin-server-name") |
||||
} |
||||
|
||||
return &origin.TunnelConfig{ |
||||
EdgeAddrs: c.StringSlice("edge"), |
||||
OriginUrl: url, |
||||
Hostname: hostname, |
||||
OriginCert: originCert, |
||||
TlsConfig: tlsconfig.CreateTunnelConfig(c, c.StringSlice("edge")), |
||||
ClientTlsConfig: httpTransport.TLSClientConfig, |
||||
Retries: c.Uint("retries"), |
||||
HeartbeatInterval: c.Duration("heartbeat-interval"), |
||||
MaxHeartbeats: c.Uint64("heartbeat-count"), |
||||
ClientID: clientID, |
||||
BuildInfo: buildInfo, |
||||
ReportedVersion: Version, |
||||
LBPool: c.String("lb-pool"), |
||||
Tags: tags, |
||||
HAConnections: c.Int("ha-connections"), |
||||
HTTPTransport: httpTransport, |
||||
Metrics: tunnelMetrics, |
||||
MetricsUpdateFreq: c.Duration("metrics-update-freq"), |
||||
ProtocolLogger: protoLogger, |
||||
Logger: logger, |
||||
IsAutoupdated: c.Bool("is-autoupdated"), |
||||
GracePeriod: c.Duration("grace-period"), |
||||
RunFromTerminal: isRunningFromTerminal(), |
||||
NoChunkedEncoding: c.Bool("no-chunked-encoding"), |
||||
CompressionQuality: c.Uint64("compression-quality"), |
||||
}, nil |
||||
} |
||||
|
||||
func loadCertPool(c *cli.Context, logger *logrus.Logger) (*x509.CertPool, error) { |
||||
const originCAPoolFlag = "origin-ca-pool" |
||||
originCAPoolFilename := c.String(originCAPoolFlag) |
||||
var originCustomCAPool []byte |
||||
|
||||
if originCAPoolFilename != "" { |
||||
var err error |
||||
originCustomCAPool, err = ioutil.ReadFile(originCAPoolFilename) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, fmt.Sprintf("unable to read the file %s for --%s", originCAPoolFilename, originCAPoolFlag)) |
||||
} |
||||
} |
||||
|
||||
originCertPool, err := tlsconfig.LoadOriginCertPool(originCustomCAPool) |
||||
if err != nil { |
||||
return nil, errors.Wrap(err, "error loading the certificate pool") |
||||
} |
||||
|
||||
// Windows users should be notified that they can use the flag
|
||||
if runtime.GOOS == "windows" && originCAPoolFilename == "" { |
||||
logger.Infof("cloudflared does not support loading the system root certificate pool on Windows. Please use the --%s to specify it", originCAPoolFlag) |
||||
} |
||||
|
||||
return originCertPool, nil |
||||
} |
@ -0,0 +1,13 @@
|
||||
// +build !windows,!darwin,!linux
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"os" |
||||
|
||||
cli "gopkg.in/urfave/cli.v2" |
||||
) |
||||
|
||||
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) { |
||||
app.Run(os.Args) |
||||
} |
@ -0,0 +1,21 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"gopkg.in/urfave/cli.v2" |
||||
|
||||
"github.com/cloudflare/cloudflared/hello" |
||||
) |
||||
|
||||
|
||||
func helloWorld(c *cli.Context) error { |
||||
address := fmt.Sprintf(":%d", c.Int("port")) |
||||
listener, err := hello.CreateTLSListener(address) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer listener.Close() |
||||
err = hello.StartHelloWorldServer(logger, listener, nil) |
||||
return err |
||||
} |
@ -0,0 +1,35 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"testing" |
||||
) |
||||
|
||||
func TestCreateListenerHostAndPortSuccess(t *testing.T) { |
||||
listener, err := createListener("localhost:1234") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if listener.Addr().String() == "" { |
||||
t.Fatal("Fail to find available port") |
||||
} |
||||
} |
||||
|
||||
func TestCreateListenerOnlyHostSuccess(t *testing.T) { |
||||
listener, err := createListener("localhost:") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if listener.Addr().String() == "" { |
||||
t.Fatal("Fail to find available port") |
||||
} |
||||
} |
||||
|
||||
func TestCreateListenerOnlyPortSuccess(t *testing.T) { |
||||
listener, err := createListener(":8888") |
||||
if err != nil { |
||||
t.Fatal(err) |
||||
} |
||||
if listener.Addr().String() == "" { |
||||
t.Fatal("Fail to find available port") |
||||
} |
||||
} |
@ -0,0 +1,292 @@
|
||||
// +build linux
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
"path/filepath" |
||||
|
||||
cli "gopkg.in/urfave/cli.v2" |
||||
) |
||||
|
||||
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) { |
||||
app.Commands = append(app.Commands, &cli.Command{ |
||||
Name: "service", |
||||
Usage: "Manages the Argo Tunnel system service", |
||||
Subcommands: []*cli.Command{ |
||||
&cli.Command{ |
||||
Name: "install", |
||||
Usage: "Install Argo Tunnel as a system service", |
||||
Action: installLinuxService, |
||||
}, |
||||
&cli.Command{ |
||||
Name: "uninstall", |
||||
Usage: "Uninstall the Argo Tunnel service", |
||||
Action: uninstallLinuxService, |
||||
}, |
||||
}, |
||||
}) |
||||
app.Run(os.Args) |
||||
} |
||||
|
||||
const serviceConfigDir = "/etc/cloudflared" |
||||
|
||||
var systemdTemplates = []ServiceTemplate{ |
||||
{ |
||||
Path: "/etc/systemd/system/cloudflared.service", |
||||
Content: `[Unit] |
||||
Description=Argo Tunnel |
||||
After=network.target |
||||
|
||||
[Service] |
||||
TimeoutStartSec=0 |
||||
Type=notify |
||||
ExecStart={{ .Path }} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --no-autoupdate |
||||
Restart=on-failure |
||||
RestartSec=5s |
||||
|
||||
[Install] |
||||
WantedBy=multi-user.target |
||||
`, |
||||
}, |
||||
{ |
||||
Path: "/etc/systemd/system/cloudflared-update.service", |
||||
Content: `[Unit] |
||||
Description=Update Argo Tunnel |
||||
After=network.target |
||||
|
||||
[Service] |
||||
ExecStart=/bin/bash -c '{{ .Path }} update; code=$?; if [ $code -eq 64 ]; then systemctl restart cloudflared; exit 0; fi; exit $code' |
||||
`, |
||||
}, |
||||
{ |
||||
Path: "/etc/systemd/system/cloudflared-update.timer", |
||||
Content: `[Unit] |
||||
Description=Update Argo Tunnel |
||||
|
||||
[Timer] |
||||
OnUnitActiveSec=1d |
||||
|
||||
[Install] |
||||
WantedBy=timers.target |
||||
`, |
||||
}, |
||||
} |
||||
|
||||
var sysvTemplate = ServiceTemplate{ |
||||
Path: "/etc/init.d/cloudflared", |
||||
FileMode: 0755, |
||||
Content: `# For RedHat and cousins: |
||||
# chkconfig: 2345 99 01 |
||||
# description: Argo Tunnel agent |
||||
# processname: {{.Path}} |
||||
### BEGIN INIT INFO |
||||
# Provides: {{.Path}} |
||||
# Required-Start: |
||||
# Required-Stop: |
||||
# Default-Start: 2 3 4 5 |
||||
# Default-Stop: 0 1 6 |
||||
# Short-Description: Argo Tunnel |
||||
# Description: Argo Tunnel agent |
||||
### END INIT INFO |
||||
name=$(basename $(readlink -f $0)) |
||||
cmd="{{.Path}} --config /etc/cloudflared/config.yml --origincert /etc/cloudflared/cert.pem --pidfile /var/run/$name.pid --autoupdate-freq 24h0m0s" |
||||
pid_file="/var/run/$name.pid" |
||||
stdout_log="/var/log/$name.log" |
||||
stderr_log="/var/log/$name.err" |
||||
[ -e /etc/sysconfig/$name ] && . /etc/sysconfig/$name |
||||
get_pid() { |
||||
cat "$pid_file" |
||||
} |
||||
is_running() { |
||||
[ -f "$pid_file" ] && ps $(get_pid) > /dev/null 2>&1 |
||||
} |
||||
case "$1" in |
||||
start) |
||||
if is_running; then |
||||
echo "Already started" |
||||
else |
||||
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) |
||||
if is_running; then |
||||
echo -n "Stopping $name.." |
||||
kill $(get_pid) |
||||
for i in {1..10} |
||||
do |
||||
if ! is_running; then |
||||
break |
||||
fi |
||||
echo -n "." |
||||
sleep 1 |
||||
done |
||||
echo |
||||
if is_running; then |
||||
echo "Not stopped; may still be shutting down or shutdown may have failed" |
||||
exit 1 |
||||
else |
||||
echo "Stopped" |
||||
if [ -f "$pid_file" ]; then |
||||
rm "$pid_file" |
||||
fi |
||||
fi |
||||
else |
||||
echo "Not running" |
||||
fi |
||||
;; |
||||
restart) |
||||
$0 stop |
||||
if is_running; then |
||||
echo "Unable to stop, will not attempt to start" |
||||
exit 1 |
||||
fi |
||||
$0 start |
||||
;; |
||||
status) |
||||
if is_running; then |
||||
echo "Running" |
||||
else |
||||
echo "Stopped" |
||||
exit 1 |
||||
fi |
||||
;; |
||||
*) |
||||
echo "Usage: $0 {start|stop|restart|status}" |
||||
exit 1 |
||||
;; |
||||
esac |
||||
exit 0 |
||||
`, |
||||
} |
||||
|
||||
func isSystemd() bool { |
||||
if _, err := os.Stat("/run/systemd/system"); err == nil { |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func installLinuxService(c *cli.Context) error { |
||||
etPath, err := os.Executable() |
||||
if err != nil { |
||||
return fmt.Errorf("error determining executable path: %v", err) |
||||
} |
||||
templateArgs := ServiceTemplateArgs{Path: etPath} |
||||
|
||||
defaultConfigDir := filepath.Dir(c.String("config")) |
||||
defaultConfigFile := filepath.Base(c.String("config")) |
||||
if err = copyCredentials(serviceConfigDir, defaultConfigDir, defaultConfigFile, defaultCredentialFile); err != nil { |
||||
logger.WithError(err).Infof("Failed to copy user configuration. Before running the service, ensure that %s contains two files, %s and %s", |
||||
serviceConfigDir, defaultCredentialFile, defaultConfigFiles[0]) |
||||
return err |
||||
} |
||||
|
||||
switch { |
||||
case isSystemd(): |
||||
logger.Infof("Using Systemd") |
||||
return installSystemd(&templateArgs) |
||||
default: |
||||
logger.Infof("Using Sysv") |
||||
return installSysv(&templateArgs) |
||||
} |
||||
} |
||||
|
||||
func installSystemd(templateArgs *ServiceTemplateArgs) error { |
||||
for _, serviceTemplate := range systemdTemplates { |
||||
err := serviceTemplate.Generate(templateArgs) |
||||
if err != nil { |
||||
logger.WithError(err).Infof("error generating service template") |
||||
return err |
||||
} |
||||
} |
||||
if err := runCommand("systemctl", "enable", "cloudflared.service"); err != nil { |
||||
logger.WithError(err).Infof("systemctl enable cloudflared.service error") |
||||
return err |
||||
} |
||||
if err := runCommand("systemctl", "start", "cloudflared-update.timer"); err != nil { |
||||
logger.WithError(err).Infof("systemctl start cloudflared-update.timer error") |
||||
return err |
||||
} |
||||
logger.Infof("systemctl daemon-reload") |
||||
return runCommand("systemctl", "daemon-reload") |
||||
} |
||||
|
||||
func installSysv(templateArgs *ServiceTemplateArgs) error { |
||||
confPath, err := sysvTemplate.ResolvePath() |
||||
if err != nil { |
||||
logger.WithError(err).Infof("error resolving system path") |
||||
return err |
||||
} |
||||
if err := sysvTemplate.Generate(templateArgs); err != nil { |
||||
logger.WithError(err).Infof("error generating system template") |
||||
return err |
||||
} |
||||
for _, i := range [...]string{"2", "3", "4", "5"} { |
||||
if err := os.Symlink(confPath, "/etc/rc"+i+".d/S50et"); err != nil { |
||||
continue |
||||
} |
||||
} |
||||
for _, i := range [...]string{"0", "1", "6"} { |
||||
if err := os.Symlink(confPath, "/etc/rc"+i+".d/K02et"); err != nil { |
||||
continue |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func uninstallLinuxService(c *cli.Context) error { |
||||
switch { |
||||
case isSystemd(): |
||||
logger.Infof("Using Systemd") |
||||
return uninstallSystemd() |
||||
default: |
||||
logger.Infof("Using Sysv") |
||||
return uninstallSysv() |
||||
} |
||||
} |
||||
|
||||
func uninstallSystemd() error { |
||||
if err := runCommand("systemctl", "disable", "cloudflared.service"); err != nil { |
||||
logger.WithError(err).Infof("systemctl disable cloudflared.service error") |
||||
return err |
||||
} |
||||
if err := runCommand("systemctl", "stop", "cloudflared-update.timer"); err != nil { |
||||
logger.WithError(err).Infof("systemctl stop cloudflared-update.timer error") |
||||
return err |
||||
} |
||||
for _, serviceTemplate := range systemdTemplates { |
||||
if err := serviceTemplate.Remove(); err != nil { |
||||
logger.WithError(err).Infof("error removing service template") |
||||
return err |
||||
} |
||||
} |
||||
logger.Infof("Successfully uninstall cloudflared service") |
||||
return nil |
||||
} |
||||
|
||||
func uninstallSysv() error { |
||||
if err := sysvTemplate.Remove(); err != nil { |
||||
logger.WithError(err).Infof("error removing service template") |
||||
return err |
||||
} |
||||
for _, i := range [...]string{"2", "3", "4", "5"} { |
||||
if err := os.Remove("/etc/rc" + i + ".d/S50et"); err != nil { |
||||
continue |
||||
} |
||||
} |
||||
for _, i := range [...]string{"0", "1", "6"} { |
||||
if err := os.Remove("/etc/rc" + i + ".d/K02et"); err != nil { |
||||
continue |
||||
} |
||||
} |
||||
logger.Infof("Successfully uninstall cloudflared service") |
||||
return nil |
||||
} |
@ -0,0 +1,68 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
|
||||
"github.com/cloudflare/cloudflared/log" |
||||
|
||||
"github.com/rifflock/lfshook" |
||||
"github.com/sirupsen/logrus" |
||||
"gopkg.in/urfave/cli.v2" |
||||
|
||||
"github.com/mitchellh/go-homedir" |
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
var logger = log.CreateLogger() |
||||
|
||||
func configMainLogger(c *cli.Context) 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) |
||||
return nil |
||||
} |
||||
|
||||
func configProtoLogger(c *cli.Context) (*logrus.Logger, error) { |
||||
protoLogLevel, err := logrus.ParseLevel(c.String("proto-loglevel")) |
||||
if err != nil { |
||||
logger.WithError(err).Fatal("Unknown protocol logging level specified") |
||||
return nil, errors.Wrap(err, "Unknown protocol logging level specified") |
||||
} |
||||
protoLogger := logrus.New() |
||||
protoLogger.Level = protoLogLevel |
||||
return protoLogger, 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.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 |
||||
} |
@ -0,0 +1,194 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"encoding/base32" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"os/exec" |
||||
"path/filepath" |
||||
"runtime" |
||||
"syscall" |
||||
"time" |
||||
|
||||
homedir "github.com/mitchellh/go-homedir" |
||||
cli "gopkg.in/urfave/cli.v2" |
||||
) |
||||
|
||||
const baseLoginURL = "https://dash.cloudflare.com/warp" |
||||
const baseCertStoreURL = "https://login.cloudflarewarp.com" |
||||
const clientTimeout = time.Minute * 20 |
||||
|
||||
func login(c *cli.Context) error { |
||||
configPath, err := homedir.Expand(defaultConfigDirs[0]) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
ok, err := fileExists(configPath) |
||||
if !ok && err == nil { |
||||
// create config directory if doesn't already exist
|
||||
err = os.Mkdir(configPath, 0700) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
path := filepath.Join(configPath, defaultCredentialFile) |
||||
fileInfo, err := os.Stat(path) |
||||
if err == nil && fileInfo.Size() > 0 { |
||||
fmt.Fprintf(os.Stderr, `You have an existing certificate at %s which login would overwrite. |
||||
If this is intentional, please move or delete that file then run this command again. |
||||
`, path) |
||||
return nil |
||||
} |
||||
if err != nil && err.(*os.PathError).Err != syscall.ENOENT { |
||||
return err |
||||
} |
||||
|
||||
// for local debugging
|
||||
baseURL := baseCertStoreURL |
||||
if c.IsSet("url") { |
||||
baseURL = c.String("url") |
||||
} |
||||
// Generate a random post URL
|
||||
certURL := baseURL + generateRandomPath() |
||||
loginURL, err := url.Parse(baseLoginURL) |
||||
if err != nil { |
||||
// shouldn't happen, URL is hardcoded
|
||||
return err |
||||
} |
||||
loginURL.RawQuery = "callback=" + url.QueryEscape(certURL) |
||||
|
||||
err = open(loginURL.String()) |
||||
if err != nil { |
||||
fmt.Fprintf(os.Stderr, `Please open the following URL and log in with your Cloudflare account: |
||||
|
||||
%s |
||||
|
||||
Leave cloudflared running to install the certificate automatically. |
||||
`, loginURL.String()) |
||||
} else { |
||||
fmt.Fprintf(os.Stderr, `A browser window should have opened at the following URL: |
||||
|
||||
%s |
||||
|
||||
If the browser failed to open, open it yourself and visit the URL above. |
||||
|
||||
`, loginURL.String()) |
||||
} |
||||
|
||||
if download(certURL, path) { |
||||
fmt.Fprintf(os.Stderr, `You have successfully logged in. |
||||
If you wish to copy your credentials to a server, they have been saved to: |
||||
%s |
||||
`, path) |
||||
} else { |
||||
fmt.Fprintf(os.Stderr, `Failed to write the certificate due to the following error: |
||||
%v |
||||
|
||||
Your browser will download the certificate instead. You will have to manually |
||||
copy it to the following path: |
||||
|
||||
%s |
||||
|
||||
`, err, path) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// generateRandomPath generates a random URL to associate with the certificate.
|
||||
func generateRandomPath() string { |
||||
randomBytes := make([]byte, 40) |
||||
_, err := rand.Read(randomBytes) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return "/" + base32.StdEncoding.EncodeToString(randomBytes) |
||||
} |
||||
|
||||
// open opens the specified URL in the default browser of the user.
|
||||
func open(url string) error { |
||||
var cmd string |
||||
var args []string |
||||
|
||||
switch runtime.GOOS { |
||||
case "windows": |
||||
cmd = "cmd" |
||||
args = []string{"/c", "start"} |
||||
case "darwin": |
||||
cmd = "open" |
||||
default: // "linux", "freebsd", "openbsd", "netbsd"
|
||||
cmd = "xdg-open" |
||||
} |
||||
args = append(args, url) |
||||
return exec.Command(cmd, args...).Start() |
||||
} |
||||
|
||||
func download(certURL, filePath string) bool { |
||||
client := &http.Client{Timeout: clientTimeout} |
||||
// attempt a (long-running) certificate get
|
||||
for i := 0; i < 20; i++ { |
||||
ok, err := tryDownload(client, certURL, filePath) |
||||
if ok { |
||||
putSuccess(client, certURL) |
||||
return true |
||||
} |
||||
if err != nil { |
||||
logger.WithError(err).Error("Error fetching certificate") |
||||
return false |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func tryDownload(client *http.Client, certURL, filePath string) (ok bool, err error) { |
||||
resp, err := client.Get(certURL) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
defer resp.Body.Close() |
||||
if resp.StatusCode == 404 { |
||||
return false, nil |
||||
} |
||||
if resp.StatusCode != 200 { |
||||
return false, fmt.Errorf("Unexpected HTTP error code %d", resp.StatusCode) |
||||
} |
||||
if resp.Header.Get("Content-Type") != "application/x-pem-file" { |
||||
return false, fmt.Errorf("Unexpected content type %s", resp.Header.Get("Content-Type")) |
||||
} |
||||
// write response
|
||||
file, err := os.Create(filePath) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
defer file.Close() |
||||
written, err := io.Copy(file, resp.Body) |
||||
switch { |
||||
case err != nil: |
||||
return false, err |
||||
case resp.ContentLength != written && resp.ContentLength != -1: |
||||
return false, fmt.Errorf("Short read (%d bytes) from server while writing certificate", written) |
||||
default: |
||||
return true, nil |
||||
} |
||||
} |
||||
|
||||
func putSuccess(client *http.Client, certURL string) { |
||||
// indicate success to the relay server
|
||||
req, err := http.NewRequest("PUT", certURL+"/ok", nil) |
||||
if err != nil { |
||||
logger.WithError(err).Error("HTTP request error") |
||||
return |
||||
} |
||||
resp, err := client.Do(req) |
||||
if err != nil { |
||||
logger.WithError(err).Error("HTTP error") |
||||
return |
||||
} |
||||
resp.Body.Close() |
||||
if resp.StatusCode != 200 { |
||||
logger.Errorf("Unexpected HTTP error code %d", resp.StatusCode) |
||||
} |
||||
} |
@ -0,0 +1,190 @@
|
||||
// +build darwin
|
||||
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
|
||||
"gopkg.in/urfave/cli.v2" |
||||
|
||||
"github.com/pkg/errors" |
||||
) |
||||
|
||||
const ( |
||||
launchdIdentifier = "com.cloudflare.cloudflared" |
||||
) |
||||
|
||||
func runApp(app *cli.App, shutdownC, graceShutdownC chan struct{}) { |
||||
app.Commands = append(app.Commands, &cli.Command{ |
||||
Name |