diff --git a/go.mod b/go.mod index d845750718e9b..c34584934a941 100644 --- a/go.mod +++ b/go.mod @@ -157,6 +157,7 @@ require ( github.com/opensearch-project/opensearch-go/v2 v2.3.0 github.com/parquet-go/parquet-go v0.23.0 github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 + github.com/pborman/ansi v1.0.0 github.com/pelletier/go-toml v1.9.5 github.com/pkg/sftp v1.13.6 github.com/pquerna/otp v1.4.0 diff --git a/go.sum b/go.sum index f83eb553e710d..54292184ce9dd 100644 --- a/go.sum +++ b/go.sum @@ -1995,6 +1995,8 @@ github.com/paulmach/orb v0.11.1/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/En github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0 h1:2nosf3P75OZv2/ZO/9Px5ZgZ5gbKrzA3joN1QMfOGMQ= github.com/pavlo-v-chernykh/keystore-go/v4 v4.5.0/go.mod h1:lAVhWwbNaveeJmxrxuSTxMgKpF6DjnuVpn6T8WiBwYQ= +github.com/pborman/ansi v1.0.0 h1:OqjHMhvlSuCCV5JT07yqPuJPQzQl+WXsiZ14gZsqOrQ= +github.com/pborman/ansi v1.0.0/go.mod h1:SgWzwMAx1X/Ez7i90VqF8LRiQtx52pWDiQP+x3iGnzw= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= diff --git a/tool/tsh/common/play.go b/tool/tsh/common/play.go index 14900ce485871..0387a13d0abcf 100644 --- a/tool/tsh/common/play.go +++ b/tool/tsh/common/play.go @@ -30,6 +30,7 @@ import ( "github.com/ghodss/yaml" "github.com/gravitational/trace" + "github.com/pborman/ansi" "github.com/gravitational/teleport" apievents "github.com/gravitational/teleport/api/types/events" @@ -115,7 +116,7 @@ func exportSession(cf *CLIConf) error { } switch format { - case teleport.JSON, teleport.YAML: + case teleport.JSON, teleport.YAML, teleport.Text: default: return trace.Errorf("Invalid format %s, only json and yaml are supported", format) } @@ -144,6 +145,8 @@ func exportSession(cf *CLIConf) error { exporter = jsonSessionExporter{} case teleport.YAML: exporter = yamlSessionExporter{} + case teleport.Text: + exporter = textSessionExporter{} } exporter.WriteStart() @@ -233,6 +236,31 @@ func (yamlSessionExporter) WriteEvent(evt apievents.AuditEvent) error { return err } +type textSessionExporter struct{} + +func (textSessionExporter) WriteStart() error { return nil } +func (textSessionExporter) WriteEnd() error { return nil } +func (textSessionExporter) WriteSeparator() error { return nil } + +func (textSessionExporter) WriteEvent(evt apievents.AuditEvent) error { + printEvent, ok := evt.(*apievents.SessionPrint) + if !ok { + return nil + } + + var writeErr error + + // strip ANSI control characters + if stripped, err := ansi.Strip(printEvent.Data); err != nil { + // best we can do is just write the original data + _, writeErr = os.Stdout.Write(printEvent.Data) + } else { + _, writeErr = os.Stdout.Write(stripped) + } + + return writeErr +} + // exportFile converts the binary protobuf events from the file // identified by path to text (JSON/YAML) and writes the converted // events to standard out. diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index 500dcab53c79f..79bf18f491934 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -983,8 +983,8 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { play.Flag("speed", "Playback speed, applicable when streaming SSH or Kubernetes sessions.").Default("1x").EnumVar(&cf.PlaySpeed, "0.5x", "1x", "2x", "4x", "8x") play.Flag("skip-idle-time", "Quickly skip over idle time, applicable when streaming SSH or Kubernetes sessions.").BoolVar(&cf.NoWait) play.Flag("format", defaults.FormatFlagDescription( - teleport.PTY, teleport.JSON, teleport.YAML, - )).Short('f').Default(teleport.PTY).EnumVar(&cf.Format, teleport.PTY, teleport.JSON, teleport.YAML) + teleport.PTY, teleport.JSON, teleport.YAML, teleport.Text, + )).Short('f').Default(teleport.PTY).EnumVar(&cf.Format, teleport.PTY, teleport.JSON, teleport.YAML, teleport.Text) play.Arg("session-id", "ID or path to session file to play").Required().StringVar(&cf.SessionID) // scp