mirror of
https://github.com/tailscale/tailscale.git
synced 2025-02-18 02:48:40 +00:00
ssh/tailssh: restore support for recording locally
We removed it earlier in 916aa782af5d43ccfa92f6245201796df212fb8a, but we still want to support it for some time longer. Updates tailscale/corp#9967 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
4d7927047c
commit
be190e990f
@ -67,6 +67,7 @@ type ipnLocalBackend interface {
|
||||
WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool)
|
||||
DoNoiseRequest(req *http.Request) (*http.Response, error)
|
||||
Dialer() *tsdial.Dialer
|
||||
TailscaleVarRoot() string
|
||||
}
|
||||
|
||||
type server struct {
|
||||
@ -1154,6 +1155,11 @@ func (ss *sshSession) run() {
|
||||
return
|
||||
}
|
||||
|
||||
// recordSSHToLocalDisk is a deprecated dev knob to allow recording SSH sessions
|
||||
// to local storage. It is only used if there is no recording configured by the
|
||||
// coordination server. This will be removed in the future.
|
||||
var recordSSHToLocalDisk = envknob.RegisterBool("TS_DEBUG_LOG_SSH")
|
||||
|
||||
// recorders returns the list of recorders to use for this session.
|
||||
// If the final action has a non-empty list of recorders, that list is
|
||||
// returned. Otherwise, the list of recorders from the initial action
|
||||
@ -1167,7 +1173,7 @@ func (ss *sshSession) recorders() ([]netip.AddrPort, *tailcfg.SSHRecorderFailure
|
||||
|
||||
func (ss *sshSession) shouldRecord() bool {
|
||||
recs, _ := ss.recorders()
|
||||
return len(recs) > 0
|
||||
return len(recs) > 0 || recordSSHToLocalDisk()
|
||||
}
|
||||
|
||||
type sshConnInfo struct {
|
||||
@ -1510,12 +1516,33 @@ func (ss *sshSession) connectToRecorder(ctx context.Context, recs []netip.AddrPo
|
||||
return nil, nil, multierr.New(errs...)
|
||||
}
|
||||
|
||||
func (ss *sshSession) openFileForRecording(now time.Time) (_ io.WriteCloser, err error) {
|
||||
varRoot := ss.conn.srv.lb.TailscaleVarRoot()
|
||||
if varRoot == "" {
|
||||
return nil, errors.New("no var root for recording storage")
|
||||
}
|
||||
dir := filepath.Join(varRoot, "ssh-sessions")
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f, err := os.CreateTemp(dir, fmt.Sprintf("ssh-session-%v-*.cast", now.UnixNano()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// startNewRecording starts a new SSH session recording.
|
||||
// It may return a nil recording if recording is not available.
|
||||
func (ss *sshSession) startNewRecording() (_ *recording, err error) {
|
||||
recorders, onFailure := ss.recorders()
|
||||
var localRecording bool
|
||||
if len(recorders) == 0 {
|
||||
return nil, errors.New("no recorders configured")
|
||||
if recordSSHToLocalDisk() {
|
||||
localRecording = true
|
||||
} else {
|
||||
return nil, errors.New("no recorders configured")
|
||||
}
|
||||
}
|
||||
|
||||
var w ssh.Window
|
||||
@ -1539,40 +1566,45 @@ func (ss *sshSession) startNewRecording() (_ *recording, err error) {
|
||||
// ss.ctx is closed when the session closes, but we don't want to break the upload at that time.
|
||||
// Instead we want to wait for the session to close the writer when it finishes.
|
||||
ctx := context.Background()
|
||||
wc, errChan, err := ss.connectToRecorder(ctx, recorders)
|
||||
if err != nil {
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.RejectSessionWithMessage != "" {
|
||||
ss.logf("recording: error starting recording (rejecting session): %v", err)
|
||||
return nil, userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.RejectSessionWithMessage,
|
||||
if localRecording {
|
||||
rec.out, err = ss.openFileForRecording(now)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
var errChan <-chan error
|
||||
rec.out, errChan, err = ss.connectToRecorder(ctx, recorders)
|
||||
if err != nil {
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.RejectSessionWithMessage != "" {
|
||||
ss.logf("recording: error starting recording (rejecting session): %v", err)
|
||||
return nil, userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.RejectSessionWithMessage,
|
||||
}
|
||||
}
|
||||
ss.logf("recording: error starting recording (failing open): %v", err)
|
||||
return nil, nil
|
||||
}
|
||||
ss.logf("recording: error starting recording (failing open): %v", err)
|
||||
return nil, nil
|
||||
go func() {
|
||||
err := <-errChan
|
||||
if err == nil {
|
||||
// Success.
|
||||
return
|
||||
}
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.TerminateSessionWithMessage != "" {
|
||||
ss.logf("recording: error uploading recording (closing session): %v", err)
|
||||
ss.cancelCtx(userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.TerminateSessionWithMessage,
|
||||
})
|
||||
return
|
||||
}
|
||||
ss.logf("recording: error uploading recording (failing open): %v", err)
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := <-errChan
|
||||
if err == nil {
|
||||
// Success.
|
||||
return
|
||||
}
|
||||
// TODO(catzkorn): notify control here.
|
||||
if onFailure != nil && onFailure.TerminateSessionWithMessage != "" {
|
||||
ss.logf("recording: error uploading recording (closing session): %v", err)
|
||||
ss.cancelCtx(userVisibleError{
|
||||
error: err,
|
||||
msg: onFailure.TerminateSessionWithMessage,
|
||||
})
|
||||
return
|
||||
}
|
||||
ss.logf("recording: error uploading recording (failing open): %v", err)
|
||||
}()
|
||||
|
||||
rec.out = wc
|
||||
|
||||
ch := CastHeader{
|
||||
Version: 2,
|
||||
Width: w.Width,
|
||||
|
Loading…
x
Reference in New Issue
Block a user