ssh/tailssh: handle session recording when running in userspace mode

Previously it would dial out using the http.DefaultClient, however that doesn't work
when tailscaled is running in userspace mode (e.g. when testing).

Updates tailscale/corp#9967

Signed-off-by: Maisem Ali <maisem@tailscale.com>
maisem/k8s-cache
Maisem Ali 2 years ago committed by Maisem Ali
parent df89b7de10
commit 583e86b7df

@ -35,6 +35,7 @@ import (
"tailscale.com/ipn/ipnlocal" "tailscale.com/ipn/ipnlocal"
"tailscale.com/logtail/backoff" "tailscale.com/logtail/backoff"
"tailscale.com/net/tsaddr" "tailscale.com/net/tsaddr"
"tailscale.com/net/tsdial"
"tailscale.com/tailcfg" "tailscale.com/tailcfg"
"tailscale.com/tempfork/gliderlabs/ssh" "tailscale.com/tempfork/gliderlabs/ssh"
"tailscale.com/types/logger" "tailscale.com/types/logger"
@ -62,6 +63,7 @@ type ipnLocalBackend interface {
NetMap() *netmap.NetworkMap NetMap() *netmap.NetworkMap
WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool) WhoIs(ipp netip.AddrPort) (n *tailcfg.Node, u tailcfg.UserProfile, ok bool)
DoNoiseRequest(req *http.Request) (*http.Response, error) DoNoiseRequest(req *http.Request) (*http.Response, error)
Dialer() *tsdial.Dialer
} }
type server struct { type server struct {
@ -76,11 +78,33 @@ type server struct {
// mu protects the following // mu protects the following
mu sync.Mutex mu sync.Mutex
httpc *http.Client // for calling out to peers.
activeConns map[*conn]bool // set; value is always true activeConns map[*conn]bool // set; value is always true
fetchPublicKeysCache map[string]pubKeyCacheEntry // by https URL fetchPublicKeysCache map[string]pubKeyCacheEntry // by https URL
shutdownCalled bool shutdownCalled bool
} }
// sessionRecordingClient returns an http.Client that uses srv.lb.Dialer() to
// dial connections. This is used to make requests to the session recording
// server to upload session recordings.
func (srv *server) sessionRecordingClient() *http.Client {
srv.mu.Lock()
defer srv.mu.Unlock()
if srv.httpc != nil {
return srv.httpc
}
tr := http.DefaultTransport.(*http.Transport).Clone()
tr.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
return srv.lb.Dialer().UserDial(ctx, network, addr)
}
srv.httpc = &http.Client{
Transport: tr,
}
return srv.httpc
}
func (srv *server) now() time.Time { func (srv *server) now() time.Time {
if srv != nil && srv.timeNow != nil { if srv != nil && srv.timeNow != nil {
return srv.timeNow() return srv.timeNow()
@ -1404,9 +1428,8 @@ func (ss *sshSession) startNewRecording() (_ *recording, err error) {
go func() { go func() {
defer pw.Close() defer pw.Close()
ss.logf("starting asciinema recording to %s", recorder) ss.logf("starting asciinema recording to %s", recorder)
hc := ss.conn.srv.sessionRecordingClient()
// We just use the default client here, which has a 30s dial timeout. resp, err := hc.Do(req)
resp, err := http.DefaultClient.Do(req)
if err != nil { if err != nil {
ss.cancelCtx(err) ss.cancelCtx(err)
ss.logf("recording: error sending recording to %s: %v", recorder, err) ss.logf("recording: error sending recording to %s: %v", recorder, err)

@ -237,6 +237,10 @@ var (
testSignerOnce sync.Once testSignerOnce sync.Once
) )
func (ts *localState) Dialer() *tsdial.Dialer {
return nil
}
func (ts *localState) GetSSH_HostKeys() ([]gossh.Signer, error) { func (ts *localState) GetSSH_HostKeys() ([]gossh.Signer, error) {
testSignerOnce.Do(func() { testSignerOnce.Do(func() {
_, priv, err := ed25519.GenerateKey(rand.Reader) _, priv, err := ed25519.GenerateKey(rand.Reader)

Loading…
Cancel
Save