mirror of
https://github.com/cloudflare/cloudflared.git
synced 2026-08-07 23:31:56 +00:00
Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| db5c6f2556 | |||
| 9dd7898792 | |||
| 92b3e8311f | |||
| 4a7763e497 | |||
| 9767ba1853 | |||
| 2c75326021 | |||
| 9023daba24 | |||
| 89d0e45d62 | |||
| a34099724e | |||
| 8c5498fad1 | |||
| 2c746b3361 | |||
| 954cd6adca | |||
| 8432735867 | |||
| d67fbbf94f |
+15
@@ -1,5 +1,20 @@
|
||||
**Experimental**: This is a new format for release notes. The format and availability is subject to change.
|
||||
|
||||
## 2021.3.2
|
||||
|
||||
### New Features
|
||||
|
||||
- It is now possible to obtain more detailed information about the cloudflared connectors to Cloudflare Edge via
|
||||
`cloudflared tunnel info <name/uuid>`. It is possible to sort the output as well as output in different formats,
|
||||
such as: `cloudflared tunnel info --sort-by version --invert-sort --output json <name/uuid>`.
|
||||
You can obtain more information via `cloudflared tunnel info --help`.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- Don't look for configuration file in default paths when `--config FILE` flag is present after `tunnel` subcommand.
|
||||
- cloudflared access token command now functions correctly with the new token-per-app change from 2021.3.0.
|
||||
|
||||
|
||||
## 2021.3.0
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
2021.3.2
|
||||
- 2021-03-23 TUN-4042: Capture cloudflared output to debug component tests
|
||||
- 2021-03-23 Publish changelog for 2021.3.2
|
||||
- 2021-03-16 TUN-4089: Address flakiness in component tests for termination
|
||||
- 2021-03-16 TUN-4060: Fix Go Vet warnings (new with go 1.16) where t.Fatalf is called from a test goroutine
|
||||
- 2021-03-16 TUN-4091: Use flaky decorator to rerun reconnect component tests when they fail
|
||||
- 2021-03-12 TUN-4081: Update log severities to use Zerolog's levels
|
||||
- 2021-03-16 TUN-4094: Don't read configuration file for access commands
|
||||
- 2021-03-15 TUN-3993: New `cloudflared tunnel info` to obtain details about the active connectors for a tunnel
|
||||
- 2021-03-17 TUN-3715: Apply input source to the correct context
|
||||
- 2021-03-17 AUTH-3394: Ensure scheme on token command
|
||||
- 2021-03-18 TUN-4096: Reduce tunnel not connected assertion backoff to address flaky termination tests
|
||||
- 2021-03-19 TUN-3998: Allow to cleanup the connections of a tunnel limited to a single client
|
||||
- 2021-02-04 TUN-3715: Only read config file once, right before invoking the command
|
||||
|
||||
2021.3.1
|
||||
- 2021-03-11 TUN-4051: Add component-tests to test graceful shutdown
|
||||
- 2021-03-12 TUN-4052: Add component tests to assert service mode behavior
|
||||
|
||||
@@ -81,7 +81,8 @@ func TestStartServer(t *testing.T) {
|
||||
go func() {
|
||||
err := Serve(wsConn, listener, shutdownC, options)
|
||||
if err != nil {
|
||||
t.Fatalf("Error running server: %v", err)
|
||||
t.Errorf("Error running server: %v", err)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ Add to your {{.Home}}/.ssh/config:
|
||||
|
||||
Host {{.Hostname}}
|
||||
{{- if .ShortLivedCerts}}
|
||||
ProxyCommand bash -c '{{.Cloudflared}} access ssh-gen --hostname %h; ssh -tt %r@cfpipe-{{.Hostname}} >&2 <&1'
|
||||
ProxyCommand bash -c '{{.Cloudflared}} access ssh-gen --hostname %h; ssh -tt %r@cfpipe-{{.Hostname}} >&2 <&1'
|
||||
|
||||
Host cfpipe-{{.Hostname}}
|
||||
HostName {{.Hostname}}
|
||||
@@ -77,30 +77,24 @@ func Commands() []*cli.Command {
|
||||
Aliases: []string{"forward"},
|
||||
Category: "Access",
|
||||
Usage: "access <subcommand>",
|
||||
Description: `Cloudflare Access protects internal resources by securing, authenticating and monitoring access
|
||||
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
|
||||
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
|
||||
Description: `Cloudflare Access protects internal resources by securing, authenticating and monitoring access
|
||||
per-user and by application. With Cloudflare Access, only authenticated users with the required permissions are
|
||||
able to reach sensitive resources. The commands provided here allow you to interact with Access protected
|
||||
applications from the command line.`,
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "login",
|
||||
Action: cliutil.ErrorHandler(login),
|
||||
Action: cliutil.Action(login),
|
||||
Usage: "login <url of access application>",
|
||||
Description: `The login subcommand initiates an authentication flow with your identity provider.
|
||||
The subcommand will launch a browser. For headless systems, a url is provided.
|
||||
Once authenticated with your identity provider, the login command will generate a JSON Web Token (JWT)
|
||||
scoped to your identity, the application you intend to reach, and valid for a session duration set by your
|
||||
scoped to your identity, the application you intend to reach, and valid for a session duration set by your
|
||||
administrator. cloudflared stores the token in local storage.`,
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "url",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "curl",
|
||||
Action: cliutil.ErrorHandler(curl),
|
||||
Action: cliutil.Action(curl),
|
||||
Usage: "curl [--allow-request, -ar] <url> [<curl args>...]",
|
||||
Description: `The curl subcommand wraps curl and automatically injects the JWT into a cf-access-token
|
||||
header when using curl to reach an application behind Access.`,
|
||||
@@ -109,7 +103,7 @@ func Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "token",
|
||||
Action: cliutil.ErrorHandler(generateToken),
|
||||
Action: cliutil.Action(generateToken),
|
||||
Usage: "token -app=<url of access application>",
|
||||
ArgsUsage: "url of Access application",
|
||||
Description: `The token subcommand produces a JWT which can be used to authenticate requests.`,
|
||||
@@ -121,7 +115,7 @@ func Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "tcp",
|
||||
Action: cliutil.ErrorHandler(ssh),
|
||||
Action: cliutil.Action(ssh),
|
||||
Aliases: []string{"rdp", "ssh", "smb"},
|
||||
Usage: "",
|
||||
ArgsUsage: "",
|
||||
@@ -164,7 +158,7 @@ func Commands() []*cli.Command {
|
||||
&cli.StringFlag{
|
||||
Name: logger.LogSSHLevelFlag,
|
||||
Aliases: []string{"loglevel"}, //added to match the tunnel side
|
||||
Usage: "Application logging level {fatal, error, info, debug}. ",
|
||||
Usage: "Application logging level {debug, info, warn, error, fatal}. ",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: sshConnectTo,
|
||||
@@ -175,7 +169,7 @@ func Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "ssh-config",
|
||||
Action: cliutil.ErrorHandler(sshConfig),
|
||||
Action: cliutil.Action(sshConfig),
|
||||
Usage: "",
|
||||
Description: `Prints an example configuration ~/.ssh/config`,
|
||||
Flags: []cli.Flag{
|
||||
@@ -191,7 +185,7 @@ func Commands() []*cli.Command {
|
||||
},
|
||||
{
|
||||
Name: "ssh-gen",
|
||||
Action: cliutil.ErrorHandler(sshGen),
|
||||
Action: cliutil.Action(sshGen),
|
||||
Usage: "",
|
||||
Description: `Generates a short lived certificate for given hostname`,
|
||||
Flags: []cli.Flag{
|
||||
@@ -296,7 +290,6 @@ func curl(c *cli.Context) error {
|
||||
return run("curl", cmdArgs...)
|
||||
}
|
||||
|
||||
|
||||
// run kicks off a shell task and pipe the results to the respective std pipes
|
||||
func run(cmd string, args ...string) error {
|
||||
c := exec.Command(cmd, args...)
|
||||
@@ -323,7 +316,7 @@ func generateToken(c *cli.Context) error {
|
||||
if err := raven.SetDSN(sentryDSN); err != nil {
|
||||
return err
|
||||
}
|
||||
appURL, err := url.Parse(c.String("app"))
|
||||
appURL, err := url.Parse(ensureURLScheme(c.String("app")))
|
||||
if err != nil || c.NumFlags() < 1 {
|
||||
fmt.Fprintln(os.Stderr, "Please provide a url.")
|
||||
return err
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
func RemovedCommand(name string) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: name,
|
||||
Action: ErrorHandler(func(context *cli.Context) error {
|
||||
Action: func(context *cli.Context) error {
|
||||
return cli.Exit(
|
||||
fmt.Sprintf("%s command is no longer supported by cloudflared. Consult Argo Tunnel documentation for possible alternative solutions.", name),
|
||||
-1,
|
||||
)
|
||||
}),
|
||||
},
|
||||
Description: fmt.Sprintf("%s is deprecated", name),
|
||||
Hidden: true,
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cliutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
@@ -21,7 +22,7 @@ func UsageError(format string, args ...interface{}) error {
|
||||
}
|
||||
|
||||
// Ensures exit with error code if actionFunc returns an error
|
||||
func ErrorHandler(actionFunc cli.ActionFunc) cli.ActionFunc {
|
||||
func WithErrorHandler(actionFunc cli.ActionFunc) cli.ActionFunc {
|
||||
return func(ctx *cli.Context) error {
|
||||
err := actionFunc(ctx)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package cliutil
|
||||
|
||||
import (
|
||||
"github.com/urfave/cli/v2"
|
||||
"github.com/urfave/cli/v2/altsrc"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
)
|
||||
|
||||
func Action(actionFunc cli.ActionFunc) cli.ActionFunc {
|
||||
return WithErrorHandler(actionFunc)
|
||||
}
|
||||
|
||||
func ConfiguredAction(actionFunc cli.ActionFunc) cli.ActionFunc {
|
||||
return WithErrorHandler(func(c *cli.Context) error {
|
||||
if err := setFlagsFromConfigFile(c); err != nil {
|
||||
return err
|
||||
}
|
||||
return actionFunc(c)
|
||||
})
|
||||
}
|
||||
|
||||
func setFlagsFromConfigFile(c *cli.Context) error {
|
||||
const errorExitCode = 1
|
||||
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
||||
inputSource, err := config.ReadConfigFile(c, log)
|
||||
if err != nil {
|
||||
if err == config.ErrNoConfigFile {
|
||||
return nil
|
||||
}
|
||||
return cli.Exit(err, errorExitCode)
|
||||
}
|
||||
|
||||
if err := applyConfig(c, inputSource); err != nil {
|
||||
return cli.Exit(err, errorExitCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyConfig(c *cli.Context, inputSource altsrc.InputSourceContext) error {
|
||||
for _, context := range c.Lineage() {
|
||||
if context.Command == nil {
|
||||
// we've reached the placeholder root context not associated with the app
|
||||
break
|
||||
}
|
||||
targetFlags := context.Command.Flags
|
||||
if context.Command.Name == "" {
|
||||
// commands that define child subcommands are executed as if they were an app
|
||||
targetFlags = context.App.Flags
|
||||
}
|
||||
if err := altsrc.ApplyInputSourceValues(context, inputSource, targetFlags); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -24,7 +24,7 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "Install Argo Tunnel as a system service",
|
||||
Action: cliutil.ErrorHandler(installLinuxService),
|
||||
Action: cliutil.ConfiguredAction(installLinuxService),
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "legacy",
|
||||
@@ -35,7 +35,7 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||
{
|
||||
Name: "uninstall",
|
||||
Usage: "Uninstall the Argo Tunnel service",
|
||||
Action: cliutil.ErrorHandler(uninstallLinuxService),
|
||||
Action: cliutil.ConfiguredAction(uninstallLinuxService),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -25,12 +25,12 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "Install Argo Tunnel as an user launch agent",
|
||||
Action: cliutil.ErrorHandler(installLaunchd),
|
||||
Action: cliutil.ConfiguredAction(installLaunchd),
|
||||
},
|
||||
{
|
||||
Name: "uninstall",
|
||||
Usage: "Uninstall the Argo Tunnel launch agent",
|
||||
Action: cliutil.ErrorHandler(uninstallLaunchd),
|
||||
Action: cliutil.ConfiguredAction(uninstallLaunchd),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/access"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/proxydns"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/tunnel"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
"github.com/cloudflare/cloudflared/metrics"
|
||||
"github.com/cloudflare/cloudflared/overwatch"
|
||||
@@ -77,7 +77,6 @@ func main() {
|
||||
See https://developers.cloudflare.com/argo-tunnel/ for more in-depth documentation.`
|
||||
app.Flags = flags()
|
||||
app.Action = action(graceShutdownC)
|
||||
app.Before = tunnel.SetFlagsFromConfigFile
|
||||
app.Commands = commands(cli.ShowVersion)
|
||||
|
||||
tunnel.Init(Version, graceShutdownC) // we need this to support the tunnel sub command...
|
||||
@@ -90,7 +89,7 @@ func commands(version func(c *cli.Context)) []*cli.Command {
|
||||
cmds := []*cli.Command{
|
||||
{
|
||||
Name: "update",
|
||||
Action: cliutil.ErrorHandler(updater.Update),
|
||||
Action: cliutil.ConfiguredAction(updater.Update),
|
||||
Usage: "Update the agent if a new version exists",
|
||||
Flags: []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
@@ -145,7 +144,7 @@ func isEmptyInvocation(c *cli.Context) bool {
|
||||
}
|
||||
|
||||
func action(graceShutdownC chan struct{}) cli.ActionFunc {
|
||||
return cliutil.ErrorHandler(func(c *cli.Context) (err error) {
|
||||
return cliutil.ConfiguredAction(func(c *cli.Context) (err error) {
|
||||
if isEmptyInvocation(c) {
|
||||
return handleServiceMode(c, graceShutdownC)
|
||||
}
|
||||
|
||||
@@ -17,8 +17,9 @@ import (
|
||||
func Command(hidden bool) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "proxy-dns",
|
||||
Action: cliutil.ErrorHandler(Run),
|
||||
Usage: "Run a DNS over HTTPS proxy server.",
|
||||
Action: cliutil.ConfiguredAction(Run),
|
||||
|
||||
Usage: "Run a DNS over HTTPS proxy server.",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "metrics",
|
||||
@@ -111,5 +112,3 @@ func Run(c *cli.Context) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime/trace"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -75,8 +74,8 @@ const (
|
||||
// uiFlag is to enable launching cloudflared in interactive UI mode
|
||||
uiFlag = "ui"
|
||||
|
||||
debugLevelWarning = "At debug level, request URL, method, protocol, content legnth and header will be logged. " +
|
||||
"Response status, content length and header will also be logged in debug level."
|
||||
debugLevelWarning = "At debug level cloudflared will log request URL, method, protocol, content length, as well as, all request and response headers. " +
|
||||
"This can expose sensitive information in your logs."
|
||||
|
||||
LogFieldCommand = "command"
|
||||
LogFieldExpandedPath = "expandedPath"
|
||||
@@ -101,6 +100,7 @@ func Commands() []*cli.Command {
|
||||
buildRouteCommand(),
|
||||
buildRunCommand(),
|
||||
buildListCommand(),
|
||||
buildInfoCommand(),
|
||||
buildIngressSubcommand(),
|
||||
buildDeleteCommand(),
|
||||
buildCleanupCommand(),
|
||||
@@ -120,8 +120,7 @@ func Commands() []*cli.Command {
|
||||
func buildTunnelCommand(subcommands []*cli.Command) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "tunnel",
|
||||
Action: cliutil.ErrorHandler(TunnelCommand),
|
||||
Before: SetFlagsFromConfigFile,
|
||||
Action: cliutil.ConfiguredAction(TunnelCommand),
|
||||
Category: "Tunnel",
|
||||
Usage: "Make a locally-running web service accessible over the internet using Argo Tunnel.",
|
||||
ArgsUsage: " ",
|
||||
@@ -369,26 +368,6 @@ func StartServer(
|
||||
return waitToShutdown(&wg, cancel, errC, graceShutdownC, c.Duration("grace-period"), log)
|
||||
}
|
||||
|
||||
func SetFlagsFromConfigFile(c *cli.Context) error {
|
||||
const exitCode = 1
|
||||
log := logger.CreateLoggerFromContext(c, logger.EnableTerminalLog)
|
||||
inputSource, err := config.ReadConfigFile(c, log)
|
||||
if err != nil {
|
||||
if err == config.ErrNoConfigFile {
|
||||
return nil
|
||||
}
|
||||
return cli.Exit(err, exitCode)
|
||||
}
|
||||
targetFlags := c.Command.Flags
|
||||
if c.Command.Name == "" {
|
||||
targetFlags = c.App.Flags
|
||||
}
|
||||
if err := altsrc.ApplyInputSourceValues(c, inputSource, targetFlags); err != nil {
|
||||
return cli.Exit(err, exitCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func waitToShutdown(wg *sync.WaitGroup,
|
||||
cancelServerContext func(),
|
||||
errC <-chan error,
|
||||
@@ -477,33 +456,6 @@ func addPortIfMissing(uri *url.URL, port int) string {
|
||||
return fmt.Sprintf("%s:%d", uri.Hostname(), port)
|
||||
}
|
||||
|
||||
// appendFlags will append extra flags to a slice of flags.
|
||||
//
|
||||
// The cli package will panic if two flags exist with the same name,
|
||||
// so if extraFlags contains a flag that was already defined, modify the
|
||||
// original flags to use the extra version.
|
||||
func appendFlags(flags []cli.Flag, extraFlags ...cli.Flag) []cli.Flag {
|
||||
for _, extra := range extraFlags {
|
||||
var found bool
|
||||
|
||||
// Check if an extra flag overrides an existing flag.
|
||||
for i, flag := range flags {
|
||||
if reflect.DeepEqual(extra.Names(), flag.Names()) {
|
||||
flags[i] = extra
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Append the extra flag if it has nothing to override.
|
||||
if !found {
|
||||
flags = append(flags, extra)
|
||||
}
|
||||
}
|
||||
|
||||
return flags
|
||||
}
|
||||
|
||||
func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
flags := configureCloudflaredFlags(shouldHide)
|
||||
flags = append(flags, configureProxyFlags(shouldHide)...)
|
||||
@@ -513,7 +465,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
credentialsFileFlag,
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: "is-autoupdated",
|
||||
Usage: "Signal the new process that Argo Tunnel client has been autoupdated",
|
||||
Usage: "Signal the new process that Argo Tunnel connector has been autoupdated",
|
||||
Value: false,
|
||||
Hidden: true,
|
||||
}),
|
||||
@@ -652,6 +604,7 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||
Aliases: []string{"n"},
|
||||
EnvVars: []string{"TUNNEL_NAME"},
|
||||
Usage: "Stable name to identify the tunnel. Using this flag will create, route and run a tunnel. For production usage, execute each command separately",
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||
Name: uiFlag,
|
||||
@@ -920,7 +873,7 @@ func configureLoggingFlags(shouldHide bool) []cli.Flag {
|
||||
altsrc.NewStringFlag(&cli.StringFlag{
|
||||
Name: logger.LogLevelFlag,
|
||||
Value: "info",
|
||||
Usage: "Application logging level {fatal, error, info, debug}. " + debugLevelWarning,
|
||||
Usage: "Application logging level {debug, info, warn, error, fatal}. " + debugLevelWarning,
|
||||
EnvVars: []string{"TUNNEL_LOGLEVEL"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
@@ -928,7 +881,7 @@ func configureLoggingFlags(shouldHide bool) []cli.Flag {
|
||||
Name: logger.LogTransportLevelFlag,
|
||||
Aliases: []string{"proto-loglevel"}, // This flag used to be called proto-loglevel
|
||||
Value: "info",
|
||||
Usage: "Transport logging level(previously called protocol logging level) {fatal, error, info, debug}",
|
||||
Usage: "Transport logging level(previously called protocol logging level) {debug, info, warn, error, fatal}",
|
||||
EnvVars: []string{"TUNNEL_PROTO_LOGLEVEL", "TUNNEL_TRANSPORT_LOGLEVEL"},
|
||||
Hidden: shouldHide,
|
||||
}),
|
||||
|
||||
@@ -199,9 +199,9 @@ func prepareTunnelConfig(
|
||||
if isNamedTunnel {
|
||||
clientUUID, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, ingress.Ingress{}, errors.Wrap(err, "can't generate clientUUID")
|
||||
return nil, ingress.Ingress{}, errors.Wrap(err, "can't generate connector UUID")
|
||||
}
|
||||
log.Info().Msgf("Generated Client ID: %s", clientUUID)
|
||||
log.Info().Msgf("Generated Connector ID: %s", clientUUID)
|
||||
features := append(c.StringSlice("features"), origin.FeatureSerializedHeaders)
|
||||
namedTunnel.Client = tunnelpogs.ClientInfo{
|
||||
ClientID: clientUUID[:],
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package tunnel
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
Connectors []*tunnelstore.ActiveClient `json:"conns"`
|
||||
}
|
||||
@@ -45,7 +45,7 @@ func buildIngressSubcommand() *cli.Command {
|
||||
func buildValidateIngressCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "validate",
|
||||
Action: cliutil.ErrorHandler(validateIngressCommand),
|
||||
Action: cliutil.ConfiguredAction(validateIngressCommand),
|
||||
Usage: "Validate the ingress configuration ",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] ingress validate",
|
||||
Description: "Validates the configuration file, ensuring your ingress rules are OK.",
|
||||
@@ -55,7 +55,7 @@ func buildValidateIngressCommand() *cli.Command {
|
||||
func buildTestURLCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "rule",
|
||||
Action: cliutil.ErrorHandler(testURLCommand),
|
||||
Action: cliutil.ConfiguredAction(testURLCommand),
|
||||
Usage: "Check which ingress rule matches a given request URL",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] ingress rule URL",
|
||||
ArgsUsage: "URL",
|
||||
|
||||
@@ -26,16 +26,10 @@ const (
|
||||
func buildLoginSubcommand(hidden bool) *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "login",
|
||||
Action: cliutil.ErrorHandler(login),
|
||||
Action: cliutil.ConfiguredAction(login),
|
||||
Usage: "Generate a configuration file with your login details",
|
||||
ArgsUsage: " ",
|
||||
Flags: []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "url",
|
||||
Hidden: true,
|
||||
},
|
||||
},
|
||||
Hidden: hidden,
|
||||
Hidden: hidden,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,7 +224,7 @@ func (sc *subcommandContext) delete(tunnelIDs []uuid.UUID) error {
|
||||
return fmt.Errorf("Tunnel %s has already been deleted", tunnel.ID)
|
||||
}
|
||||
if forceFlagSet {
|
||||
if err := client.CleanupConnections(tunnel.ID); err != nil {
|
||||
if err := client.CleanupConnections(tunnel.ID, tunnelstore.NewCleanupParams()); err != nil {
|
||||
return errors.Wrapf(err, "Error cleaning up connections for tunnel %s", tunnel.ID)
|
||||
}
|
||||
}
|
||||
@@ -276,13 +276,24 @@ func (sc *subcommandContext) run(tunnelID uuid.UUID) error {
|
||||
}
|
||||
|
||||
func (sc *subcommandContext) cleanupConnections(tunnelIDs []uuid.UUID) error {
|
||||
params := tunnelstore.NewCleanupParams()
|
||||
extraLog := ""
|
||||
if connector := sc.c.String("connector-id"); connector != "" {
|
||||
connectorID, err := uuid.Parse(connector)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "%s is not a valid client ID (must be a UUID)", connector)
|
||||
}
|
||||
params.ForClient(connectorID)
|
||||
extraLog = fmt.Sprintf(" for connector-id %s", connectorID.String())
|
||||
}
|
||||
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, tunnelID := range tunnelIDs {
|
||||
sc.log.Info().Msgf("Cleanup connection for tunnel %s", tunnelID)
|
||||
if err := client.CleanupConnections(tunnelID); err != nil {
|
||||
sc.log.Info().Msgf("Cleanup connection for tunnel %s%s", tunnelID, extraLog)
|
||||
if err := client.CleanupConnections(tunnelID, params); err != nil {
|
||||
sc.log.Error().Msgf("Error cleaning up connections for tunnel %v, error :%v", tunnelID, err)
|
||||
}
|
||||
}
|
||||
@@ -343,6 +354,12 @@ func (sc *subcommandContext) findID(input string) (uuid.UUID, error) {
|
||||
// one Tunnelstore API call.
|
||||
func (sc *subcommandContext) findIDs(inputs []string) ([]uuid.UUID, error) {
|
||||
|
||||
// Shortcut without Tunnelstore call if we find that all inputs are already UUIDs.
|
||||
uuids, err := convertNamesToUuids(inputs, make(map[string]uuid.UUID))
|
||||
if err == nil {
|
||||
return uuids, nil
|
||||
}
|
||||
|
||||
// First, look up all tunnels the user has
|
||||
filter := tunnelstore.NewFilter()
|
||||
filter.NoDeleted()
|
||||
@@ -362,7 +379,10 @@ func findIDs(tunnels []*tunnelstore.Tunnel, inputs []string) ([]uuid.UUID, error
|
||||
nameToID[tunnel.Name] = tunnel.ID
|
||||
}
|
||||
|
||||
// For each input, try to find the tunnel ID.
|
||||
return convertNamesToUuids(inputs, nameToID)
|
||||
}
|
||||
|
||||
func convertNamesToUuids(inputs []string, nameToID map[string]uuid.UUID) ([]uuid.UUID, error) {
|
||||
tunnelIDs := make([]uuid.UUID, len(inputs))
|
||||
var badInputs []string
|
||||
for i, input := range inputs {
|
||||
|
||||
@@ -4,11 +4,12 @@ import (
|
||||
"encoding/base64"
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
|
||||
"github.com/cloudflare/cloudflared/connection"
|
||||
"github.com/cloudflare/cloudflared/tunnelstore"
|
||||
"github.com/google/uuid"
|
||||
@@ -260,7 +261,7 @@ func (d *deleteMockTunnelStore) DeleteTunnel(tunnelID uuid.UUID) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *deleteMockTunnelStore) CleanupConnections(tunnelID uuid.UUID) error {
|
||||
func (d *deleteMockTunnelStore) CleanupConnections(tunnelID uuid.UUID, _ *tunnelstore.CleanupParams) error {
|
||||
tunnel, ok := d.mockTunnels[tunnelID]
|
||||
if !ok {
|
||||
return fmt.Errorf("Couldn't find tunnel: %v", tunnelID)
|
||||
|
||||
@@ -29,9 +29,10 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
allSortByOptions = "name, id, createdAt, deletedAt, numConnections"
|
||||
CredFileFlagAlias = "cred-file"
|
||||
CredFileFlag = "credentials-file"
|
||||
allSortByOptions = "name, id, createdAt, deletedAt, numConnections"
|
||||
connsSortByOptions = "id, startedAt, numConnections, version"
|
||||
CredFileFlagAlias = "cred-file"
|
||||
CredFileFlag = "credentials-file"
|
||||
|
||||
LogFieldTunnelID = "tunnelID"
|
||||
)
|
||||
@@ -64,11 +65,11 @@ var (
|
||||
Aliases: []string{"rd"},
|
||||
Usage: "Include connections that have recently disconnected in the list",
|
||||
}
|
||||
outputFormatFlag = altsrc.NewStringFlag(&cli.StringFlag{
|
||||
outputFormatFlag = &cli.StringFlag{
|
||||
Name: "output",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "Render output using given `FORMAT`. Valid options are 'json' or 'yaml'",
|
||||
})
|
||||
}
|
||||
sortByFlag = &cli.StringFlag{
|
||||
Name: "sort-by",
|
||||
Value: "name",
|
||||
@@ -114,12 +115,29 @@ var (
|
||||
EnvVars: []string{"TUNNEL_TRANSPORT_PROTOCOL"},
|
||||
Hidden: true,
|
||||
})
|
||||
sortInfoByFlag = &cli.StringFlag{
|
||||
Name: "sort-by",
|
||||
Value: "createdAt",
|
||||
Usage: fmt.Sprintf("Sorts the list of connections of a tunnel by the given field. Valid options are {%s}", connsSortByOptions),
|
||||
EnvVars: []string{"TUNNEL_INFO_SORT_BY"},
|
||||
}
|
||||
invertInfoSortFlag = &cli.BoolFlag{
|
||||
Name: "invert-sort",
|
||||
Usage: "Inverts the sort order of the tunnel info.",
|
||||
EnvVars: []string{"TUNNEL_INFO_INVERT_SORT"},
|
||||
}
|
||||
cleanupClientFlag = &cli.StringFlag{
|
||||
Name: "connector-id",
|
||||
Aliases: []string{"c"},
|
||||
Usage: `Constraints the cleanup to stop the connections of a single Connector (by its ID). You can find the various Connectors (and their IDs) currently connected to your tunnel via 'cloudflared tunnel info <name>'.`,
|
||||
EnvVars: []string{"TUNNEL_CLEANUP_CONNECTOR"},
|
||||
}
|
||||
)
|
||||
|
||||
func buildCreateCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "create",
|
||||
Action: cliutil.ErrorHandler(createCommand),
|
||||
Action: cliutil.ConfiguredAction(createCommand),
|
||||
Usage: "Create a new tunnel with given name",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] create [subcommand options] NAME",
|
||||
Description: `Creates a tunnel, registers it with Cloudflare edge and generates credential file used to run this tunnel.
|
||||
@@ -190,7 +208,7 @@ func writeTunnelCredentials(
|
||||
func buildListCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "list",
|
||||
Action: cliutil.ErrorHandler(listCommand),
|
||||
Action: cliutil.ConfiguredAction(listCommand),
|
||||
Usage: "List existing tunnels",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] list [subcommand options]",
|
||||
Description: "cloudflared tunnel list will display all active tunnels, their created time and associated connections. Use -d flag to include deleted tunnels. See the list of options to filter the list",
|
||||
@@ -214,6 +232,9 @@ func listCommand(c *cli.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
warningChecker := updater.StartWarningCheck(c)
|
||||
defer warningChecker.LogWarningIfAny(sc.log)
|
||||
|
||||
filter := tunnelstore.NewFilter()
|
||||
if !c.Bool("show-deleted") {
|
||||
filter.NoDeleted()
|
||||
@@ -232,9 +253,6 @@ func listCommand(c *cli.Context) error {
|
||||
filter.ByTunnelID(tunnelID)
|
||||
}
|
||||
|
||||
warningChecker := updater.StartWarningCheck(c)
|
||||
defer warningChecker.LogWarningIfAny(sc.log)
|
||||
|
||||
tunnels, err := sc.list(filter)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -284,17 +302,11 @@ func listCommand(c *cli.Context) error {
|
||||
}
|
||||
|
||||
func formatAndPrintTunnelList(tunnels []*tunnelstore.Tunnel, showRecentlyDisconnected bool) {
|
||||
const (
|
||||
minWidth = 0
|
||||
tabWidth = 8
|
||||
padding = 1
|
||||
padChar = ' '
|
||||
flags = 0
|
||||
)
|
||||
|
||||
writer := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags)
|
||||
writer := tabWriter()
|
||||
defer writer.Flush()
|
||||
|
||||
_, _ = fmt.Fprintln(writer, "You can obtain more detailed information for each tunnel with `cloudflared tunnel info <name/uuid>`")
|
||||
|
||||
// Print column headers with tabbed columns
|
||||
_, _ = fmt.Fprintln(writer, "ID\tNAME\tCREATED\tCONNECTIONS\t")
|
||||
|
||||
@@ -336,10 +348,156 @@ func fmtConnections(connections []tunnelstore.Connection, showRecentlyDisconnect
|
||||
return strings.Join(output, ", ")
|
||||
}
|
||||
|
||||
func buildInfoCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "info",
|
||||
Action: cliutil.ConfiguredAction(tunnelInfo),
|
||||
Usage: "List details about the active connectors for a tunnel",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] info [subcommand options] [TUNNEL]",
|
||||
Description: "cloudflared tunnel info displays details about the active connectors for a given tunnel (identified by name or uuid).",
|
||||
Flags: []cli.Flag{
|
||||
outputFormatFlag,
|
||||
showRecentlyDisconnected,
|
||||
sortInfoByFlag,
|
||||
invertInfoSortFlag,
|
||||
},
|
||||
CustomHelpTemplate: commandHelpTemplate(),
|
||||
}
|
||||
}
|
||||
|
||||
func tunnelInfo(c *cli.Context) error {
|
||||
sc, err := newSubcommandContext(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
warningChecker := updater.StartWarningCheck(c)
|
||||
defer warningChecker.LogWarningIfAny(sc.log)
|
||||
|
||||
if c.NArg() > 1 {
|
||||
return cliutil.UsageError(`"cloudflared tunnel info" accepts only one argument, the ID or name of the tunnel to run.`)
|
||||
}
|
||||
tunnelID, err := sc.findID(c.Args().First())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error parsing tunnel ID")
|
||||
}
|
||||
|
||||
client, err := sc.client()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clients, err := client.ListActiveClients(tunnelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sortBy := c.String("sort-by")
|
||||
invalidSortField := false
|
||||
sort.Slice(clients, func(i, j int) bool {
|
||||
cmp := func() bool {
|
||||
switch sortBy {
|
||||
case "id":
|
||||
return clients[i].ID.String() < clients[j].ID.String()
|
||||
case "createdAt":
|
||||
return clients[i].RunAt.Unix() < clients[j].RunAt.Unix()
|
||||
case "numConnections":
|
||||
return len(clients[i].Connections) < len(clients[j].Connections)
|
||||
case "version":
|
||||
return clients[i].Version < clients[j].Version
|
||||
default:
|
||||
invalidSortField = true
|
||||
return clients[i].RunAt.Unix() < clients[j].RunAt.Unix()
|
||||
}
|
||||
}()
|
||||
if c.Bool("invert-sort") {
|
||||
return !cmp
|
||||
}
|
||||
return cmp
|
||||
})
|
||||
if invalidSortField {
|
||||
sc.log.Error().Msgf("%s is not a valid sort field. Valid sort fields are %s. Defaulting to 'name'.", sortBy, connsSortByOptions)
|
||||
}
|
||||
|
||||
tunnel, err := getTunnel(sc, tunnelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info := Info{
|
||||
tunnel.ID,
|
||||
tunnel.Name,
|
||||
tunnel.CreatedAt,
|
||||
clients,
|
||||
}
|
||||
|
||||
if outputFormat := c.String(outputFormatFlag.Name); outputFormat != "" {
|
||||
return renderOutput(outputFormat, info)
|
||||
}
|
||||
|
||||
if len(clients) > 0 {
|
||||
formatAndPrintConnectionsList(info, c.Bool("show-recently-disconnected"))
|
||||
} else {
|
||||
fmt.Printf("Your tunnel %s does not have any active connection.\n", tunnelID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getTunnel(sc *subcommandContext, tunnelID uuid.UUID) (*tunnelstore.Tunnel, error) {
|
||||
filter := tunnelstore.NewFilter()
|
||||
filter.ByTunnelID(tunnelID)
|
||||
tunnels, err := sc.list(filter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(tunnels) != 1 {
|
||||
return nil, errors.Errorf("Expected to find a single tunnel with uuid %v but found %d tunnels.", tunnelID, len(tunnels))
|
||||
}
|
||||
return tunnels[0], nil
|
||||
}
|
||||
|
||||
func formatAndPrintConnectionsList(tunnelInfo Info, showRecentlyDisconnected bool) {
|
||||
writer := tabWriter()
|
||||
defer writer.Flush()
|
||||
|
||||
_, _ = fmt.Fprintf(writer, "NAME: %s\nID: %s\nCREATED: %s\n\n", tunnelInfo.Name, tunnelInfo.ID, tunnelInfo.CreatedAt)
|
||||
|
||||
_, _ = fmt.Fprintln(writer, "CONNECTOR ID\tCREATED\tARCHITECTURE\tVERSION\tORIGIN IP\tEDGE\t")
|
||||
for _, c := range tunnelInfo.Connectors {
|
||||
var originIp = ""
|
||||
if len(c.Connections) > 0 {
|
||||
originIp = c.Connections[0].OriginIP.String()
|
||||
}
|
||||
formattedStr := fmt.Sprintf(
|
||||
"%s\t%s\t%s\t%s\t%s\t%s\t",
|
||||
c.ID,
|
||||
c.RunAt.Format(time.RFC3339),
|
||||
c.Arch,
|
||||
c.Version,
|
||||
originIp,
|
||||
fmtConnections(c.Connections, showRecentlyDisconnected),
|
||||
)
|
||||
_, _ = fmt.Fprintln(writer, formattedStr)
|
||||
}
|
||||
}
|
||||
|
||||
func tabWriter() *tabwriter.Writer {
|
||||
const (
|
||||
minWidth = 0
|
||||
tabWidth = 8
|
||||
padding = 1
|
||||
padChar = ' '
|
||||
flags = 0
|
||||
)
|
||||
|
||||
writer := tabwriter.NewWriter(os.Stdout, minWidth, tabWidth, padding, padChar, flags)
|
||||
return writer
|
||||
}
|
||||
|
||||
func buildDeleteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "delete",
|
||||
Action: cliutil.ErrorHandler(deleteCommand),
|
||||
Action: cliutil.ConfiguredAction(deleteCommand),
|
||||
Usage: "Delete existing tunnel by UUID or name",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] delete [subcommand options] TUNNEL",
|
||||
Description: "cloudflared tunnel delete will delete tunnels with the given tunnel UUIDs or names. A tunnel cannot be deleted if it has active connections. To delete the tunnel unconditionally, use -f flag.",
|
||||
@@ -392,8 +550,7 @@ func buildRunCommand() *cli.Command {
|
||||
flags = append(flags, configureProxyFlags(false)...)
|
||||
return &cli.Command{
|
||||
Name: "run",
|
||||
Action: cliutil.ErrorHandler(runCommand),
|
||||
Before: SetFlagsFromConfigFile,
|
||||
Action: cliutil.ConfiguredAction(runCommand),
|
||||
Usage: "Proxy a local web server by running the given tunnel",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] run [subcommand options] [TUNNEL]",
|
||||
Description: `Runs the tunnel identified by name or UUUD, creating highly available connections
|
||||
@@ -445,10 +602,11 @@ func runNamedTunnel(sc *subcommandContext, tunnelRef string) error {
|
||||
func buildCleanupCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "cleanup",
|
||||
Action: cliutil.ErrorHandler(cleanupCommand),
|
||||
Action: cliutil.ConfiguredAction(cleanupCommand),
|
||||
Usage: "Cleanup tunnel connections",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] cleanup [subcommand options] TUNNEL",
|
||||
Description: "Delete connections for tunnels with the given UUIDs or names.",
|
||||
Flags: []cli.Flag{cleanupClientFlag},
|
||||
CustomHelpTemplate: commandHelpTemplate(),
|
||||
}
|
||||
}
|
||||
@@ -474,7 +632,7 @@ func cleanupCommand(c *cli.Context) error {
|
||||
func buildRouteCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "route",
|
||||
Action: cliutil.ErrorHandler(routeCommand),
|
||||
Action: cliutil.ConfiguredAction(routeCommand),
|
||||
Usage: "Define which traffic routed from Cloudflare edge to this tunnel: requests to a DNS hostname, to a Cloudflare Load Balancer, or traffic originating from Cloudflare WARP clients",
|
||||
UsageText: "cloudflared tunnel [tunnel command options] route [subcommand options] [dns TUNNEL HOSTNAME]|[lb TUNNEL HOSTNAME LB-POOL]|[ip NETWORK TUNNEL]",
|
||||
Description: `The route command defines how Cloudflare will proxy requests to this tunnel.
|
||||
|
||||
@@ -6,10 +6,11 @@ import (
|
||||
"os"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/updater"
|
||||
"github.com/cloudflare/cloudflared/teamnet"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@@ -25,7 +26,7 @@ Cloudflare WARP client. You can also build rules to determine who can reach cert
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "add",
|
||||
Action: cliutil.ErrorHandler(addRouteCommand),
|
||||
Action: cliutil.ConfiguredAction(addRouteCommand),
|
||||
Usage: "Add any new network to the routing table reachable via the tunnel",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] route ip add [CIDR] [TUNNEL] [COMMENT?]",
|
||||
Description: `Adds any network route space (represented as a CIDR) to your routing table.
|
||||
@@ -38,7 +39,7 @@ reachable from the tunnel.`,
|
||||
{
|
||||
Name: "show",
|
||||
Aliases: []string{"list"},
|
||||
Action: cliutil.ErrorHandler(showRoutesCommand),
|
||||
Action: cliutil.ConfiguredAction(showRoutesCommand),
|
||||
Usage: "Show the routing table",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] route ip show [flags]",
|
||||
Description: `Shows your organization private routing table. You can use flags to filter the results.`,
|
||||
@@ -46,7 +47,7 @@ reachable from the tunnel.`,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Action: cliutil.ErrorHandler(deleteRouteCommand),
|
||||
Action: cliutil.ConfiguredAction(deleteRouteCommand),
|
||||
Usage: "Delete a row from your organization's private routing table",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] route ip delete [CIDR]",
|
||||
Description: `Deletes the row for a given CIDR from your routing table. That portion
|
||||
@@ -54,7 +55,7 @@ of your network will no longer be reachable by the WARP clients.`,
|
||||
},
|
||||
{
|
||||
Name: "get",
|
||||
Action: cliutil.ErrorHandler(getRouteByIPCommand),
|
||||
Action: cliutil.ConfiguredAction(getRouteByIPCommand),
|
||||
Usage: "Check which row of the routing table matches a given IP.",
|
||||
UsageText: "cloudflared tunnel [--config FILEPATH] route ip get [IP]",
|
||||
Description: `Checks which row of the routing table will be used to proxy a given IP.
|
||||
|
||||
@@ -12,6 +12,9 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/cloudflare/cloudflared/cmd/cloudflared/cliutil"
|
||||
"github.com/cloudflare/cloudflared/logger"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
@@ -48,13 +51,13 @@ func runApp(app *cli.App, graceShutdownC chan struct{}) {
|
||||
{
|
||||
Name: "install",
|
||||
Usage: "Install Argo Tunnel as a Windows service",
|
||||
Action: installWindowsService,
|
||||
},
|
||||
Action: cliutil.ConfiguredAction(installWindowsService),
|
||||
},
|
||||
{
|
||||
Name: "uninstall",
|
||||
Usage: "Uninstall the Argo Tunnel service",
|
||||
Action: uninstallWindowsService,
|
||||
},
|
||||
Action: cliutil.ConfiguredAction(uninstallWindowsService),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -177,35 +180,30 @@ func installWindowsService(c *cli.Context) error {
|
||||
zeroLogger.Info().Msg("Installing Argo Tunnel Windows service")
|
||||
exepath, err := os.Executable()
|
||||
if err != nil {
|
||||
zeroLogger.Err(err).Msg("Cannot find path name that start the process")
|
||||
return err
|
||||
return errors.Wrap(err, "Cannot find path name that start the process")
|
||||
}
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
zeroLogger.Err(err).Msg("Cannot establish a connection to the service control manager")
|
||||
return err
|
||||
return errors.Wrap(err, "Cannot establish a connection to the service control manager")
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(windowsServiceName)
|
||||
log := zeroLogger.With().Str(LogFieldWindowsServiceName, windowsServiceName).Logger()
|
||||
if err == nil {
|
||||
s.Close()
|
||||
log.Err(err).Msg("service already exists")
|
||||
return fmt.Errorf("service %s already exists", windowsServiceName)
|
||||
return fmt.Errorf("Service %s already exists", windowsServiceName)
|
||||
}
|
||||
config := mgr.Config{StartType: mgr.StartAutomatic, DisplayName: windowsServiceDescription}
|
||||
s, err = m.CreateService(windowsServiceName, exepath, config)
|
||||
if err != nil {
|
||||
log.Error().Msg("Cannot install service")
|
||||
return err
|
||||
return errors.Wrap(err, "Cannot install service")
|
||||
}
|
||||
defer s.Close()
|
||||
log.Info().Msg("Argo Tunnel agent service is installed")
|
||||
err = eventlog.InstallAsEventCreate(windowsServiceName, eventlog.Error|eventlog.Warning|eventlog.Info)
|
||||
if err != nil {
|
||||
s.Delete()
|
||||
log.Err(err).Msg("Cannot install event logger")
|
||||
return fmt.Errorf("SetupEventLogSource() failed: %s", err)
|
||||
return errors.Wrap(err, "Cannot install event logger")
|
||||
}
|
||||
err = configRecoveryOption(s.Handle)
|
||||
if err != nil {
|
||||
@@ -223,26 +221,22 @@ func uninstallWindowsService(c *cli.Context) error {
|
||||
log.Info().Msg("Uninstalling Argo Tunnel Windows Service")
|
||||
m, err := mgr.Connect()
|
||||
if err != nil {
|
||||
log.Error().Msg("Cannot establish a connection to the service control manager")
|
||||
return err
|
||||
return errors.Wrap(err, "Cannot establish a connection to the service control manager")
|
||||
}
|
||||
defer m.Disconnect()
|
||||
s, err := m.OpenService(windowsServiceName)
|
||||
if err != nil {
|
||||
log.Error().Msg("service is not installed")
|
||||
return fmt.Errorf("service %s is not installed", windowsServiceName)
|
||||
return fmt.Errorf("Service %s is not installed", windowsServiceName)
|
||||
}
|
||||
defer s.Close()
|
||||
err = s.Delete()
|
||||
if err != nil {
|
||||
log.Error().Msg("Cannot delete service")
|
||||
return err
|
||||
return errors.Wrap(err, "Cannot delete service")
|
||||
}
|
||||
log.Info().Msg("Argo Tunnel agent service is uninstalled")
|
||||
err = eventlog.Remove(windowsServiceName)
|
||||
if err != nil {
|
||||
log.Error().Msg("Cannot remove event logger")
|
||||
return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
|
||||
return errors.Wrap(err, "Cannot remove event logger")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
flaky==3.7.0
|
||||
pytest==6.2.2
|
||||
pyyaml==5.4.1
|
||||
requests==2.25.1
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
import copy
|
||||
|
||||
from flaky import flaky
|
||||
from retrying import retry
|
||||
from time import sleep
|
||||
|
||||
from util import start_cloudflared, wait_tunnel_ready, check_tunnel_not_connected, send_requests
|
||||
|
||||
|
||||
@flaky(max_runs=3, min_passes=1)
|
||||
class TestReconnect():
|
||||
default_ha_conns = 4
|
||||
default_reconnect_secs = 5
|
||||
|
||||
@@ -50,9 +50,9 @@ class TestTermination():
|
||||
target=self.stream_request, args=(config, connected, True, ))
|
||||
in_flight_req.start()
|
||||
|
||||
with connected:
|
||||
connected.wait(self.timeout)
|
||||
with self.within_grace_period():
|
||||
with connected:
|
||||
connected.wait(self.timeout)
|
||||
# Send signal after the SSE connection is established
|
||||
self.terminate_by_signal(cloudflared, sig)
|
||||
self.wait_eyeball_thread(in_flight_req, self.grace_period)
|
||||
|
||||
+10
-3
@@ -49,6 +49,9 @@ def run_cloudflared_background(cmd, allow_input, capture_output):
|
||||
yield cfd
|
||||
finally:
|
||||
cfd.terminate()
|
||||
if capture_output:
|
||||
LOGGER.info(
|
||||
f"cloudflared log: {cfd.stderr.read()}")
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||
@@ -63,12 +66,16 @@ def wait_tunnel_ready(tunnel_url=None, require_min_connections=1):
|
||||
send_request(s, tunnel_url, True)
|
||||
|
||||
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES, wait_fixed=BACKOFF_SECS * 1000)
|
||||
@retry(stop_max_attempt_number=MAX_RETRIES * BACKOFF_SECS, wait_fixed=1000)
|
||||
def check_tunnel_not_connected():
|
||||
url = f'http://localhost:{METRICS_PORT}/ready'
|
||||
|
||||
resp = requests.get(url, timeout=1)
|
||||
assert resp.status_code == 503, f"Expect {url} returns 503, got {resp.status_code}"
|
||||
try:
|
||||
resp = requests.get(url, timeout=1)
|
||||
assert resp.status_code == 503, f"Expect {url} returns 503, got {resp.status_code}"
|
||||
# cloudflared might already terminate
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
LOGGER.warning(f"Failed to connect to {url}, error: {e}")
|
||||
|
||||
|
||||
# In some cases we don't need to check response status, such as when sending batch requests to generate logs
|
||||
|
||||
+2
-1
@@ -663,7 +663,8 @@ func AssertIfPipeReadable(t *testing.T, pipe io.ReadCloser) {
|
||||
b := []byte{0}
|
||||
n, err := pipe.Read(b)
|
||||
if n > 0 {
|
||||
t.Fatalf("read pipe was not empty")
|
||||
t.Errorf("read pipe was not empty")
|
||||
return
|
||||
}
|
||||
errC <- err
|
||||
}()
|
||||
|
||||
@@ -81,7 +81,8 @@ func TestSharedBufferConcurrentReadWrite(t *testing.T) {
|
||||
expectedResult.Write(block[:blockSize])
|
||||
n, err := b.Write(block[:blockSize])
|
||||
if n != blockSize || err != nil {
|
||||
t.Fatalf("write error: %d %s", n, err)
|
||||
t.Errorf("write error: %d %s", n, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +95,8 @@ func TestSharedBufferConcurrentReadWrite(t *testing.T) {
|
||||
for i := 0; i < 256; i++ {
|
||||
n, err := io.ReadFull(b, block[:blockSize])
|
||||
if n != blockSize || err != nil {
|
||||
t.Fatalf("read error: %d %s", n, err)
|
||||
t.Errorf("read error: %d %s", n, err)
|
||||
return
|
||||
}
|
||||
actualResult.Write(block[:blockSize])
|
||||
}
|
||||
|
||||
+11
-5
@@ -64,6 +64,8 @@ func (t resilientMultiWriter) Write(p []byte) (n int, err error) {
|
||||
return len(p), nil
|
||||
}
|
||||
|
||||
var levelErrorLogged = false
|
||||
|
||||
func newZerolog(loggerConfig *Config) *zerolog.Logger {
|
||||
var writers []io.Writer
|
||||
|
||||
@@ -91,11 +93,15 @@ func newZerolog(loggerConfig *Config) *zerolog.Logger {
|
||||
|
||||
multi := resilientMultiWriter{writers}
|
||||
|
||||
level, err := zerolog.ParseLevel(loggerConfig.MinLevel)
|
||||
if err != nil {
|
||||
return fallbackLogger(err)
|
||||
level, levelErr := zerolog.ParseLevel(loggerConfig.MinLevel)
|
||||
if levelErr != nil {
|
||||
level = zerolog.InfoLevel
|
||||
}
|
||||
log := zerolog.New(multi).With().Timestamp().Logger().Level(level)
|
||||
if !levelErrorLogged && levelErr != nil {
|
||||
log.Error().Msgf("Failed to parse log level %q, using %q instead", loggerConfig.MinLevel, level)
|
||||
levelErrorLogged = true
|
||||
}
|
||||
|
||||
return &log
|
||||
}
|
||||
@@ -151,8 +157,8 @@ func Create(loggerConfig *Config) *zerolog.Logger {
|
||||
func createConsoleLogger(config ConsoleConfig) io.Writer {
|
||||
consoleOut := os.Stderr
|
||||
return zerolog.ConsoleWriter{
|
||||
Out: colorable.NewColorable(consoleOut),
|
||||
NoColor: config.noColor || !term.IsTerminal(int(consoleOut.Fd())),
|
||||
Out: colorable.NewColorable(consoleOut),
|
||||
NoColor: config.noColor || !term.IsTerminal(int(consoleOut.Fd())),
|
||||
TimeFormat: consoleTimeFormat,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,12 +30,6 @@ const (
|
||||
refreshAuthMaxBackoff = 10
|
||||
// Waiting time before retrying a failed 'Authenticate' connection
|
||||
refreshAuthRetryDuration = time.Second * 10
|
||||
// Maximum time to make an Authenticate RPC
|
||||
authTokenTimeout = time.Second * 30
|
||||
)
|
||||
|
||||
var (
|
||||
errEventDigestUnset = errors.New("event digest unset")
|
||||
)
|
||||
|
||||
// Supervisor manages non-declarative tunnels. Establishes TCP connections with the edge, and
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
"github.com/pkg/errors"
|
||||
gossh "golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/cloudflare/cloudflared/config"
|
||||
cfpath "github.com/cloudflare/cloudflared/token"
|
||||
)
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
//+build linux
|
||||
// +build linux
|
||||
|
||||
package token
|
||||
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package tunnelstore
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type CleanupParams struct {
|
||||
queryParams url.Values
|
||||
}
|
||||
|
||||
func NewCleanupParams() *CleanupParams {
|
||||
return &CleanupParams{
|
||||
queryParams: url.Values{},
|
||||
}
|
||||
}
|
||||
|
||||
func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
|
||||
cp.queryParams.Set("client_id", clientID.String())
|
||||
}
|
||||
|
||||
func (cp CleanupParams) encode() string {
|
||||
return cp.queryParams.Encode()
|
||||
}
|
||||
+37
-2
@@ -43,6 +43,17 @@ type Connection struct {
|
||||
ColoName string `json:"colo_name"`
|
||||
ID uuid.UUID `json:"id"`
|
||||
IsPendingReconnect bool `json:"is_pending_reconnect"`
|
||||
OriginIP net.IP `json:"origin_ip"`
|
||||
OpenedAt time.Time `json:"opened_at"`
|
||||
}
|
||||
|
||||
type ActiveClient struct {
|
||||
ID uuid.UUID `json:"id"`
|
||||
Features []string `json:"features"`
|
||||
Version string `json:"version"`
|
||||
Arch string `json:"arch"`
|
||||
RunAt time.Time `json:"run_at"`
|
||||
Connections []Connection `json:"conns"`
|
||||
}
|
||||
|
||||
type Change = string
|
||||
@@ -192,7 +203,8 @@ type Client interface {
|
||||
GetTunnel(tunnelID uuid.UUID) (*Tunnel, error)
|
||||
DeleteTunnel(tunnelID uuid.UUID) error
|
||||
ListTunnels(filter *Filter) ([]*Tunnel, error)
|
||||
CleanupConnections(tunnelID uuid.UUID) error
|
||||
ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error)
|
||||
CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error
|
||||
RouteTunnel(tunnelID uuid.UUID, route Route) (RouteResult, error)
|
||||
|
||||
// Teamnet endpoints
|
||||
@@ -336,9 +348,32 @@ func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
|
||||
return tunnels, err
|
||||
}
|
||||
|
||||
func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID) error {
|
||||
func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
|
||||
endpoint := r.baseEndpoints.accountLevel
|
||||
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
|
||||
resp, err := r.sendRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "REST request failed")
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
return parseConnectionsDetails(resp.Body)
|
||||
}
|
||||
|
||||
return nil, r.statusCodeToError("list connection details", resp)
|
||||
}
|
||||
|
||||
func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
|
||||
var clients []*ActiveClient
|
||||
err := parseResponse(reader, &clients)
|
||||
return clients, err
|
||||
}
|
||||
|
||||
func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
|
||||
endpoint := r.baseEndpoints.accountLevel
|
||||
endpoint.RawQuery = params.encode()
|
||||
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
|
||||
resp, err := r.sendRequest("DELETE", endpoint, nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "REST request failed")
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -205,3 +206,24 @@ func TestUnmarshalTunnelErr(t *testing.T) {
|
||||
assert.Error(t, err, fmt.Sprintf("Test #%v failed", i))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnmarshalConnections(t *testing.T) {
|
||||
jsonBody := `{"success":true,"messages":[],"errors":[],"result":[{"id":"d4041254-91e3-4deb-bd94-b46e11680b1e","features":["ha-origin"],"version":"2021.2.5","arch":"darwin_amd64","conns":[{"colo_name":"LIS","id":"ac2286e5-c708-4588-a6a0-ba6b51940019","is_pending_reconnect":false,"origin_ip":"148.38.28.2","opened_at":"0001-01-01T00:00:00Z"}],"run_at":"0001-01-01T00:00:00Z"}]}`
|
||||
expected := ActiveClient{
|
||||
ID: uuid.MustParse("d4041254-91e3-4deb-bd94-b46e11680b1e"),
|
||||
Features: []string{"ha-origin"},
|
||||
Version: "2021.2.5",
|
||||
Arch: "darwin_amd64",
|
||||
RunAt: time.Time{},
|
||||
Connections: []Connection{{
|
||||
ID: uuid.MustParse("ac2286e5-c708-4588-a6a0-ba6b51940019"),
|
||||
ColoName: "LIS",
|
||||
IsPendingReconnect: false,
|
||||
OriginIP: net.ParseIP("148.38.28.2"),
|
||||
OpenedAt: time.Time{},
|
||||
}},
|
||||
}
|
||||
actual, err := parseConnectionsDetails(bytes.NewReader([]byte(jsonBody)))
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, []*ActiveClient{&expected}, actual)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user