|
|
|
@@ -24,6 +24,11 @@ import "codit/internal/models"
|
|
|
|
|
const logIDSSHBroker string = "ssh-broker"
|
|
|
|
|
const sshWebsocketWriteTimeout time.Duration = 5 * time.Second
|
|
|
|
|
const sshSessionInputBufferSize int = 64
|
|
|
|
|
const sshPrepareStageLoadProfile string = "load_profile"
|
|
|
|
|
const sshPrepareStageLoadServer string = "load_server"
|
|
|
|
|
const sshPrepareStageLoadHostKeys string = "load_host_keys"
|
|
|
|
|
const sshPrepareStageHostKeyMissing string = "host_key_missing"
|
|
|
|
|
const sshPrepareStageBuildAuth string = "build_auth"
|
|
|
|
|
|
|
|
|
|
type sshServerRequest struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
@@ -1769,7 +1774,7 @@ func (api *API) CreateSSHSessionForSelf(w http.ResponseWriter, r *http.Request,
|
|
|
|
|
|
|
|
|
|
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" {
|
|
|
|
|
if prepareStage == sshPrepareStageHostKeyMissing || prepareStage == sshPrepareStageBuildAuth {
|
|
|
|
|
WriteJSONWithErrorReason(w, r, http.StatusBadRequest, err.Error())
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
@@ -2509,12 +2514,12 @@ func (api *API) prepareSSHSession(store *db.Store, user *models.User, sessionIte
|
|
|
|
|
prepared.Session = *sessionItem
|
|
|
|
|
prepared.Profile, err = store.GetSSHAccessProfileForUser(user.ID, sessionItem.ProfileID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, "load_profile", err
|
|
|
|
|
return prepared, sshPrepareStageLoadProfile, err
|
|
|
|
|
}
|
|
|
|
|
if strings.TrimSpace(sessionItem.ServerID) != "" && sessionItem.ServerID != prepared.Profile.ServerID {
|
|
|
|
|
prepared.Profile.Server, err = store.GetSSHServer(sessionItem.ServerID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, "load_server", err
|
|
|
|
|
return prepared, sshPrepareStageLoadServer, err
|
|
|
|
|
}
|
|
|
|
|
prepared.Profile.ServerID = prepared.Profile.Server.ID
|
|
|
|
|
}
|
|
|
|
@@ -2522,18 +2527,18 @@ func (api *API) prepareSSHSession(store *db.Store, user *models.User, sessionIte
|
|
|
|
|
api.logSSHSessionPrepare(sessionItem, user, &prepared.Profile)
|
|
|
|
|
prepared.HostKeys, err = store.ListSSHServerHostKeys(sessionItem.ServerID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, "load_host_keys", err
|
|
|
|
|
return prepared, sshPrepareStageLoadHostKeys, err
|
|
|
|
|
}
|
|
|
|
|
if len(prepared.HostKeys) == 0 {
|
|
|
|
|
if normalizeSSHServerHostKeyPolicy(prepared.Profile.Server.HostKeyPolicy) != "trust_on_first_use" {
|
|
|
|
|
return prepared, "host_key_missing", errors.New("no pinned host key for " + prepared.Profile.Server.Name)
|
|
|
|
|
return prepared, sshPrepareStageHostKeyMissing, errors.New("no pinned host key for " + prepared.Profile.Server.Name)
|
|
|
|
|
}
|
|
|
|
|
prepared.PinHostKeyOnFirstUse = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prepared.AuthMethods, err = api.buildSSHSessionAuth(store, prepared.Profile, promptedPassword, otpCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return prepared, "build_auth", err
|
|
|
|
|
return prepared, sshPrepareStageBuildAuth, err
|
|
|
|
|
}
|
|
|
|
|
return prepared, "", nil
|
|
|
|
|
}
|
|
|
|
@@ -2888,6 +2893,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user *models.User, sessionI
|
|
|
|
|
var promptedAuthInput SSHPromptedAuthInput
|
|
|
|
|
var runtimeStartedAt time.Time
|
|
|
|
|
var pinHostKeyErr error
|
|
|
|
|
var pinHostKeyStartedAt time.Time
|
|
|
|
|
|
|
|
|
|
runtimeStartedAt = time.Now()
|
|
|
|
|
defer func() {
|
|
|
|
@@ -2943,7 +2949,7 @@ func (api *API) runSSHSessionStream(store *db.Store, user *models.User, sessionI
|
|
|
|
|
prepared = &sshSessionPrepared{}
|
|
|
|
|
*prepared, prepareStage, err = api.prepareSSHSession(store, user, sessionItem, promptedPassword, promptedOTPCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
if prepareStage == "load_profile" {
|
|
|
|
|
if prepareStage == sshPrepareStageLoadProfile {
|
|
|
|
|
api.logSSHSessionPrepareFailure(sessionItem, user, prepareStage, err)
|
|
|
|
|
} else if prepared.Profile.ID != "" {
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &prepared.Profile, user, prepareStage, err)
|
|
|
|
@@ -2951,14 +2957,14 @@ func (api *API) runSSHSessionStream(store *db.Store, user *models.User, sessionI
|
|
|
|
|
api.logSSHSessionPrepareFailure(sessionItem, user, prepareStage, err)
|
|
|
|
|
}
|
|
|
|
|
if sendStatus != nil {
|
|
|
|
|
if prepareStage == "load_profile" {
|
|
|
|
|
if prepareStage == sshPrepareStageLoadProfile {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: "ssh access profile not found"})
|
|
|
|
|
} else {
|
|
|
|
|
sendStatus(sshSessionStreamMessage{Type: "status", Status: "error", Message: err.Error()})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
now = time.Now().UTC().Unix()
|
|
|
|
|
if prepareStage == "load_profile" {
|
|
|
|
|
if prepareStage == sshPrepareStageLoadProfile {
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "error", sessionItem.HostKeyFingerprint, sessionItem.ConnectedAt, now, "ssh access profile not found")
|
|
|
|
|
} else {
|
|
|
|
|
_ = store.UpdateSSHSessionStatus(sessionItem.ID, "error", sessionItem.HostKeyFingerprint, sessionItem.ConnectedAt, now, err.Error())
|
|
|
|
@@ -3020,13 +3026,43 @@ func (api *API) runSSHSessionStream(store *db.Store, user *models.User, sessionI
|
|
|
|
|
PublicKey: strings.TrimSpace(string(ssh.MarshalAuthorizedKey(connectedHostKey))),
|
|
|
|
|
Fingerprint: connectedFingerprint,
|
|
|
|
|
}
|
|
|
|
|
pinHostKeyStartedAt = time.Now()
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"host key pin start session=%s server=%s server_name=%q fingerprint=%s algorithm=%s",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
profile.Server.ID,
|
|
|
|
|
profile.Server.Name,
|
|
|
|
|
pinnedHostKey.Fingerprint,
|
|
|
|
|
pinnedHostKey.Algorithm)
|
|
|
|
|
pinnedHostKey, err = store.CreateSSHServerHostKey(pinnedHostKey)
|
|
|
|
|
if err != nil {
|
|
|
|
|
pinHostKeyErr = err
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN,
|
|
|
|
|
"host key pin insert failed session=%s server=%s server_name=%q fingerprint=%s dur_ms=%d err=%s",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
profile.Server.ID,
|
|
|
|
|
profile.Server.Name,
|
|
|
|
|
connectedFingerprint,
|
|
|
|
|
time.Since(pinHostKeyStartedAt).Milliseconds(),
|
|
|
|
|
err.Error())
|
|
|
|
|
pinnedHostKey = models.SSHServerHostKey{}
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"host key pin reload start session=%s server=%s server_name=%q fingerprint=%s",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
profile.Server.ID,
|
|
|
|
|
profile.Server.Name,
|
|
|
|
|
connectedFingerprint)
|
|
|
|
|
reloadedHostKeys, err = store.ListSSHServerHostKeys(sessionItem.ServerID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
pinHostKeyErr = fmt.Errorf("%v; reload host keys: %w", pinHostKeyErr, err)
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN,
|
|
|
|
|
"host key pin reload failed session=%s server=%s server_name=%q fingerprint=%s dur_ms=%d err=%s",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
profile.Server.ID,
|
|
|
|
|
profile.Server.Name,
|
|
|
|
|
connectedFingerprint,
|
|
|
|
|
time.Since(pinHostKeyStartedAt).Milliseconds(),
|
|
|
|
|
err.Error())
|
|
|
|
|
}
|
|
|
|
|
if err == nil {
|
|
|
|
|
for i = 0; i < len(reloadedHostKeys); i++ {
|
|
|
|
@@ -3035,6 +3071,25 @@ func (api *API) runSSHSessionStream(store *db.Store, user *models.User, sessionI
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if pinnedHostKey.ID != "" {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"host key pin reload found existing session=%s server=%s server_name=%q host_key=%s fingerprint=%s dur_ms=%d",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
profile.Server.ID,
|
|
|
|
|
profile.Server.Name,
|
|
|
|
|
pinnedHostKey.ID,
|
|
|
|
|
pinnedHostKey.Fingerprint,
|
|
|
|
|
time.Since(pinHostKeyStartedAt).Milliseconds())
|
|
|
|
|
} else {
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_WARN,
|
|
|
|
|
"host key pin reload found no match session=%s server=%s server_name=%q fingerprint=%s loaded=%d dur_ms=%d",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
profile.Server.ID,
|
|
|
|
|
profile.Server.Name,
|
|
|
|
|
connectedFingerprint,
|
|
|
|
|
len(reloadedHostKeys),
|
|
|
|
|
time.Since(pinHostKeyStartedAt).Milliseconds())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if pinnedHostKey.ID == "" {
|
|
|
|
|
api.logSSHSessionConnectFailure(sessionItem, &profile, user, "pin_host_key", pinHostKeyErr)
|
|
|
|
@@ -3047,12 +3102,13 @@ func (api *API) runSSHSessionStream(store *db.Store, user *models.User, sessionI
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
api.Logger.Write(logIDSSHBroker, codit_logger.LOG_INFO,
|
|
|
|
|
"host key pinned session=%s server=%s server_name=%q fingerprint=%s algorithm=%s",
|
|
|
|
|
"host key pinned session=%s server=%s server_name=%q fingerprint=%s algorithm=%s dur_ms=%d",
|
|
|
|
|
sessionItem.ID,
|
|
|
|
|
profile.Server.ID,
|
|
|
|
|
profile.Server.Name,
|
|
|
|
|
pinnedHostKey.Fingerprint,
|
|
|
|
|
pinnedHostKey.Algorithm)
|
|
|
|
|
pinnedHostKey.Algorithm,
|
|
|
|
|
time.Since(pinHostKeyStartedAt).Milliseconds())
|
|
|
|
|
}
|
|
|
|
|
runtimeSession.SetResources(transportWS, sshClient, nil, nil)
|
|
|
|
|
closeReason = runtimeSession.CloseReason()
|
|
|
|
|