mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
ssh/tailssh: terminate sessions when tailscaled shutsdown
Ideally we would re-establish these sessions when tailscaled comes back up, however we do not do that yet so this is better than leaking the sessions. Updates #3802 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
760740905e
commit
7cd8c3e839
@ -81,6 +81,9 @@ type SSHServer interface {
|
|||||||
// so that existing sessions can be re-evaluated for validity
|
// so that existing sessions can be re-evaluated for validity
|
||||||
// and closed if they'd no longer be accepted.
|
// and closed if they'd no longer be accepted.
|
||||||
OnPolicyChange()
|
OnPolicyChange()
|
||||||
|
|
||||||
|
// Shutdown is called when tailscaled is shutting down.
|
||||||
|
Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
type newSSHServerFunc func(logger.Logf, *LocalBackend) (SSHServer, error)
|
type newSSHServerFunc func(logger.Logf, *LocalBackend) (SSHServer, error)
|
||||||
@ -346,6 +349,9 @@ func (b *LocalBackend) Shutdown() {
|
|||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.shutdownCalled = true
|
b.shutdownCalled = true
|
||||||
cc := b.cc
|
cc := b.cc
|
||||||
|
if b.sshServer != nil {
|
||||||
|
b.sshServer.Shutdown()
|
||||||
|
}
|
||||||
b.closePeerAPIListenersLocked()
|
b.closePeerAPIListenersLocked()
|
||||||
b.mu.Unlock()
|
b.mu.Unlock()
|
||||||
|
|
||||||
|
@ -58,11 +58,14 @@ type server struct {
|
|||||||
pubKeyHTTPClient *http.Client // or nil for http.DefaultClient
|
pubKeyHTTPClient *http.Client // or nil for http.DefaultClient
|
||||||
timeNow func() time.Time // or nil for time.Now
|
timeNow func() time.Time // or nil for time.Now
|
||||||
|
|
||||||
|
sessionWaitGroup sync.WaitGroup
|
||||||
|
|
||||||
// mu protects the following
|
// mu protects the following
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
activeSessionByH map[string]*sshSession // ssh.SessionID (DH H) => session
|
activeSessionByH map[string]*sshSession // ssh.SessionID (DH H) => session
|
||||||
activeSessionBySharedID map[string]*sshSession // yyymmddThhmmss-XXXXX => session
|
activeSessionBySharedID map[string]*sshSession // yyymmddThhmmss-XXXXX => session
|
||||||
fetchPublicKeysCache map[string]pubKeyCacheEntry // by https URL
|
fetchPublicKeysCache map[string]pubKeyCacheEntry // by https URL
|
||||||
|
shutdownCalled bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *server) now() time.Time {
|
func (srv *server) now() time.Time {
|
||||||
@ -101,6 +104,20 @@ func (srv *server) HandleSSHConn(c net.Conn) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shutdown terminates all active sessions.
|
||||||
|
func (srv *server) Shutdown() {
|
||||||
|
srv.mu.Lock()
|
||||||
|
srv.shutdownCalled = true
|
||||||
|
for _, s := range srv.activeSessionByH {
|
||||||
|
s.ctx.CloseWithError(userVisibleError{
|
||||||
|
fmt.Sprintf("Tailscale shutting down.\r\n"),
|
||||||
|
context.Canceled,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
srv.mu.Unlock()
|
||||||
|
srv.sessionWaitGroup.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
// OnPolicyChange terminates any active sessions that no longer match
|
// OnPolicyChange terminates any active sessions that no longer match
|
||||||
// the SSH access policy.
|
// the SSH access policy.
|
||||||
func (srv *server) OnPolicyChange() {
|
func (srv *server) OnPolicyChange() {
|
||||||
@ -227,6 +244,15 @@ func (c *conn) ServerConfig(ctx ssh.Context) *gossh.ServerConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (srv *server) newConn() (*conn, error) {
|
func (srv *server) newConn() (*conn, error) {
|
||||||
|
srv.mu.Lock()
|
||||||
|
shutdownCalled := srv.shutdownCalled
|
||||||
|
srv.mu.Unlock()
|
||||||
|
if shutdownCalled {
|
||||||
|
// Stop accepting new connections.
|
||||||
|
// Connections in the auth phase are handled in handleConnPostSSHAuth.
|
||||||
|
// Existing sessions are terminated by Shutdown.
|
||||||
|
return nil, gossh.ErrDenied
|
||||||
|
}
|
||||||
c := &conn{srv: srv, now: srv.now()}
|
c := &conn{srv: srv, now: srv.now()}
|
||||||
c.Server = &ssh.Server{
|
c.Server = &ssh.Server{
|
||||||
Version: "Tailscale",
|
Version: "Tailscale",
|
||||||
@ -756,10 +782,10 @@ func (srv *server) getSessionForContext(sctx ssh.Context) (ss *sshSession, ok bo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// startSession registers ss as an active session.
|
// startSessionLocked registers ss as an active session.
|
||||||
func (srv *server) startSession(ss *sshSession) {
|
// It must be called with srv.mu held.
|
||||||
srv.mu.Lock()
|
func (srv *server) startSessionLocked(ss *sshSession) {
|
||||||
defer srv.mu.Unlock()
|
srv.sessionWaitGroup.Add(1)
|
||||||
if ss.idH == "" {
|
if ss.idH == "" {
|
||||||
panic("empty idH")
|
panic("empty idH")
|
||||||
}
|
}
|
||||||
@ -778,6 +804,7 @@ func (srv *server) startSession(ss *sshSession) {
|
|||||||
|
|
||||||
// endSession unregisters s from the list of active sessions.
|
// endSession unregisters s from the list of active sessions.
|
||||||
func (srv *server) endSession(ss *sshSession) {
|
func (srv *server) endSession(ss *sshSession) {
|
||||||
|
defer srv.sessionWaitGroup.Done()
|
||||||
srv.mu.Lock()
|
srv.mu.Lock()
|
||||||
defer srv.mu.Unlock()
|
defer srv.mu.Unlock()
|
||||||
delete(srv.activeSessionByH, ss.idH)
|
delete(srv.activeSessionByH, ss.idH)
|
||||||
@ -842,11 +869,21 @@ func (ss *sshSession) handleSSHAgentForwarding(s ssh.Session, lu *user.User) err
|
|||||||
// It handles ss once it's been accepted and determined
|
// It handles ss once it's been accepted and determined
|
||||||
// that it should run.
|
// that it should run.
|
||||||
func (ss *sshSession) run() {
|
func (ss *sshSession) run() {
|
||||||
srv := ss.conn.srv
|
|
||||||
srv.startSession(ss)
|
|
||||||
defer srv.endSession(ss)
|
|
||||||
|
|
||||||
defer ss.ctx.CloseWithError(errSessionDone)
|
defer ss.ctx.CloseWithError(errSessionDone)
|
||||||
|
srv := ss.conn.srv
|
||||||
|
|
||||||
|
srv.mu.Lock()
|
||||||
|
if srv.shutdownCalled {
|
||||||
|
srv.mu.Unlock()
|
||||||
|
// Do not start any new sessions.
|
||||||
|
fmt.Fprintf(ss, "Tailscale is shutting down\r\n")
|
||||||
|
ss.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
srv.startSessionLocked(ss)
|
||||||
|
srv.mu.Unlock()
|
||||||
|
|
||||||
|
defer srv.endSession(ss)
|
||||||
|
|
||||||
if ss.action.SessionDuration != 0 {
|
if ss.action.SessionDuration != 0 {
|
||||||
t := time.AfterFunc(ss.action.SessionDuration, func() {
|
t := time.AfterFunc(ss.action.SessionDuration, func() {
|
||||||
@ -858,7 +895,7 @@ func (ss *sshSession) run() {
|
|||||||
defer t.Stop()
|
defer t.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
logf := srv.logf
|
logf := ss.logf
|
||||||
lu := ss.conn.localUser
|
lu := ss.conn.localUser
|
||||||
localUser := lu.Username
|
localUser := lu.Username
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user