From fb20de03188b37b5c5d6b4109ab63df76ec27d82 Mon Sep 17 00:00:00 2001 From: Andrew Burke <31974658+atburke@users.noreply.github.com> Date: Thu, 2 Dec 2021 12:48:27 -0800 Subject: [PATCH] Clear web terminal when session ends (#8850) This change clears the screen when an ssh session ends (only in FIPS mode). Note: This doesn't currently do anything in `tsh` on Windows since BoringCrypto isn't supported, but once it is supported, the behavior will match Unix and web. Co-authored-by: Grzegorz Co-authored-by: Russell Jones --- lib/client/session.go | 24 +++++++++ lib/client/terminal/terminal_common.go | 69 ++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 lib/client/terminal/terminal_common.go diff --git a/lib/client/session.go b/lib/client/session.go index bf08586fefebc..658a61d472b0c 100644 --- a/lib/client/session.go +++ b/lib/client/session.go @@ -26,6 +26,7 @@ import ( "os" "os/signal" "strings" + "sync" "syscall" "time" @@ -68,6 +69,10 @@ type NodeSession struct { // this session. It's also used to wait for everyone to close closer *utils.CloseBroadcaster + // closeWait is used to wait for cleanup-related goroutines created by + // this session to close. + closeWait *sync.WaitGroup + ExitMsg string enableEscapeSequences bool @@ -108,6 +113,7 @@ func newSession(client *NodeClient, stderr: stderr, namespace: client.Namespace, closer: utils.NewCloseBroadcaster(), + closeWait: &sync.WaitGroup{}, enableEscapeSequences: enableEscapeSequences, } // if we're joining an existing session, we need to assume that session's @@ -140,6 +146,24 @@ func newSession(client *NodeClient, ns.id = session.ID(sid) } ns.env[sshutils.SessionEnvVar] = string(ns.id) + + // Close the Terminal when finished. + ns.closeWait.Add(1) + go func() { + defer ns.closeWait.Done() + + <-ns.closer.C + if isFIPS() { + // \x1b[3J - clears scrollback (it is needed at least for the Mac terminal) - + // https://newbedev.com/how-do-i-reset-the-scrollback-in-the-terminal-via-a-shell-command + // \x1b\x63 - clears current screen - same as '\0033\0143' from https://superuser.com/a/123007 + const resetPattern = "\x1b[3J\x1b\x63\n" + if _, err := ns.stdout.Write([]byte(resetPattern)); err != nil { + log.Warnf("Failed to clear screen: %v.", err) + } + } + }() + return ns, nil } diff --git a/lib/client/terminal/terminal_common.go b/lib/client/terminal/terminal_common.go new file mode 100644 index 0000000000000..bb78fedf43368 --- /dev/null +++ b/lib/client/terminal/terminal_common.go @@ -0,0 +1,69 @@ +/* +Copyright 2021 Gravitational, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ +package terminal + +import ( + "sync" + + "github.com/gravitational/teleport" + "github.com/gravitational/trace" + "github.com/sirupsen/logrus" +) + +var log = logrus.WithFields(logrus.Fields{ + trace.Component: teleport.ComponentClient, +}) + +// ResizeEvent is emitted when a terminal window is resized. +type ResizeEvent struct{} + +// StopEvent is emitted when the user sends a SIGSTOP +type StopEvent struct{} + +type signalEmitter struct { + subscribers []chan interface{} + subscribersMutex sync.Mutex +} + +// Subscribe creates a channel that will receive terminal events. +func (e *signalEmitter) Subscribe() chan interface{} { + e.subscribersMutex.Lock() + defer e.subscribersMutex.Unlock() + + ch := make(chan interface{}) + e.subscribers = append(e.subscribers, ch) + + return ch +} + +func (e *signalEmitter) writeEvent(event interface{}) { + e.subscribersMutex.Lock() + defer e.subscribersMutex.Unlock() + + for _, sub := range e.subscribers { + sub <- event + } +} + +func (e *signalEmitter) clearSubscribers() { + e.subscribersMutex.Lock() + defer e.subscribersMutex.Unlock() + + for _, ch := range e.subscribers { + close(ch) + } + e.subscribers = e.subscribers[:0] +}