diff --git a/constants.go b/constants.go index c409c228f572b..efd1f3126b212 100644 --- a/constants.go +++ b/constants.go @@ -475,6 +475,10 @@ const ( // SessionEvent is sent by servers to clients when an audit event occurs on // the session. SessionEvent = "x-teleport-event" + + // VersionRequest is sent by clients to server requesting the Teleport + // version they are running. + VersionRequest = "x-teleport-version" ) const ( diff --git a/lib/client/api.go b/lib/client/api.go index a1b40dafd7623..8cfdd19848d97 100644 --- a/lib/client/api.go +++ b/lib/client/api.go @@ -1390,7 +1390,7 @@ func (tc *TeleportClient) runCommand( if len(nodeAddresses) > 1 { fmt.Printf("Running command on %v:\n", address) } - nodeSession, err = newSession(nodeClient, nil, tc.Config.Env, tc.Stdin, tc.Stdout, tc.Stderr) + nodeSession, err = newSession(nodeClient, nil, tc.Config.Env, tc.Stdin, tc.Stdout, tc.Stderr, tc.useLegacyID(nodeClient)) if err != nil { log.Error(err) return @@ -1424,7 +1424,7 @@ func (tc *TeleportClient) runCommand( // runShell starts an interactive SSH session/shell. // sessionID : when empty, creates a new shell. otherwise it tries to join the existing session. func (tc *TeleportClient) runShell(nodeClient *NodeClient, sessToJoin *session.Session) error { - nodeSession, err := newSession(nodeClient, sessToJoin, tc.Env, tc.Stdin, tc.Stdout, tc.Stderr) + nodeSession, err := newSession(nodeClient, sessToJoin, tc.Env, tc.Stdin, tc.Stdout, tc.Stderr, tc.useLegacyID(nodeClient)) if err != nil { return trace.Wrap(err) } @@ -2032,6 +2032,49 @@ func (tc *TeleportClient) AskPassword() (pwd string, err error) { return pwd, nil } +// DELETE IN: 4.1.0 +// +// useLegacyID returns true if an old style (UUIDv1) session ID should be +// generated because the client is talking with a older server. +func (tc *TeleportClient) useLegacyID(nodeClient *NodeClient) bool { + _, err := tc.getServerVersion(nodeClient) + if trace.IsNotFound(err) { + return true + } + return false +} + +type serverResponse struct { + version string + err error +} + +// getServerVersion makes a SSH global request to the server to request the +// version. +func (tc *TeleportClient) getServerVersion(nodeClient *NodeClient) (string, error) { + responseCh := make(chan serverResponse) + + go func() { + ok, payload, err := nodeClient.Client.SendRequest(teleport.VersionRequest, true, nil) + if err != nil { + responseCh <- serverResponse{err: trace.NotFound(err.Error())} + } else if !ok { + responseCh <- serverResponse{err: trace.NotFound("server does not support version request")} + } + responseCh <- serverResponse{version: string(payload)} + }() + + select { + case resp := <-responseCh: + if resp.err != nil { + return "", trace.Wrap(resp.err) + } + return resp.version, nil + case <-time.After(500 * time.Millisecond): + return "", trace.NotFound("timed out waiting for server response") + } +} + // passwordFromConsole reads from stdin without echoing typed characters to stdout func passwordFromConsole() (string, error) { fd := syscall.Stdin diff --git a/lib/client/client_test.go b/lib/client/client_test.go index aaa6d1bbb9ce2..cca4dc20d1a61 100644 --- a/lib/client/client_test.go +++ b/lib/client/client_test.go @@ -56,7 +56,7 @@ func (s *ClientTestSuite) TestNewSession(c *check.C) { } // defaults: - ses, err := newSession(nc, nil, nil, nil, nil, nil) + ses, err := newSession(nc, nil, nil, nil, nil, nil, false) c.Assert(err, check.IsNil) c.Assert(ses, check.NotNil) c.Assert(ses.NodeClient(), check.Equals, nc) @@ -70,7 +70,7 @@ func (s *ClientTestSuite) TestNewSession(c *check.C) { env := map[string]string{ sshutils.SessionEnvVar: "session-id", } - ses, err = newSession(nc, nil, env, nil, nil, nil) + ses, err = newSession(nc, nil, env, nil, nil, nil, false) c.Assert(err, check.IsNil) c.Assert(ses, check.NotNil) c.Assert(ses.env, check.DeepEquals, env) diff --git a/lib/client/session.go b/lib/client/session.go index 05e6ae0d82f41..71956e1d2024f 100644 --- a/lib/client/session.go +++ b/lib/client/session.go @@ -77,7 +77,8 @@ func newSession(client *NodeClient, env map[string]string, stdin io.Reader, stdout io.Writer, - stderr io.Writer) (*NodeSession, error) { + stderr io.Writer, + legacyID bool) (*NodeSession, error) { if stdin == nil { stdin = os.Stdin @@ -119,7 +120,15 @@ func newSession(client *NodeClient, } else { sid, ok := ns.env[sshutils.SessionEnvVar] if !ok { - sid = string(session.NewID()) + // DELETE IN: 4.1.0. + // + // Always send UUIDv4 after 4.1. + if legacyID { + sid = string(session.NewLegacyID()) + } else { + sid = string(session.NewID()) + } + } ns.id = session.ID(sid) } diff --git a/lib/session/session.go b/lib/session/session.go index 47ecfc58d49aa..15e56598cfcbb 100644 --- a/lib/session/session.go +++ b/lib/session/session.go @@ -71,6 +71,13 @@ func NewID() ID { return ID(uuid.New()) } +// DELETE IN: 4.1.0. +// +// NewLegacyID creates a new session ID in the UUIDv1 legacy format. +func NewLegacyID() ID { + return ID(uuid.NewUUID().String()) +} + // Session is an interactive collaboration session that represents one // or many SSH session started by teleport user type Session struct { diff --git a/lib/srv/forward/sshserver.go b/lib/srv/forward/sshserver.go index 6e3567709ec9b..b4668b95b9637 100644 --- a/lib/srv/forward/sshserver.go +++ b/lib/srv/forward/sshserver.go @@ -576,6 +576,16 @@ func (s *Server) rejectChannel(chans <-chan ssh.NewChannel, err error) { } func (s *Server) handleGlobalRequest(req *ssh.Request) { + // Version requests are internal Teleport requests, they should not be + // forwarded to the remote server. + if req.Type == teleport.VersionRequest { + err := req.Reply(true, []byte(teleport.Version)) + if err != nil { + s.log.Debugf("Failed to reply to version request: %v.", err) + } + return + } + ok, payload, err := s.remoteClient.SendRequest(req.Type, req.WantReply, req.Payload) if err != nil { s.log.Warnf("Failed to forward global request %v: %v", req.Type, err) diff --git a/lib/srv/regular/sshserver.go b/lib/srv/regular/sshserver.go index 5abf1fe0fd414..547ceb2b39953 100644 --- a/lib/srv/regular/sshserver.go +++ b/lib/srv/regular/sshserver.go @@ -789,6 +789,8 @@ func (s *Server) HandleRequest(r *ssh.Request) { s.handleKeepAlive(r) case teleport.RecordingProxyReqType: s.handleRecordingProxy(r) + case teleport.VersionRequest: + s.handleVersionRequest(r) default: if r.WantReply { r.Reply(false, nil) @@ -1273,6 +1275,14 @@ func (s *Server) handleRecordingProxy(req *ssh.Request) { log.Debugf("Replied to global request (%v, %v): %v", req.Type, req.WantReply, recordingProxy) } +// handleVersionRequest replies with the Teleport version of the server. +func (s *Server) handleVersionRequest(req *ssh.Request) { + err := req.Reply(true, []byte(teleport.Version)) + if err != nil { + log.Debugf("Failed to reply to version request: %v.", err) + } +} + // handleProxyJump handles ProxyJump request that is executed via direct tcp-ip dial on the proxy func (s *Server) handleProxyJump(conn net.Conn, sconn *ssh.ServerConn, identityContext srv.IdentityContext, ch ssh.Channel, req sshutils.DirectTCPIPReq) { // Create context for this channel. This context will be closed when the