Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 09d0b63

Browse files
Maisem Alimaisem
authored andcommitted
ssh/tailssh: add session recording test for non-pty sessions
Updates tailscale/corp#9967 Signed-off-by: Maisem Ali <[email protected]>
1 parent d39a5e4 commit 09d0b63

1 file changed

Lines changed: 96 additions & 2 deletions

File tree

ssh/tailssh/tailssh_test.go

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ package tailssh
77

88
import (
99
"bytes"
10+
"context"
1011
"crypto/ed25519"
1112
"crypto/rand"
1213
"crypto/sha256"
1314
"encoding/json"
1415
"errors"
1516
"fmt"
1617
"io"
18+
"io/ioutil"
1719
"net"
1820
"net/http"
1921
"net/http/httptest"
@@ -324,9 +326,101 @@ func newSSHRule(action *tailcfg.SSHAction) *tailcfg.SSHRule {
324326
}
325327
}
326328

329+
// TestSSHRecordingNonInteractive tests that the SSH server records the SSH session
330+
// when the client is not interactive (i.e. no PTY).
331+
// It starts a local SSH server and a recording server. The recording server
332+
// records the SSH session and returns it to the test.
333+
// The test then verifies that the recording has a valid CastHeader, it does not
334+
// validate the contents of the recording.
335+
func TestSSHRecordingNonInteractive(t *testing.T) {
336+
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
337+
t.Skipf("skipping on %q; only runs on linux and darwin", runtime.GOOS)
338+
}
339+
var recording []byte
340+
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
341+
recordingServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
342+
defer cancel()
343+
var err error
344+
recording, err = ioutil.ReadAll(r.Body)
345+
if err != nil {
346+
t.Error(err)
347+
return
348+
}
349+
w.WriteHeader(http.StatusOK)
350+
}))
351+
defer recordingServer.Close()
352+
353+
state := &localState{
354+
sshEnabled: true,
355+
matchingRule: newSSHRule(
356+
&tailcfg.SSHAction{
357+
Accept: true,
358+
Recorders: []netip.AddrPort{
359+
must.Get(netip.ParseAddrPort(recordingServer.Listener.Addr().String())),
360+
},
361+
},
362+
),
363+
}
364+
s := &server{
365+
logf: t.Logf,
366+
httpc: recordingServer.Client(),
367+
}
368+
defer s.Shutdown()
369+
370+
src, dst := must.Get(netip.ParseAddrPort("100.100.100.101:2231")), must.Get(netip.ParseAddrPort("100.100.100.102:22"))
371+
sc, dc := memnet.NewTCPConn(src, dst, 1024)
372+
s.lb = state
373+
374+
const sshUser = "alice"
375+
cfg := &gossh.ClientConfig{
376+
User: sshUser,
377+
HostKeyCallback: gossh.InsecureIgnoreHostKey(),
378+
}
379+
380+
var wg sync.WaitGroup
381+
wg.Add(1)
382+
go func() {
383+
defer wg.Done()
384+
c, chans, reqs, err := gossh.NewClientConn(sc, sc.RemoteAddr().String(), cfg)
385+
if err != nil {
386+
t.Errorf("client: %v", err)
387+
return
388+
}
389+
client := gossh.NewClient(c, chans, reqs)
390+
defer client.Close()
391+
session, err := client.NewSession()
392+
if err != nil {
393+
t.Errorf("client: %v", err)
394+
return
395+
}
396+
defer session.Close()
397+
t.Logf("client established session")
398+
_, err = session.CombinedOutput("echo Ran echo!")
399+
if err != nil {
400+
t.Errorf("client: %v", err)
401+
}
402+
}()
403+
if err := s.HandleSSHConn(dc); err != nil {
404+
t.Errorf("unexpected error: %v", err)
405+
}
406+
wg.Wait()
407+
408+
<-ctx.Done() // wait for recording to finish
409+
var ch CastHeader
410+
if err := json.NewDecoder(bytes.NewReader(recording)).Decode(&ch); err != nil {
411+
t.Fatal(err)
412+
}
413+
if ch.SSHUser != sshUser {
414+
t.Errorf("SSHUser = %q; want %q", ch.SSHUser, sshUser)
415+
}
416+
if ch.Command != "echo Ran echo!" {
417+
t.Errorf("Command = %q; want %q", ch.Command, "echo Ran echo!")
418+
}
419+
}
420+
327421
func TestSSHAuthFlow(t *testing.T) {
328-
if runtime.GOOS != "linux" {
329-
t.Skip("Not running on Linux, skipping")
422+
if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
423+
t.Skipf("skipping on %q; only runs on linux and darwin", runtime.GOOS)
330424
}
331425
acceptRule := newSSHRule(&tailcfg.SSHAction{
332426
Accept: true,

0 commit comments

Comments
 (0)