Skip to content

Commit

Permalink
Convert tsh play to a streaming API (#34547)
Browse files Browse the repository at this point in the history
* Convert tsh play to use the new streaming API

Instead of downloading the entire session before starting playback,
tsh now streams the recording directly to the terminal. This results
in a significant reduction in "time to first byte" when playing back
large session recordings.

This has the side effect of making playback of SSH consistent with
that of desktop sessions (we no longer "skip" dead time for SSH
sessions and instead play them back at true speed). Additionally,
a new CLI flag was added to support configurable playback speeds.

Fixes #10579
Closes #11385
Updates gravitational/teleport-private#1024

* Start to phase out the GetSessionEvents API

- Mark the old HTTP client implementations as not implemented.
  Nothing is calling these, but they remain for now until we can
  fully remove them from the ClientI interface.
- Move `tsh play --format=json` over to a streaming approach.
  This ensures that we don't load the entire session into memory
  before converting it to JSON/YAML format.

* Clean up terminal escape codes

- Move more utilities into lib/client.Terminal
- Stop timestamping the frame by moving the cursor and writing
  to the terminal - this is racy. Write the timestamp to the
  terminal title instead.

* Update changelog

* Move terminal functions from Unix to common build

This is valid because we initialize terminals on Windows with
ENABLE_VIRTUAL_TERMINAL_PROCESSING, so the same escape sequences
that work on Unix will also work on Windows.

This matches Microsoft's recommendation to use virtual terminal
sequences over the Windows Console API [1].

For more info, see: https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences

[1] https://learn.microsoft.com/en-us/windows/console/classic-vs-vt
  • Loading branch information
zmb3 authored Jan 2, 2024
1 parent d343684 commit 25ee7b1
Show file tree
Hide file tree
Showing 13 changed files with 451 additions and 823 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ rely on parsing the output from multiple nodes should pass the `--log-dir` flag
to `tsh ssh`, which will create a directory where the separated output of each node
will be written.

#### `tsh play` now streams PTY playback

Prior to Teleport 15, `tsh play` would download the entire session recording
before starting playback. As a result, playback of large recordings could be
slow to start. In Teleport 15 session recordings are streamed from the auth
server, allowing playback to start before the entire session is downloaded and
unpacked.

Additionally, `tsh play` now supports a `--speed` flag for adjusting the
playback speed.

#### `drop` host user creation mode

The `drop` host user creation mode has been removed in Teleport 15. It is replaced
Expand Down
8 changes: 3 additions & 5 deletions docs/pages/connect-your-client/tsh.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ to call [`tsh login`](../reference/cli/tsh.mdx#tsh-login) in the beginning.

## Installing tsh

Follow the instructions below to install the `tsh` binary.
Follow the instructions below to install the `tsh` binary.

We recommend installing `tsh` of the same major version as the version used in
your Teleport cluster.
your Teleport cluster.

To find the version number, either:

Expand All @@ -97,7 +97,7 @@ To find the version number, either:
- Use `curl` and `jq`. Replace <Var name="teleport.example.com" />
with your Proxy Service address (e.g. `mytenant.teleport.sh` for Teleport
Enterprise Cloud):

```code
$ curl https://<Var name="teleport.example.com" />/webapi/find | jq '.server_version'
"(=teleport.version=)"
Expand Down Expand Up @@ -934,8 +934,6 @@ tmpfs 1982720 0 1982720 0% /proc/acpi
tmpfs 1982720 0 1982720 0% /sys/firmware
root@ubuntu:/# exit
exit
end of session playback
```

## tsh configuration files
Expand Down
11 changes: 7 additions & 4 deletions lib/auth/auth_with_roles_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1734,8 +1734,9 @@ func TestStreamSessionEvents_Builtin(t *testing.T) {
require.Empty(t, searchEvents)
}

// TestGetSessionEvents ensures that when a user streams a session's events, it emits an audit event.
func TestGetSessionEvents(t *testing.T) {
// TestStreamSessionEvents ensures that when a user streams a session's events
// a "session recording access" event is emitted.
func TestStreamSessionEvents(t *testing.T) {
t.Parallel()

srv := newTestTLSServer(t)
Expand All @@ -1748,12 +1749,14 @@ func TestGetSessionEvents(t *testing.T) {
clt, err := srv.NewClient(identity)
require.NoError(t, err)

ctx, cancel := context.WithCancel(context.Background())
t.Cleanup(cancel)

// ignore the response as we don't want the events or the error (the session will not exist)
_, _ = clt.GetSessionEvents(apidefaults.Namespace, "44c6cea8-362f-11ea-83aa-125400432324", 0)
clt.StreamSessionEvents(ctx, session.ID("44c6cea8-362f-11ea-83aa-125400432324"), 0)

// we need to wait for a short period to ensure the event is returned
time.Sleep(500 * time.Millisecond)
ctx := context.Background()
searchEvents, _, err := srv.AuthServer.AuditLog.SearchEvents(ctx, events.SearchEventsRequest{
From: srv.Clock().Now().Add(-time.Hour),
To: srv.Clock().Now().Add(time.Hour),
Expand Down
11 changes: 6 additions & 5 deletions lib/auth/http_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,10 @@ func (c *HTTPClient) ValidateGithubAuthCallback(ctx context.Context, q url.Value
// GetSessionChunk allows clients to receive a byte array (chunk) from a recorded
// session stream, starting from 'offset', up to 'max' in length. The upper bound
// of 'max' is set to events.MaxChunkBytes
//
// Deprecated: use StreamSessionEvents API instead
func (c *HTTPClient) GetSessionChunk(namespace string, sid session.ID, offsetBytes, maxBytes int) ([]byte, error) {
// DELETE IN 16(zmb3): v15 web UIs stopped calling this
if namespace == "" {
return nil, trace.BadParameter(MissingNamespaceError)
}
Expand All @@ -867,12 +870,10 @@ func (c *HTTPClient) GetSessionChunk(namespace string, sid session.ID, offsetByt
return response.Bytes(), nil
}

// Returns events that happen during a session sorted by time
// (oldest first).
//
// afterN allows to filter by "newer than N" value where N is the cursor ID
// of previously returned bunch (good for polling for latest)
// Deprecated: use StreamSessionEvents API instead.
// TODO(zmb3): remove from ClientI interface
func (c *HTTPClient) GetSessionEvents(namespace string, sid session.ID, afterN int) (retval []events.EventFields, err error) {
// DELETE IN 16(zmb3): v15 web UIs stopped calling this
if namespace == "" {
return nil, trace.BadParameter(MissingNamespaceError)
}
Expand Down
Loading

0 comments on commit 25ee7b1

Please sign in to comment.