|
|
|
@@ -152,7 +152,7 @@ const sshAuthMethodStoredPrivateKey string = "stored_private_key"
|
|
|
|
|
const sshSecondFactorNone string = "none"
|
|
|
|
|
const sshSecondFactorPromptedTOTP string = "prompted_totp"
|
|
|
|
|
|
|
|
|
|
func (api *API) logSSHSessionConnectStart(sessionItem models.SSHSession, profile models.SSHAccessProfile, user models.User) {
|
|
|
|
|
func (api *API) logSSHSessionConnectStart(sessionItem *models.SSHSession, profile *models.SSHAccessProfile, user *models.User) {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"connect start session=%s requester=%s user=%s profile=%s profile_name=%q server=%s server_name=%q target=%s:%d remote_user=%s auth=%s",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
@@ -168,11 +168,11 @@ func (api *API) logSSHSessionConnectStart(sessionItem models.SSHSession, profile
|
|
|
|
|
profile.AuthMethod)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) logSSHSessionPrepare(sessionID string, remoteAddr string, user models.User, profile models.SSHAccessProfile) {
|
|
|
|
|
func (api *API) logSSHSessionPrepare(sessionItem *models.SSHSession, user *models.User, profile *models.SSHAccessProfile) {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"connect prepare build session=%s requester=%s user=%s profile=%s profile_name=%q server=%s server_name=%q target=%s:%d remote_user=%s auth=%s",
|
|
|
|
|
sessionID,
|
|
|
|
|
remoteAddr,
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
sessionItem.RemoteAddr,
|
|
|
|
|
user.Username,
|
|
|
|
|
profile.ID,
|
|
|
|
|
profile.Name,
|
|
|
|
@@ -184,11 +184,11 @@ func (api *API) logSSHSessionPrepare(sessionID string, remoteAddr string, user m
|
|
|
|
|
profile.AuthMethod)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) logSSHSessionPreparedUse(sessionID string, remoteAddr string, user models.User, profile models.SSHAccessProfile) {
|
|
|
|
|
func (api *API) logSSHSessionPreparedUse(sessionItem *models.SSHSession, user *models.User, profile *models.SSHAccessProfile) {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"connect prepare use session=%s requester=%s user=%s profile=%s profile_name=%q server=%s server_name=%q target=%s:%d remote_user=%s auth=%s",
|
|
|
|
|
sessionID,
|
|
|
|
|
remoteAddr,
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
sessionItem.RemoteAddr,
|
|
|
|
|
user.Username,
|
|
|
|
|
profile.ID,
|
|
|
|
|
profile.Name,
|
|
|
|
@@ -200,7 +200,7 @@ func (api *API) logSSHSessionPreparedUse(sessionID string, remoteAddr string, us
|
|
|
|
|
profile.AuthMethod)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) logSSHSessionConnectSuccess(sessionItem models.SSHSession, profile models.SSHAccessProfile, user models.User, hostKeyFingerprint string) {
|
|
|
|
|
func (api *API) logSSHSessionConnectSuccess(sessionItem *models.SSHSession, profile *models.SSHAccessProfile, user *models.User, hostKeyFingerprint string) {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"connect success session=%s requester=%s user=%s profile=%s profile_name=%q server=%s server_name=%q target=%s:%d remote_user=%s auth=%s host_key=%s",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
@@ -217,7 +217,7 @@ func (api *API) logSSHSessionConnectSuccess(sessionItem models.SSHSession, profi
|
|
|
|
|
hostKeyFingerprint)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) logSSHSessionConnectFailure(sessionItem models.SSHSession, profile models.SSHAccessProfile, user models.User, stage string, err error) {
|
|
|
|
|
func (api *API) logSSHSessionConnectFailure(sessionItem *models.SSHSession, profile *models.SSHAccessProfile, user *models.User, stage string, err error) {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN,
|
|
|
|
|
"connect failed session=%s requester=%s user=%s profile=%s profile_name=%q server=%s server_name=%q target=%s:%d remote_user=%s auth=%s stage=%s err=%v",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
@@ -235,18 +235,18 @@ func (api *API) logSSHSessionConnectFailure(sessionItem models.SSHSession, profi
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) logSSHSessionPrepareFailure(sessionID string, remoteAddr string, username string, profileID string, stage string, err error) {
|
|
|
|
|
func (api *API) logSSHSessionPrepareFailure(sessionItem *models.SSHSession, user *models.User, stage string, err error) {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN,
|
|
|
|
|
"connect failed session=%s requester=%s user=%s profile=%s stage=%s err=%v",
|
|
|
|
|
sessionID,
|
|
|
|
|
remoteAddr,
|
|
|
|
|
username,
|
|
|
|
|
profileID,
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
sessionItem.RemoteAddr,
|
|
|
|
|
user.Username,
|
|
|
|
|
sessionItem.ProfileID,
|
|
|
|
|
stage,
|
|
|
|
|
err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) logSSHSessionClosed(sessionItem models.SSHSession, profile models.SSHAccessProfile, user models.User, status string, reason string, connectedAt int64, endedAt int64) {
|
|
|
|
|
func (api *API) logSSHSessionClosed(sessionItem *models.SSHSession, profile *models.SSHAccessProfile, user *models.User, status string, reason string, connectedAt int64, endedAt int64) {
|
|
|
|
|
var durationSeconds int64
|
|
|
|
|
|
|
|
|
|
durationSeconds = 0
|
|
|
|
@@ -1581,6 +1581,8 @@ func (api *API) CreateSSHSessionForSelf(w http.ResponseWriter, r *http.Request,
|
|
|
|
|
var prepareStage string
|
|
|
|
|
var hostKeyFingerprint string
|
|
|
|
|
var belongs bool
|
|
|
|
|
var pendingCount int
|
|
|
|
|
var pendingCutoff int64
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
user, ok = middleware.UserFromContext(r.Context())
|
|
|
|
@@ -1593,6 +1595,21 @@ func (api *API) CreateSSHSessionForSelf(w http.ResponseWriter, r *http.Request,
|
|
|
|
|
WriteJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
pendingCutoff = time.Now().UTC().Add(-sshSessionPreparationTTL).Unix()
|
|
|
|
|
err = api.store(r).ExpireStalePendingSSHSessions(pendingCutoff, "session preparation expired")
|
|
|
|
|
if err != nil {
|
|
|
|
|
WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
pendingCount, err = api.store(r).CountPendingSSHSessionsForUser(user.ID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
WriteJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if pendingCount >= sshSessionPendingPerUserLimit {
|
|
|
|
|
WriteJSON(w, http.StatusTooManyRequests, map[string]string{"error": "too many pending ssh sessions; attach or wait for pending sessions to expire"})
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
profile, err = api.store(r).GetSSHAccessProfileForUser(user.ID, params["id"])
|
|
|
|
|
if err != nil {
|
|
|
|
|
if err == sql.ErrNoRows {
|
|
|
|
@@ -1665,7 +1682,7 @@ func (api *API) CreateSSHSessionForSelf(w http.ResponseWriter, r *http.Request,
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prepared, prepareStage, err = api.prepareSSHSession(api.store(r), user, item, req.Password, req.OTPCode)
|
|
|
|
|
prepared, prepareStage, err = api.prepareSSHSession(api.store(r), &user, &item, req.Password, req.OTPCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if prepareStage == "host_key_missing" || prepareStage == "build_auth" {
|
|
|
|
|
WriteJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
|
|
@@ -1851,7 +1868,7 @@ func (api *API) DisconnectSSHSessionForSelf(w http.ResponseWriter, r *http.Reque
|
|
|
|
|
api.SSHPreparedSessionStore.Delete(item.ID)
|
|
|
|
|
}
|
|
|
|
|
if api.SSHSessionRegistry != nil {
|
|
|
|
|
closed = api.SSHSessionRegistry.RequestClose(item.ID, "disconnected by user")
|
|
|
|
|
closed = api.SSHSessionRegistry.RequestClose(item.ID, SSHSessionCloseUserDisconnect)
|
|
|
|
|
}
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
if !closed {
|
|
|
|
@@ -2328,9 +2345,7 @@ func discoverSSHServerHostKey(host string, port int, defaultUser string) (models
|
|
|
|
|
|
|
|
|
|
addr = net.JoinHostPort(strings.TrimSpace(host), fmt.Sprintf("%d", port))
|
|
|
|
|
defaultUser = strings.TrimSpace(defaultUser)
|
|
|
|
|
if defaultUser == "" {
|
|
|
|
|
defaultUser = "codit"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
config = &ssh.ClientConfig{
|
|
|
|
|
User: defaultUser,
|
|
|
|
|
Auth: []ssh.AuthMethod{},
|
|
|
|
@@ -2359,21 +2374,17 @@ func discoverSSHServerHostKey(host string, port int, defaultUser string) (models
|
|
|
|
|
item.PublicKey = strings.TrimSpace(string(ssh.MarshalAuthorizedKey(discovered)))
|
|
|
|
|
item.Fingerprint = strings.TrimSpace(ssh.FingerprintSHA256(discovered))
|
|
|
|
|
if err != nil {
|
|
|
|
|
if strings.Contains(strings.ToLower(err.Error()), "unable to authenticate") {
|
|
|
|
|
return item, nil
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(strings.ToLower(err.Error()), "no auth passed yet") {
|
|
|
|
|
return item, nil
|
|
|
|
|
}
|
|
|
|
|
if strings.Contains(strings.ToLower(err.Error()), "unable to authenticate") { return item, nil }
|
|
|
|
|
if strings.Contains(strings.ToLower(err.Error()), "no auth passed yet") { return item, nil }
|
|
|
|
|
}
|
|
|
|
|
return item, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) prepareSSHSession(store *db.Store, user models.User, sessionItem models.SSHSession, promptedPassword string, otpCode string) (sshSessionPrepared, string, error) {
|
|
|
|
|
func (api *API) prepareSSHSession(store *db.Store, user *models.User, sessionItem *models.SSHSession, promptedPassword string, otpCode string) (sshSessionPrepared, string, error) {
|
|
|
|
|
var prepared sshSessionPrepared
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
prepared.Session = sessionItem
|
|
|
|
|
prepared.Session = *sessionItem
|
|
|
|
|
prepared.Profile, err = store.GetSSHAccessProfileForUser(user.ID, sessionItem.ProfileID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, "load_profile", err
|
|
|
|
@@ -2386,7 +2397,7 @@ func (api *API) prepareSSHSession(store *db.Store, user models.User, sessionItem
|
|
|
|
|
prepared.Profile.ServerID = prepared.Profile.Server.ID
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
api.logSSHSessionPrepare(sessionItem.ID, sessionItem.RemoteAddr, user, prepared.Profile)
|
|
|
|
|
api.logSSHSessionPrepare(sessionItem, user, &prepared.Profile)
|
|
|
|
|
prepared.HostKeys, err = store.ListSSHServerHostKeys(sessionItem.ServerID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, "load_host_keys", err
|
|
|
|
@@ -2405,26 +2416,26 @@ func (api *API) prepareSSHSession(store *db.Store, user models.User, sessionItem
|
|
|
|
|
return prepared, "", nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) reconcileSSHPreparedSessionForUse(store *db.Store, sessionItem models.SSHSession, prepared sshSessionPrepared) (sshSessionPrepared, error) {
|
|
|
|
|
func (api *API) reconcileSSHPreparedSessionForUse(store *db.Store, sessionItem *models.SSHSession, prepared *sshSessionPrepared) error {
|
|
|
|
|
var err error
|
|
|
|
|
var i int
|
|
|
|
|
var reloadServer bool
|
|
|
|
|
var reloadHostKeys bool
|
|
|
|
|
|
|
|
|
|
if strings.TrimSpace(prepared.Session.ID) == "" {
|
|
|
|
|
return prepared, errors.New("prepared ssh session is missing session id")
|
|
|
|
|
return errors.New("prepared ssh session is missing session id")
|
|
|
|
|
}
|
|
|
|
|
if prepared.Session.ID != sessionItem.ID {
|
|
|
|
|
return prepared, fmt.Errorf("prepared ssh session id mismatch: prepared=%s session=%s", prepared.Session.ID, sessionItem.ID)
|
|
|
|
|
return fmt.Errorf("prepared ssh session id mismatch: prepared=%s session=%s", prepared.Session.ID, sessionItem.ID)
|
|
|
|
|
}
|
|
|
|
|
if strings.TrimSpace(prepared.Session.ServerID) != "" && prepared.Session.ServerID != sessionItem.ServerID {
|
|
|
|
|
return prepared, fmt.Errorf("prepared ssh session server mismatch: prepared=%s session=%s", prepared.Session.ServerID, sessionItem.ServerID)
|
|
|
|
|
return fmt.Errorf("prepared ssh session server mismatch: prepared=%s session=%s", prepared.Session.ServerID, sessionItem.ServerID)
|
|
|
|
|
}
|
|
|
|
|
if strings.TrimSpace(prepared.Profile.ID) != "" && prepared.Profile.ID != sessionItem.ProfileID {
|
|
|
|
|
return prepared, fmt.Errorf("prepared ssh profile mismatch: prepared=%s session=%s", prepared.Profile.ID, sessionItem.ProfileID)
|
|
|
|
|
return fmt.Errorf("prepared ssh profile mismatch: prepared=%s session=%s", prepared.Profile.ID, sessionItem.ProfileID)
|
|
|
|
|
}
|
|
|
|
|
if strings.TrimSpace(sessionItem.ServerID) == "" {
|
|
|
|
|
return prepared, errors.New("ssh session server_id is empty")
|
|
|
|
|
return errors.New("ssh session server_id is empty")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reloadServer = prepared.Profile.ServerID != sessionItem.ServerID || prepared.Profile.Server.ID != sessionItem.ServerID
|
|
|
|
@@ -2444,7 +2455,7 @@ func (api *API) reconcileSSHPreparedSessionForUse(store *db.Store, sessionItem m
|
|
|
|
|
sessionItem.Port)
|
|
|
|
|
prepared.Profile.Server, err = store.GetSSHServer(sessionItem.ServerID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, err
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
prepared.Profile.ServerID = prepared.Profile.Server.ID
|
|
|
|
|
if strings.TrimSpace(sessionItem.Host) != "" {
|
|
|
|
@@ -2465,22 +2476,37 @@ func (api *API) reconcileSSHPreparedSessionForUse(store *db.Store, sessionItem m
|
|
|
|
|
if reloadHostKeys {
|
|
|
|
|
prepared.HostKeys, err = store.ListSSHServerHostKeys(sessionItem.ServerID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, err
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
prepared.PinHostKeyOnFirstUse = false
|
|
|
|
|
if len(prepared.HostKeys) == 0 {
|
|
|
|
|
if normalizeSSHServerHostKeyPolicy(prepared.Profile.Server.HostKeyPolicy) != "trust_on_first_use" {
|
|
|
|
|
return prepared, errors.New("no pinned host key for " + prepared.Profile.Server.Name)
|
|
|
|
|
return errors.New("no pinned host key for " + prepared.Profile.Server.Name)
|
|
|
|
|
}
|
|
|
|
|
prepared.PinHostKeyOnFirstUse = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prepared.Session = sessionItem
|
|
|
|
|
return prepared, nil
|
|
|
|
|
prepared.Session = *sessionItem
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) requestSSHSessionCloseOnWebsocketError(inputErrCh chan<- error, readerErrCh <-chan error, sessionId string) {
|
|
|
|
|
var err error
|
|
|
|
|
var code SSHSessionCloseCode
|
|
|
|
|
|
|
|
|
|
err = <-readerErrCh
|
|
|
|
|
code = sshSessionWebsocketCloseCode(err)
|
|
|
|
|
if api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(sessionId, code)
|
|
|
|
|
}
|
|
|
|
|
inputErrCh <- err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) serveSSHSessionStream(ws *websocket.Conn, store *db.Store, user models.User, sessionItem models.SSHSession) {
|
|
|
|
|
// TODO: remove this single stream handler if the frontend doesn't really need it..
|
|
|
|
|
// it's hard to maintain multiple functions for similar features. the frontend
|
|
|
|
|
// should use the workspace stream api instead.
|
|
|
|
|
var sendMu sync.Mutex
|
|
|
|
|
var inputCh chan sshSessionStreamMessage
|
|
|
|
|
var inputErrCh chan error
|
|
|
|
@@ -2490,45 +2516,35 @@ func (api *API) serveSSHSessionStream(ws *websocket.Conn, store *db.Store, user
|
|
|
|
|
var preparedPtr *sshSessionPrepared
|
|
|
|
|
|
|
|
|
|
defer ws.Close()
|
|
|
|
|
|
|
|
|
|
inputCh = make(chan sshSessionStreamMessage, sshSessionInputBufferSize)
|
|
|
|
|
inputErrCh = make(chan error, 1)
|
|
|
|
|
readerErrCh = make(chan error, 1)
|
|
|
|
|
go api.readSSHSessionWebsocket(ws, inputCh, readerErrCh) // read websocket and write to inputCh
|
|
|
|
|
go func() {
|
|
|
|
|
var err error
|
|
|
|
|
var reason string
|
|
|
|
|
|
|
|
|
|
err = <-readerErrCh
|
|
|
|
|
reason = sshSessionWebsocketCloseReason(err)
|
|
|
|
|
if api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(sessionItem.ID, reason)
|
|
|
|
|
}
|
|
|
|
|
inputErrCh <- err
|
|
|
|
|
}()
|
|
|
|
|
// read websocket and write to inputCh and readerErrCh
|
|
|
|
|
go api.readSSHSessionWebsocket(ws, inputCh, readerErrCh)
|
|
|
|
|
|
|
|
|
|
// waits for websocket reader error, converts it to a reason, requests session close, then relays the error to inputErrCh
|
|
|
|
|
go api.requestSSHSessionCloseOnWebsocketError(inputErrCh, readerErrCh, sessionItem.ID)
|
|
|
|
|
|
|
|
|
|
prepared, preparedOK = api.SSHPreparedSessionStore.Take(sessionItem.ID)
|
|
|
|
|
if !preparedOK {
|
|
|
|
|
prepared = sshSessionPrepared{}
|
|
|
|
|
} else {
|
|
|
|
|
preparedPtr = &prepared
|
|
|
|
|
}
|
|
|
|
|
if preparedOK { preparedPtr = &prepared }
|
|
|
|
|
// if preparedOK is not true, preparedPtr is nil. api.RunSSHSessionStream build one inside itself.
|
|
|
|
|
|
|
|
|
|
// this reads the inputCh
|
|
|
|
|
api.runSSHSessionStream(store, user, sessionItem, ws, inputCh, inputErrCh,
|
|
|
|
|
preparedPtr,
|
|
|
|
|
func(msg sshSessionStreamMessage) {
|
|
|
|
|
api.runSSHSessionStream(store, &user, &sessionItem, ws, inputCh, inputErrCh, preparedPtr,
|
|
|
|
|
func(msg sshSessionStreamMessage) { // stream message sender
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
err = api.sendSSHSessionWS(&sendMu, ws, msg)
|
|
|
|
|
if err != nil && api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(sessionItem.ID, "websocket send failed")
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(sessionItem.ID, SSHSessionCloseWebsocketSendFailed)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
func(data []byte) {
|
|
|
|
|
func(data []byte) { // binary data sender
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
err = api.sendSSHSessionBinary(&sendMu, ws, data)
|
|
|
|
|
if err != nil && api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(sessionItem.ID, "websocket send failed")
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(sessionItem.ID, SSHSessionCloseWebsocketSendFailed)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
@@ -2548,7 +2564,7 @@ func (api *API) serveSSHWorkspaceStream(ws *websocket.Conn, store *db.Store, use
|
|
|
|
|
var attachedID string
|
|
|
|
|
var attachmentDoneCh chan sshWorkspaceAttachmentDone
|
|
|
|
|
var attachmentDone sshWorkspaceAttachmentDone
|
|
|
|
|
var websocketCloseReason string
|
|
|
|
|
var websocketCloseCode SSHSessionCloseCode
|
|
|
|
|
|
|
|
|
|
defer ws.Close()
|
|
|
|
|
controlCh = make(chan sshSessionStreamMessage, sshSessionInputBufferSize)
|
|
|
|
@@ -2561,7 +2577,7 @@ event_loop:
|
|
|
|
|
for {
|
|
|
|
|
select {
|
|
|
|
|
case err = <-controlErrCh:
|
|
|
|
|
websocketCloseReason = sshSessionWebsocketCloseReason(err)
|
|
|
|
|
websocketCloseCode = sshSessionWebsocketCloseCode(err)
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN,
|
|
|
|
|
"workspace stream closed requester=%s user=%s err=%v",
|
|
|
|
|
remoteAddr,
|
|
|
|
@@ -2570,7 +2586,7 @@ event_loop:
|
|
|
|
|
|
|
|
|
|
for attachedID, attachment = range attachments {
|
|
|
|
|
if api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(attachedID, websocketCloseReason)
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(attachedID, websocketCloseCode)
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case attachment.InputErrCh <- err:
|
|
|
|
@@ -2624,9 +2640,7 @@ event_loop:
|
|
|
|
|
if !preparedOK {
|
|
|
|
|
attachedStatus = "closed"
|
|
|
|
|
attachedMessage = item.Error
|
|
|
|
|
if item.Status == "error" {
|
|
|
|
|
attachedStatus = "error"
|
|
|
|
|
}
|
|
|
|
|
if item.Status == "error" { attachedStatus = "error" }
|
|
|
|
|
if attachedMessage == "" {
|
|
|
|
|
if item.Status == "closed" {
|
|
|
|
|
attachedMessage = "SSH session is closed"
|
|
|
|
@@ -2668,33 +2682,32 @@ event_loop:
|
|
|
|
|
user.Username)
|
|
|
|
|
attachedItem = item
|
|
|
|
|
attachedSessionID = attachedItem.ID
|
|
|
|
|
go func(runItem models.SSHSession, runPrepared sshSessionPrepared, runSessionID string, runAttachment *sshWorkspaceAttachment) {
|
|
|
|
|
var runPreparedPtr *sshSessionPrepared
|
|
|
|
|
|
|
|
|
|
runPreparedPtr = &runPrepared
|
|
|
|
|
go func(runUser models.User, runItem models.SSHSession, runPrepared sshSessionPrepared, runSessionID string, runAttachment *sshWorkspaceAttachment) {
|
|
|
|
|
// accept runUser, runItem, runPrepared by value because the values can mutate
|
|
|
|
|
// in the main event loop if we reference them directly(closure or pointer)
|
|
|
|
|
defer func() {
|
|
|
|
|
select {
|
|
|
|
|
case attachmentDoneCh <- sshWorkspaceAttachmentDone{SessionID: runSessionID, Attachment: runAttachment}:
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
api.runSSHSessionStream(store, user, runItem, nil, runAttachment.InputCh, runAttachment.InputErrCh, runPreparedPtr,
|
|
|
|
|
api.runSSHSessionStream(store, &runUser, &runItem, nil, runAttachment.InputCh, runAttachment.InputErrCh, &runPrepared,
|
|
|
|
|
func(status sshSessionStreamMessage) {
|
|
|
|
|
var sendErr error
|
|
|
|
|
status.SessionID = runSessionID
|
|
|
|
|
sendErr = api.sendSSHSessionWS(&sendMu, ws, status)
|
|
|
|
|
if sendErr != nil && api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(runSessionID, "websocket send failed")
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(runSessionID, SSHSessionCloseWebsocketSendFailed)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
func(data []byte) {
|
|
|
|
|
var sendErr error
|
|
|
|
|
sendErr = api.sendSSHWorkspaceOutput(&sendMu, ws, runSessionID, data)
|
|
|
|
|
if sendErr != nil && api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(runSessionID, "websocket send failed")
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(runSessionID, SSHSessionCloseWebsocketSendFailed)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}(attachedItem, attachedPrepared, attachedSessionID, attachment)
|
|
|
|
|
}(user, attachedItem, attachedPrepared, attachedSessionID, attachment)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
attachment, ok = attachments[msg.SessionID]
|
|
|
|
@@ -2710,7 +2723,7 @@ event_loop:
|
|
|
|
|
}
|
|
|
|
|
if msg.Type == "detach" {
|
|
|
|
|
if api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(msg.SessionID, "detached")
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(msg.SessionID, SSHSessionCloseDetached)
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case attachment.InputErrCh <- io.EOF:
|
|
|
|
@@ -2731,7 +2744,7 @@ event_loop:
|
|
|
|
|
user.Username,
|
|
|
|
|
msg.Type)
|
|
|
|
|
if api.SSHSessionRegistry != nil {
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(msg.SessionID, "websocket input queue full")
|
|
|
|
|
api.SSHSessionRegistry.RequestClose(msg.SessionID, SSHSessionCloseWebsocketInputQueueFull)
|
|
|
|
|
}
|
|
|
|
|
select {
|
|
|
|
|
case attachment.InputErrCh <- errors.New("websocket input queue full"):
|
|
|
|
@@ -2742,7 +2755,7 @@ event_loop:
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionItem models.SSHSession, transportWS *websocket.Conn, inputCh <-chan sshSessionStreamMessage, inputErrCh <-chan error, prepared *sshSessionPrepared, sendStatus func(sshSessionStreamMessage), sendBinary func([]byte)) {
|
|
|
|
|
func (api *API) runSSHSessionStream(store *db.Store, user *models.User, sessionItem *models.SSHSession, transportWS *websocket.Conn, inputCh <-chan sshSessionStreamMessage, inputErrCh <-chan error, prepared *sshSessionPrepared, sendStatus func(sshSessionStreamMessage), sendBinary func([]byte)) {
|
|
|
|
|
var profile models.SSHAccessProfile
|
|
|
|
|
var authMethods []ssh.AuthMethod
|
|
|
|
|
var hostKeys []models.SSHServerHostKey
|
|
|
|
@@ -2754,6 +2767,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
var stderr io.Reader
|
|
|
|
|
var runtimeSession *sshActiveSession
|
|
|
|
|
var closeReason string
|
|
|
|
|
var closeCode SSHSessionCloseCode
|
|
|
|
|
var err error
|
|
|
|
|
var connectedFingerprint string
|
|
|
|
|
var connectedHostKey ssh.PublicKey
|
|
|
|
@@ -2807,7 +2821,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
promptedOTPCode = promptedAuthInput.OTPCode
|
|
|
|
|
}
|
|
|
|
|
if prepared != nil {
|
|
|
|
|
*prepared, err = api.reconcileSSHPreparedSessionForUse(store, sessionItem, *prepared)
|
|
|
|
|
err = api.reconcileSSHPreparedSessionForUse(store, sessionItem, prepared)
|
|
|
|
|
if err != nil {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN,
|
|
|
|
|
"prepared session rejected session=%s requester=%s user=%s err=%v",
|
|
|
|
@@ -2825,17 +2839,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
profile = prepared.Profile
|
|
|
|
|
hostKeys = prepared.HostKeys
|
|
|
|
|
authMethods = prepared.AuthMethods
|
|
|
|
|
api.logSSHSessionPreparedUse(sessionItem.ID, sessionItem.RemoteAddr, user, profile)
|
|
|
|
|
api.logSSHSessionPreparedUse(sessionItem, user, &profile)
|
|
|
|
|
} else {
|
|
|
|
|
prepared = &sshSessionPrepared{}
|
|
|
|
|
*prepared, prepareStage, err = api.prepareSSHSession(store, user, sessionItem, promptedPassword, promptedOTPCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if prepareStage == "load_profile" {
|
|
|
|
|
api.logSSHSessionPrepareFailure(sessionItem.ID, sessionItem.RemoteAddr, user.Username, sessionItem.ProfileID, prepareStage, err)
|
|
|
|
|
api.logSSHSessionPrepareFailure(sessionItem, user, prepareStage, err)
|
|
|
|
|
} else if prepared.Profile.ID != "" {
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, prepared.Profile, user, prepareStage, err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &prepared.Profile, user, prepareStage, err)
|
|
|
|
|
} else {
|
|
|
|
|
api.logSSHSessionPrepareFailure(sessionItem.ID, sessionItem.RemoteAddr, user.Username, sessionItem.ProfileID, prepareStage, err)
|
|
|
|
|
api.logSSHSessionPrepareFailure(sessionItem, user, prepareStage, err)
|
|
|
|
|
}
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
if prepareStage == "load_profile" {
|
|
|
|
@@ -2857,16 +2871,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
authMethods = prepared.AuthMethods
|
|
|
|
|
}
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", "", 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
api.logSSHSessionConnectStart(sessionItem, profile, user)
|
|
|
|
|
api.logSSHSessionConnectStart(sessionItem, &profile, user)
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "connecting", "", 0, 0, "")
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
@@ -2880,7 +2895,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
}
|
|
|
|
|
sshClient, err = ssh.Dial("tcp", net.JoinHostPort(profile.Server.Host, fmt.Sprintf("%d", profile.Server.Port)), sshConfig)
|
|
|
|
|
if err != nil {
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "dial", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "dial", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -2892,7 +2907,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
if prepared.PinHostKeyOnFirstUse {
|
|
|
|
|
if connectedHostKey == nil {
|
|
|
|
|
err = errors.New("failed to capture ssh host key")
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "pin_host_key", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "pin_host_key", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -2919,7 +2934,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if pinnedHostKey.ID == "" {
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "pin_host_key", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "pin_host_key", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: "failed to pin host key: " + err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -2938,11 +2953,12 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
}
|
|
|
|
|
runtimeSession.SetResources(transportWS, sshClient, nil, nil)
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
@@ -2950,16 +2966,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
sshSession, err = sshClient.NewSession()
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "new_session", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "new_session", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -2970,11 +2987,12 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
defer sshSession.Close()
|
|
|
|
|
runtimeSession.SetResources(transportWS, sshClient, sshSession, nil)
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
@@ -2982,16 +3000,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
stdin, err = sshSession.StdinPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "stdin_pipe", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "stdin_pipe", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -3002,16 +3021,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
stdout, err = sshSession.StdoutPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "stdout_pipe", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "stdout_pipe", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -3022,16 +3042,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
stderr, err = sshSession.StderrPipe()
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "stderr_pipe", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "stderr_pipe", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -3041,10 +3062,11 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
}
|
|
|
|
|
runtimeSession.SetResources(transportWS, sshClient, sshSession, stdin)
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
@@ -3056,16 +3078,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "request_pty", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "request_pty", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -3074,11 +3097,12 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
@@ -3086,16 +3110,17 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
err = sshSession.Shell()
|
|
|
|
|
if err != nil {
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, profile, user, "shell", err)
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "shell", err)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
@@ -3104,18 +3129,19 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, 0, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, 0, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
connectedAt = time.Now().UTC().Unix()
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "connected", connectedFingerprint, connectedAt, 0, "")
|
|
|
|
|
api.logSSHSessionConnectSuccess(sessionItem, profile, user, connectedFingerprint)
|
|
|
|
|
api.logSSHSessionConnectSuccess(sessionItem, &profile, user, connectedFingerprint)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "connected", Message: connectedFingerprint})
|
|
|
|
|
}
|
|
|
|
@@ -3186,10 +3212,11 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
_ = stdin.Close()
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|
closeCode = runtimeSession.CloseCode()
|
|
|
|
|
if closeReason != "" {
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, connectedAt, now, closeReason)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", closeReason, connectedAt, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseReason(closeReason) {
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", closeReason, connectedAt, now)
|
|
|
|
|
if sendStatus != nil && !isSSHSessionTransportCloseCode(closeCode) {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: closeReason})
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
@@ -3227,7 +3254,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user models.User, sessionIt
|
|
|
|
|
waitErr)
|
|
|
|
|
}
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "closed", connectedFingerprint, connectedAt, now, "")
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, profile, user, "closed", "", connectedAt, now)
|
|
|
|
|
api.logSSHSessionClosed(sessionItem, &profile, user, "closed", "", connectedAt, now)
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "closed", Message: ""})
|
|
|
|
|
}
|
|
|
|
@@ -3467,27 +3494,16 @@ func (api *API) readSSHSessionWebsocket(ws *websocket.Conn, inputCh chan<- sshSe
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func sshSessionWebsocketCloseReason(err error) string {
|
|
|
|
|
if err != nil && strings.Contains(err.Error(), "input queue full") {
|
|
|
|
|
return err.Error()
|
|
|
|
|
}
|
|
|
|
|
return "websocket closed"
|
|
|
|
|
func sshSessionWebsocketCloseCode(err error) SSHSessionCloseCode {
|
|
|
|
|
if err != nil && strings.Contains(err.Error(), "input queue full") { return SSHSessionCloseWebsocketInputQueueFull }
|
|
|
|
|
return SSHSessionCloseWebsocketClosed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func isSSHSessionTransportCloseReason(reason string) bool {
|
|
|
|
|
reason = strings.TrimSpace(reason)
|
|
|
|
|
if reason == "websocket closed" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if reason == "websocket send failed" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if reason == "websocket input queue full" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if reason == "detached" {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
func isSSHSessionTransportCloseCode(code SSHSessionCloseCode) bool {
|
|
|
|
|
if code == SSHSessionCloseWebsocketClosed { return true }
|
|
|
|
|
if code == SSHSessionCloseWebsocketSendFailed { return true }
|
|
|
|
|
if code == SSHSessionCloseWebsocketInputQueueFull { return true }
|
|
|
|
|
if code == SSHSessionCloseDetached { return true }
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|