AUTH-2056: Writes stderr to its own stream for non-pty connections

This commit is contained in:
Michael Borkenstein 2019-09-10 18:50:04 -05:00
parent 40d9370bb6
commit ff795a7beb
4 changed files with 72 additions and 41 deletions

4
.gitignore vendored
View File

@ -9,5 +9,7 @@ guide/public
\#*\# \#*\#
cscope.* cscope.*
cloudflared cloudflared
cloudflared.exe
!cmd/cloudflared/ !cmd/cloudflared/
.DS_Store .DS_Store
*-session.log

View File

@ -366,8 +366,9 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
if c.IsSet("ssh-server") { if c.IsSet("ssh-server") {
if runtime.GOOS != "darwin" && runtime.GOOS != "linux" { if runtime.GOOS != "darwin" && runtime.GOOS != "linux" {
logger.Errorf("--ssh-server is not supported on %s", runtime.GOOS) msg := fmt.Sprintf("--ssh-server is not supported on %s", runtime.GOOS)
return errors.New(fmt.Sprintf("--ssh-server is not supported on %s", runtime.GOOS)) logger.Error(msg)
return errors.New(msg)
} }
@ -378,11 +379,17 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
uploader, err := awsuploader.NewFileUploader(c.String(bucketNameFlag), c.String(regionNameFlag), uploader, err := awsuploader.NewFileUploader(c.String(bucketNameFlag), c.String(regionNameFlag),
c.String(accessKeyIDFlag), c.String(secretIDFlag), c.String(sessionTokenIDFlag), c.String(s3URLFlag)) c.String(accessKeyIDFlag), c.String(secretIDFlag), c.String(sessionTokenIDFlag), c.String(s3URLFlag))
if err != nil { if err != nil {
logger.WithError(err).Error("Cannot create uploader for SSH Server") msg := "Cannot create uploader for SSH Server"
return errors.Wrap(err, "Cannot create uploader for SSH Server") logger.WithError(err).Error(msg)
return errors.Wrap(err, msg)
}
if err := os.Mkdir(sshLogFileDirectory, 0600); err != nil {
msg := fmt.Sprintf("Cannot create SSH log file directory %s", sshLogFileDirectory)
logger.WithError(err).Errorf(msg)
return errors.Wrap(err, msg)
} }
os.Mkdir(sshLogFileDirectory, 0600)
logManager = sshlog.New(sshLogFileDirectory) logManager = sshlog.New(sshLogFileDirectory)
uploadManager := awsuploader.NewDirectoryUploadManager(logger, uploader, sshLogFileDirectory, 30*time.Minute, shutdownC) uploadManager := awsuploader.NewDirectoryUploadManager(logger, uploader, sshLogFileDirectory, 30*time.Minute, shutdownC)
@ -392,8 +399,9 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
sshServerAddress := "127.0.0.1:" + c.String(sshPortFlag) sshServerAddress := "127.0.0.1:" + c.String(sshPortFlag)
server, err := sshserver.New(logManager, logger, version, sshServerAddress, shutdownC, c.Duration(sshIdleTimeoutFlag), c.Duration(sshMaxTimeoutFlag), c.Bool(enablePortForwardingFlag)) server, err := sshserver.New(logManager, logger, version, sshServerAddress, shutdownC, c.Duration(sshIdleTimeoutFlag), c.Duration(sshMaxTimeoutFlag), c.Bool(enablePortForwardingFlag))
if err != nil { if err != nil {
logger.WithError(err).Error("Cannot create new SSH Server") msg := "Cannot create new SSH Server"
return errors.Wrap(err, "Cannot create new SSH Server") logger.WithError(err).Error(msg)
return errors.Wrap(err, msg)
} }
wg.Add(1) wg.Add(1)
go func() { go func() {

View File

@ -29,7 +29,7 @@ func (m *emptyManager) NewSessionLogger(name string, logger *logrus.Logger) (io.
// emptyWriteCloser // emptyWriteCloser
func (w *emptyWriteCloser) Write(p []byte) (n int, err error) { func (w *emptyWriteCloser) Write(p []byte) (n int, err error) {
return 0, nil return len(p), nil
} }
func (w *emptyWriteCloser) Close() error { func (w *emptyWriteCloser) Close() error {

View File

@ -33,7 +33,6 @@ const (
auditEventExec = "exec" auditEventExec = "exec"
auditEventScp = "scp" auditEventScp = "scp"
auditEventResize = "resize" auditEventResize = "resize"
auditEventTamper = "tamper"
) )
type auditEvent struct { type auditEvent struct {
@ -156,14 +155,18 @@ func (s *SSHServer) connectionHandler(session ssh.Session) {
var shellInput io.WriteCloser var shellInput io.WriteCloser
var shellOutput io.ReadCloser var shellOutput io.ReadCloser
pr, pw := io.Pipe()
defer pw.Close()
ptyReq, winCh, isPty := session.Pty() ptyReq, winCh, isPty := session.Pty()
if isPty { if isPty {
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term)) cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
shellInput, shellOutput, err = s.startPtySession(cmd, winCh, func() { tty, err := s.startPtySession(cmd, winCh, func() {
s.logAuditEvent(eventLogger, session, sessionID, auditEventResize) s.logAuditEvent(eventLogger, session, sessionID, auditEventResize)
}) })
shellInput = tty
shellOutput = tty
if err != nil { if err != nil {
s.logger.WithError(err).Error("Failed to start pty session") s.logger.WithError(err).Error("Failed to start pty session")
close(s.shutdownC) close(s.shutdownC)
@ -172,7 +175,8 @@ func (s *SSHServer) connectionHandler(session ssh.Session) {
s.logAuditEvent(eventLogger, session, sessionID, auditEventStart) s.logAuditEvent(eventLogger, session, sessionID, auditEventStart)
defer s.logAuditEvent(eventLogger, session, sessionID, auditEventStop) defer s.logAuditEvent(eventLogger, session, sessionID, auditEventStop)
} else { } else {
shellInput, shellOutput, err = s.startNonPtySession(cmd) var shellError io.ReadCloser
shellInput, shellOutput, shellError, err = s.startNonPtySession(cmd)
if err != nil { if err != nil {
s.logger.WithError(err).Error("Failed to start non-pty session") s.logger.WithError(err).Error("Failed to start non-pty session")
close(s.shutdownC) close(s.shutdownC)
@ -183,19 +187,16 @@ func (s *SSHServer) connectionHandler(session ssh.Session) {
event = auditEventScp event = auditEventScp
} }
s.logAuditEvent(eventLogger, session, sessionID, event) s.logAuditEvent(eventLogger, session, sessionID, event)
// Write stderr to both the command recorder, and remote user
go func() {
mw := io.MultiWriter(pw, session.Stderr())
if _, err := io.Copy(mw, shellError); err != nil {
s.logger.WithError(err).Error("Failed to write stderr to user")
}
}()
} }
// Write incoming commands to shell
go func() {
if _, err := io.Copy(shellInput, session); err != nil {
s.logger.WithError(err).Error("Failed to write incoming command to pty")
}
}()
pr, pw := io.Pipe()
defer pr.Close()
defer pw.Close()
sessionLogger, err := s.logManager.NewSessionLogger(fmt.Sprintf("%s-session.log", sessionID), s.logger) sessionLogger, err := s.logManager.NewSessionLogger(fmt.Sprintf("%s-session.log", sessionID), s.logger)
if err != nil { if err != nil {
if _, err := io.WriteString(session, "Failed to create log\n"); err != nil { if _, err := io.WriteString(session, "Failed to create log\n"); err != nil {
@ -206,13 +207,25 @@ func (s *SSHServer) connectionHandler(session ssh.Session) {
} }
defer sessionLogger.Close() defer sessionLogger.Close()
go func() { go func() {
io.Copy(sessionLogger, pr) defer pr.Close()
_, err := io.Copy(sessionLogger, pr)
if err != nil {
s.logger.WithError(err).Error("Failed to write session log")
}
}() }()
// Write outgoing command output to both the command recorder, and remote user // Write stdin to shell
go func() {
defer shellInput.Close()
if _, err := io.Copy(shellInput, session); err != nil {
s.logger.WithError(err).Error("Failed to write incoming command to pty")
}
}()
// Write stdout to both the command recorder, and remote user
mw := io.MultiWriter(pw, session) mw := io.MultiWriter(pw, session)
if _, err := io.Copy(mw, shellOutput); err != nil { if _, err := io.Copy(mw, shellOutput); err != nil {
s.logger.WithError(err).Error("Failed to write command output to user") s.logger.WithError(err).Error("Failed to write stdout to user")
} }
// Wait for all resources associated with cmd to be released // Wait for all resources associated with cmd to be released
@ -264,8 +277,8 @@ func (s *SSHServer) getSSHUser(session ssh.Session, sessionID string, eventLogge
// errorAndExit reports an error with the session and exits // errorAndExit reports an error with the session and exits
func (s *SSHServer) errorAndExit(session ssh.Session, errText string, err error) { func (s *SSHServer) errorAndExit(session ssh.Session, errText string, err error) {
if err := session.Exit(1); err != nil { if exitError := session.Exit(1); exitError != nil {
s.logger.WithError(err).Error("Failed to close SSH session") s.logger.WithError(exitError).Error("Failed to close SSH session")
} else if err != nil { } else if err != nil {
s.logger.WithError(err).Error(errText) s.logger.WithError(err).Error(errText)
} else if errText != "" { } else if errText != "" {
@ -273,26 +286,31 @@ func (s *SSHServer) errorAndExit(session ssh.Session, errText string, err error)
} }
} }
func (s *SSHServer) startNonPtySession(cmd *exec.Cmd) (io.WriteCloser, io.ReadCloser, error) { func (s *SSHServer) startNonPtySession(cmd *exec.Cmd) (stdin io.WriteCloser, stdout io.ReadCloser, stderr io.ReadCloser, err error) {
in, err := cmd.StdinPipe() stdin, err = cmd.StdinPipe()
if err != nil { if err != nil {
return nil, nil, err return
} }
out, err := cmd.StdoutPipe() stdout, err = cmd.StdoutPipe()
if err != nil { if err != nil {
return nil, nil, err return
} }
cmd.Stderr = cmd.Stdout
stderr, err = cmd.StderrPipe()
if err != nil {
return
}
if err = cmd.Start(); err != nil { if err = cmd.Start(); err != nil {
return nil, nil, err return
} }
return in, out, nil return
} }
func (s *SSHServer) startPtySession(cmd *exec.Cmd, winCh <-chan ssh.Window, logCallback func()) (io.WriteCloser, io.ReadCloser, error) { func (s *SSHServer) startPtySession(cmd *exec.Cmd, winCh <-chan ssh.Window, logCallback func()) (io.ReadWriteCloser, error) {
tty, err := pty.Start(cmd) tty, err := pty.Start(cmd)
if err != nil { if err != nil {
return nil, nil, err return nil, err
} }
// Handle terminal window size changes // Handle terminal window size changes
@ -307,7 +325,7 @@ func (s *SSHServer) startPtySession(cmd *exec.Cmd, winCh <-chan ssh.Window, logC
} }
}() }()
return tty, tty, nil return tty, nil
} }
func (s *SSHServer) logAuditEvent(writer io.WriteCloser, session ssh.Session, sessionID string, eventType string) { func (s *SSHServer) logAuditEvent(writer io.WriteCloser, session ssh.Session, sessionID string, eventType string) {
@ -328,11 +346,14 @@ func (s *SSHServer) logAuditEvent(writer io.WriteCloser, session ssh.Session, se
} }
data, err := json.Marshal(&event) data, err := json.Marshal(&event)
if err != nil { if err != nil {
s.logger.WithError(err).Error("Failed to log audit event. malformed audit object") s.logger.WithError(err).Error("Failed to marshal audit event. malformed audit object")
return return
} }
line := string(data) + "\n" line := string(data) + "\n"
writer.Write([]byte(line)) if _, err := writer.Write([]byte(line)); err != nil {
s.logger.WithError(err).Error("Failed to write audit event.")
}
} }
// Sets PTY window size for terminal // Sets PTY window size for terminal