Skip to content

Commit 2b4815a

Browse files
committed
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.
1 parent 729890d commit 2b4815a

File tree

4 files changed

+100
-6
lines changed

4 files changed

+100
-6
lines changed

cmd/cloudflared/access/carrier.go

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package access
33
import (
44
"crypto/tls"
55
"fmt"
6+
"io"
67
"net/http"
78
"strings"
89

@@ -13,6 +14,7 @@ import (
1314
"github.com/cloudflare/cloudflared/carrier"
1415
"github.com/cloudflare/cloudflared/config"
1516
"github.com/cloudflare/cloudflared/logger"
17+
"github.com/cloudflare/cloudflared/stream"
1618
"github.com/cloudflare/cloudflared/validation"
1719
)
1820

@@ -38,6 +40,7 @@ func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *z
3840
if forwarder.TokenSecret != "" {
3941
headers.Set(cfAccessClientSecretHeader, forwarder.TokenSecret)
4042
}
43+
headers.Set("User-Agent", userAgent)
4144

4245
carrier.SetBastionDest(headers, forwarder.Destination)
4346

@@ -58,7 +61,12 @@ func StartForwarder(forwarder config.Forwarder, shutdown <-chan struct{}, log *z
5861
// useful for proxying other protocols (like ssh) over websockets
5962
// (which you can put Access in front of)
6063
func ssh(c *cli.Context) error {
61-
log := logger.CreateSSHLoggerFromContext(c, logger.EnableTerminalLog)
64+
// If not running as a forwarder, disable terminal logs as it collides with the stdin/stdout of the parent process
65+
outputTerminal := logger.DisableTerminalLog
66+
if c.IsSet(sshURLFlag) {
67+
outputTerminal = logger.EnableTerminalLog
68+
}
69+
log := logger.CreateSSHLoggerFromContext(c, outputTerminal)
6270

6371
// get the hostname from the cmdline and error out if its not provided
6472
rawHostName := c.String(sshHostnameFlag)
@@ -76,6 +84,7 @@ func ssh(c *cli.Context) error {
7684
if c.IsSet(sshTokenSecretFlag) {
7785
headers.Set(cfAccessClientSecretHeader, c.String(sshTokenSecretFlag))
7886
}
87+
headers.Set("User-Agent", userAgent)
7988

8089
carrier.SetBastionDest(headers, c.String(sshDestinationFlag))
8190

@@ -121,7 +130,19 @@ func ssh(c *cli.Context) error {
121130
return err
122131
}
123132

124-
return carrier.StartClient(wsConn, &carrier.StdinoutStream{}, options)
133+
var s io.ReadWriter
134+
s = &carrier.StdinoutStream{}
135+
if c.IsSet(sshDebugStream) {
136+
maxMessages := c.Uint64(sshDebugStream)
137+
if maxMessages == 0 {
138+
// default to 10 if provided but unset
139+
maxMessages = 10
140+
}
141+
logger := log.With().Str("host", hostname).Logger()
142+
s = stream.NewDebugStream(s, &logger, maxMessages)
143+
}
144+
carrier.StartClient(wsConn, s, options)
145+
return nil
125146
}
126147

127148
func buildRequestHeaders(values []string) http.Header {

cmd/cloudflared/access/cmd.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const (
3434
sshTokenSecretFlag = "service-token-secret"
3535
sshGenCertFlag = "short-lived-cert"
3636
sshConnectTo = "connect-to"
37+
sshDebugStream = "debug-stream"
3738
sshConfigTemplate = `
3839
Add to your {{.Home}}/.ssh/config:
3940
@@ -151,9 +152,12 @@ func Commands() []*cli.Command {
151152
EnvVars: []string{"TUNNEL_SERVICE_TOKEN_SECRET"},
152153
},
153154
&cli.StringFlag{
154-
Name: logger.LogSSHDirectoryFlag,
155-
Aliases: []string{"logfile"}, //added to match the tunnel side
156-
Usage: "Save application log to this directory for reporting issues.",
155+
Name: logger.LogFileFlag,
156+
Usage: "Save application log to this file for reporting issues.",
157+
},
158+
&cli.StringFlag{
159+
Name: logger.LogSSHDirectoryFlag,
160+
Usage: "Save application log to this directory for reporting issues.",
157161
},
158162
&cli.StringFlag{
159163
Name: logger.LogSSHLevelFlag,
@@ -165,6 +169,11 @@ func Commands() []*cli.Command {
165169
Hidden: true,
166170
Usage: "Connect to alternate location for testing, value is host, host:port, or sni:port:host",
167171
},
172+
&cli.Uint64Flag{
173+
Name: sshDebugStream,
174+
Hidden: true,
175+
Usage: "Writes up-to the max provided stream payloads to the logger as debug statements.",
176+
},
168177
},
169178
},
170179
{

logger/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ func createFromContext(
175175

176176
log := newZerolog(loggerConfig)
177177
if incompatibleFlagsSet := logFile != "" && logDirectory != ""; incompatibleFlagsSet {
178-
log.Error().Msgf("Your config includes values for both %s and %s, but they are incompatible. %s takes precedence.", LogFileFlag, logDirectoryFlagName, LogFileFlag)
178+
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)
179179
}
180180
return log
181181
}

stream/debug.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package stream
2+
3+
import (
4+
"io"
5+
"sync/atomic"
6+
7+
"github.com/rs/zerolog"
8+
)
9+
10+
// DebugStream will tee each read and write to the output logger as a debug message
11+
type DebugStream struct {
12+
reader io.Reader
13+
writer io.Writer
14+
log *zerolog.Logger
15+
max uint64
16+
count atomic.Uint64
17+
}
18+
19+
func NewDebugStream(stream io.ReadWriter, logger *zerolog.Logger, max uint64) *DebugStream {
20+
return &DebugStream{
21+
reader: stream,
22+
writer: stream,
23+
log: logger,
24+
max: max,
25+
}
26+
}
27+
28+
func (d *DebugStream) Read(p []byte) (n int, err error) {
29+
n, err = d.reader.Read(p)
30+
if n > 0 && d.max > d.count.Load() {
31+
d.count.Add(1)
32+
if err != nil {
33+
d.log.Err(err).
34+
Str("dir", "r").
35+
Int("count", n).
36+
Msgf("%+q", p[:n])
37+
} else {
38+
d.log.Debug().
39+
Str("dir", "r").
40+
Int("count", n).
41+
Msgf("%+q", p[:n])
42+
}
43+
}
44+
return
45+
}
46+
47+
func (d *DebugStream) Write(p []byte) (n int, err error) {
48+
n, err = d.writer.Write(p)
49+
if n > 0 && d.max > d.count.Load() {
50+
d.count.Add(1)
51+
if err != nil {
52+
d.log.Err(err).
53+
Str("dir", "w").
54+
Int("count", n).
55+
Msgf("%+q", p[:n])
56+
} else {
57+
d.log.Debug().
58+
Str("dir", "w").
59+
Int("count", n).
60+
Msgf("%+q", p[:n])
61+
}
62+
}
63+
return
64+
}

0 commit comments

Comments
 (0)