TUN-7543: Add --debug-stream flag to cloudflared access ssh
Allows for debugging the payloads that are sent in client mode to the ssh server. Required to be run with --log-directory to capture logging output. Additionally has maximum limit that is provided with the flag that will only capture the first N number of reads plus writes through the WebSocket stream. These reads/writes are not directly captured at the packet boundary so some reconstruction from the log messages will be required. Added User-Agent for all out-going cloudflared access tcp requests in client mode. Added check to not run terminal logging in cloudflared access tcp client mode to not obstruct the stdin and stdout.
This commit is contained in:
parent
729890d847
commit
2b4815a9f5
|
@ -3,6 +3,7 @@ package access
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -13,6 +14,7 @@ import (
|
||||||
"github.com/cloudflare/cloudflared/carrier"
|
"github.com/cloudflare/cloudflared/carrier"
|
||||||
"github.com/cloudflare/cloudflared/config"
|
"github.com/cloudflare/cloudflared/config"
|
||||||
"github.com/cloudflare/cloudflared/logger"
|
"github.com/cloudflare/cloudflared/logger"
|
||||||
|
"github.com/cloudflare/cloudflared/stream"
|
||||||
"github.com/cloudflare/cloudflared/validation"
|
"github.com/cloudflare/cloudflared/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -38,6 +40,7 @@ func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *z
|
||||||
if forwarder.TokenSecret != "" {
|
if forwarder.TokenSecret != "" {
|
||||||
headers.Set(cfAccessClientSecretHeader, forwarder.TokenSecret)
|
headers.Set(cfAccessClientSecretHeader, forwarder.TokenSecret)
|
||||||
}
|
}
|
||||||
|
headers.Set("User-Agent", userAgent)
|
||||||
|
|
||||||
carrier.SetBastionDest(headers, forwarder.Destination)
|
carrier.SetBastionDest(headers, forwarder.Destination)
|
||||||
|
|
||||||
|
@ -58,7 +61,12 @@ func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *z
|
||||||
// useful for proxying other protocols (like ssh) over websockets
|
// useful for proxying other protocols (like ssh) over websockets
|
||||||
// (which you can put Access in front of)
|
// (which you can put Access in front of)
|
||||||
func ssh(c *cli.Context) error {
|
func ssh(c *cli.Context) error {
|
||||||
log := logger.CreateSSHLoggerFromContext(c, logger.EnableTerminalLog)
|
// If not running as a forwarder, disable terminal logs as it collides with the stdin/stdout of the parent process
|
||||||
|
outputTerminal := logger.DisableTerminalLog
|
||||||
|
if c.IsSet(sshURLFlag) {
|
||||||
|
outputTerminal = logger.EnableTerminalLog
|
||||||
|
}
|
||||||
|
log := logger.CreateSSHLoggerFromContext(c, outputTerminal)
|
||||||
|
|
||||||
// get the hostname from the cmdline and error out if its not provided
|
// get the hostname from the cmdline and error out if its not provided
|
||||||
rawHostName := c.String(sshHostnameFlag)
|
rawHostName := c.String(sshHostnameFlag)
|
||||||
|
@ -76,6 +84,7 @@ func ssh(c *cli.Context) error {
|
||||||
if c.IsSet(sshTokenSecretFlag) {
|
if c.IsSet(sshTokenSecretFlag) {
|
||||||
headers.Set(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
|
headers.Set(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
|
||||||
}
|
}
|
||||||
|
headers.Set("User-Agent", userAgent)
|
||||||
|
|
||||||
carrier.SetBastionDest(headers, c.String(sshDestinationFlag))
|
carrier.SetBastionDest(headers, c.String(sshDestinationFlag))
|
||||||
|
|
||||||
|
@ -121,7 +130,19 @@ func ssh(c *cli.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return carrier.StartClient(wsConn, &carrier.StdinoutStream{}, options)
|
var s io.ReadWriter
|
||||||
|
s = &carrier.StdinoutStream{}
|
||||||
|
if c.IsSet(sshDebugStream) {
|
||||||
|
maxMessages := c.Uint64(sshDebugStream)
|
||||||
|
if maxMessages == 0 {
|
||||||
|
// default to 10 if provided but unset
|
||||||
|
maxMessages = 10
|
||||||
|
}
|
||||||
|
logger := log.With().Str("host", hostname).Logger()
|
||||||
|
s = stream.NewDebugStream(s, &logger, maxMessages)
|
||||||
|
}
|
||||||
|
carrier.StartClient(wsConn, s, options)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildRequestHeaders(values []string) http.Header {
|
func buildRequestHeaders(values []string) http.Header {
|
||||||
|
|
|
@ -34,6 +34,7 @@ const (
|
||||||
sshTokenSecretFlag = "service-token-secret"
|
sshTokenSecretFlag = "service-token-secret"
|
||||||
sshGenCertFlag = "short-lived-cert"
|
sshGenCertFlag = "short-lived-cert"
|
||||||
sshConnectTo = "connect-to"
|
sshConnectTo = "connect-to"
|
||||||
|
sshDebugStream = "debug-stream"
|
||||||
sshConfigTemplate = `
|
sshConfigTemplate = `
|
||||||
Add to your {{.Home}}/.ssh/config:
|
Add to your {{.Home}}/.ssh/config:
|
||||||
|
|
||||||
|
@ -151,9 +152,12 @@ func Commands() []*cli.Command {
|
||||||
EnvVars: []string{"TUNNEL_SERVICE_TOKEN_SECRET"},
|
EnvVars: []string{"TUNNEL_SERVICE_TOKEN_SECRET"},
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: logger.LogSSHDirectoryFlag,
|
Name: logger.LogFileFlag,
|
||||||
Aliases: []string{"logfile"}, //added to match the tunnel side
|
Usage: "Save application log to this file for reporting issues.",
|
||||||
Usage: "Save application log to this directory for reporting issues.",
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: logger.LogSSHDirectoryFlag,
|
||||||
|
Usage: "Save application log to this directory for reporting issues.",
|
||||||
},
|
},
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: logger.LogSSHLevelFlag,
|
Name: logger.LogSSHLevelFlag,
|
||||||
|
@ -165,6 +169,11 @@ func Commands() []*cli.Command {
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
Usage: "Connect to alternate location for testing, value is host, host:port, or sni:port:host",
|
Usage: "Connect to alternate location for testing, value is host, host:port, or sni:port:host",
|
||||||
},
|
},
|
||||||
|
&cli.Uint64Flag{
|
||||||
|
Name: sshDebugStream,
|
||||||
|
Hidden: true,
|
||||||
|
Usage: "Writes up-to the max provided stream payloads to the logger as debug statements.",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -175,7 +175,7 @@ func createFromContext(
|
||||||
|
|
||||||
log := newZerolog(loggerConfig)
|
log := newZerolog(loggerConfig)
|
||||||
if incompatibleFlagsSet := logFile != "" && logDirectory != ""; incompatibleFlagsSet {
|
if incompatibleFlagsSet := logFile != "" && logDirectory != ""; incompatibleFlagsSet {
|
||||||
log.Error().Msgf("Your config includes values for both %s and %s, but they are incompatible. %s takes precedence.", LogFileFlag, logDirectoryFlagName, LogFileFlag)
|
log.Error().Msgf("Your config includes values for both %s (%s) and %s (%s), but they are incompatible. %s takes precedence.", LogFileFlag, logFile, logDirectoryFlagName, logDirectory, LogFileFlag)
|
||||||
}
|
}
|
||||||
return log
|
return log
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
package stream
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
|
"github.com/rs/zerolog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DebugStream will tee each read and write to the output logger as a debug message
|
||||||
|
type DebugStream struct {
|
||||||
|
reader io.Reader
|
||||||
|
writer io.Writer
|
||||||
|
log *zerolog.Logger
|
||||||
|
max uint64
|
||||||
|
count atomic.Uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewDebugStream(stream io.ReadWriter, logger *zerolog.Logger, max uint64) *DebugStream {
|
||||||
|
return &DebugStream{
|
||||||
|
reader: stream,
|
||||||
|
writer: stream,
|
||||||
|
log: logger,
|
||||||
|
max: max,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DebugStream) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = d.reader.Read(p)
|
||||||
|
if n > 0 && d.max > d.count.Load() {
|
||||||
|
d.count.Add(1)
|
||||||
|
if err != nil {
|
||||||
|
d.log.Err(err).
|
||||||
|
Str("dir", "r").
|
||||||
|
Int("count", n).
|
||||||
|
Msgf("%+q", p[:n])
|
||||||
|
} else {
|
||||||
|
d.log.Debug().
|
||||||
|
Str("dir", "r").
|
||||||
|
Int("count", n).
|
||||||
|
Msgf("%+q", p[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *DebugStream) Write(p []byte) (n int, err error) {
|
||||||
|
n, err = d.writer.Write(p)
|
||||||
|
if n > 0 && d.max > d.count.Load() {
|
||||||
|
d.count.Add(1)
|
||||||
|
if err != nil {
|
||||||
|
d.log.Err(err).
|
||||||
|
Str("dir", "w").
|
||||||
|
Int("count", n).
|
||||||
|
Msgf("%+q", p[:n])
|
||||||
|
} else {
|
||||||
|
d.log.Debug().
|
||||||
|
Str("dir", "w").
|
||||||
|
Int("count", n).
|
||||||
|
Msgf("%+q", p[:n])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
Loading…
Reference in New Issue