parent
e8c621a648
commit
d06fc520c7
@ -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: "service",
|
||||
Usage: "Manages the Argo Tunnel launch agent",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "Install Argo Tunnel as an user launch agent",
|
||||
Action: installLaunchd,
|
||||
},
|
||||
{
|
||||
Name: "uninstall",
|
||||
Usage: "Uninstall the Argo Tunnel launch agent",
|
||||
Action: uninstallLaunchd,
|
||||
},
|
||||
},
|
||||
})
|
||||
app.Run(os.Args)
|
||||
}
|
||||
|
||||
func newLaunchdTemplate(installPath, stdoutPath, stderrPath string) *ServiceTemplate {
|
||||
return &ServiceTemplate{
|
||||
Path: installPath,
|
||||
Content: fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>%s</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>{{ .Path }}</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>StandardOutPath</key>
|
||||
<string>%s</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>%s</string>
|
||||
<key>KeepAlive</key>
|
||||
<dict>
|
||||
<key>SuccessfulExit</key>
|
||||
<false/>
|
||||
</dict>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>20</integer>
|
||||
</dict>
|
||||
</plist>`, launchdIdentifier, stdoutPath, stderrPath),
|
||||
}
|
||||
}
|
||||
|
||||
func isRootUser() bool {
|
||||
return os.Geteuid() == 0
|
||||
}
|
||||
|
||||
func installPath() (string, error) {
|
||||
// User is root, use /Library/LaunchDaemons instead of home directory
|
||||
if isRootUser() {
|
||||
return fmt.Sprintf("/Library/LaunchDaemons/%s.plist", launchdIdentifier), nil
|
||||
}
|
||||
userHomeDir, err := userHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s/Library/LaunchAgents/%s.plist", userHomeDir, launchdIdentifier), nil
|
||||
}
|
||||
|
||||
func stdoutPath() (string, error) {
|
||||
if isRootUser() {
|
||||
return fmt.Sprintf("/Library/Logs/%s.out.log", launchdIdentifier), nil
|
||||
}
|
||||
userHomeDir, err := userHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s/Library/Logs/%s.out.log", userHomeDir, launchdIdentifier), nil
|
||||
}
|
||||
|
||||
func stderrPath() (string, error) {
|
||||
if isRootUser() {
|
||||
return fmt.Sprintf("/Library/Logs/%s.err.log", launchdIdentifier), nil
|
||||
}
|
||||
userHomeDir, err := userHomeDir()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%s/Library/Logs/%s.err.log", userHomeDir, launchdIdentifier), nil
|
||||
}
|
||||
|
||||
func installLaunchd(c *cli.Context) error {
|
||||
if isRootUser() {
|
||||
logger.Infof("Installing Argo Tunnel client as a system launch daemon. " +
|
||||
"Argo Tunnel client will run at boot")
|
||||
} else {
|
||||
logger.Infof("Installing Argo Tunnel client as an user launch agent. " +
|
||||
"Note that Argo Tunnel client will only run when the user is logged in. " +
|
||||
"If you want to run Argo Tunnel client at boot, install with root permission. " +
|
||||
"For more information, visit https://developers.cloudflare.com/argo-tunnel/reference/service/")
|
||||
}
|
||||
etPath, err := os.Executable()
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("Error determining executable path")
|
||||
return fmt.Errorf("Error determining executable path: %v", err)
|
||||
}
|
||||
installPath, err := installPath()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Error determining install path")
|
||||
}
|
||||
stdoutPath, err := stdoutPath()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error determining stdout path")
|
||||
}
|
||||
stderrPath, err := stderrPath()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error determining stderr path")
|
||||
}
|
||||
launchdTemplate := newLaunchdTemplate(installPath, stdoutPath, stderrPath)
|
||||
if err != nil {
|
||||
logger.WithError(err).Errorf("error creating launchd template")
|
||||
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")
|
||||
return err
|
||||
}
|
||||
plistPath, err := launchdTemplate.ResolvePath()
|
||||
if err != nil {
|
||||
logger.WithError(err).Infof("error resolving launchd template path")
|
||||
return err
|
||||
}
|
||||
|
||||
|