fix(ssh): correlate disconnect logs by client endpoint (#13581)

* fix(ssh): correlate disconnect logs by client endpoint

* fix(ssh): scope endpoint correlation to active sessions

---------

Co-authored-by: JayLee-sre <1.18655426e+08+JayLee-sre@users.noreply.github.com>
Co-authored-by: ssongliu <sloooop1x@gmail.com>
This commit is contained in:
Jet.
2026-08-19 10:20:16 +08:00
committed by GitHub
parent ff199245b0
commit 14728f889e

View File

@@ -1328,6 +1328,8 @@ func collectSSHLogItems(lines []string, filter, status string) []sshParsedLog {
var items []sshParsedLog
auxiliaryIndex := make(map[string]int)
sessionHasAuthEvent := make(map[string]bool)
authenticatedEndpoints := make(map[string]bool)
matchedTerminalSessions := make(map[string]bool)
for lineIndex, line := range lines {
if !shouldParseSSHLogLine(line, status, filter) {
continue
@@ -1338,11 +1340,32 @@ func collectSSHLogItems(lines []string, filter, status string) []sshParsedLog {
}
item.Index = lineIndex
item.Search = line
endpointKey := loadSSHLogEndpointKey(item.History)
if isSSHAuthEvent(item) && item.SessionKey != "" {
sessionHasAuthEvent[item.SessionKey] = true
delete(matchedTerminalSessions, item.SessionKey)
if endpointKey != "" {
if item.History.Status == constant.StatusSuccess {
authenticatedEndpoints[endpointKey] = true
} else {
delete(authenticatedEndpoints, endpointKey)
}
}
items = append(items, item)
continue
}
if endpointKey != "" && isSSHTerminalEvent(item) && authenticatedEndpoints[endpointKey] {
// A successful authentication and its terminal log can be emitted by
// different sshd processes. Associate only this active connection by
// endpoint, then clear it so a reused client port starts a new session.
delete(authenticatedEndpoints, endpointKey)
matchedTerminalSessions[item.SessionKey] = true
continue
}
if isSSHTerminalEvent(item) && matchedTerminalSessions[item.SessionKey] {
continue
}
delete(matchedTerminalSessions, item.SessionKey)
if index, ok := auxiliaryIndex[item.SessionKey]; ok {
items[index].Search += "\n" + line
continue
@@ -1381,6 +1404,13 @@ func isSSHAuthEvent(item sshParsedLog) bool {
return item.History.Status == constant.StatusSuccess || item.History.AuthMode != ""
}
func isSSHTerminalEvent(item sshParsedLog) bool {
message := item.History.Message
return strings.HasPrefix(message, "Connection closed by ") ||
strings.HasPrefix(message, "Disconnected from ") ||
strings.HasPrefix(message, "Received disconnect from ")
}
func shouldParseSSHLogLine(line, status, filter string) bool {
if !strings.Contains(line, "sshd") {
return false
@@ -1482,18 +1512,27 @@ func parseSSHLogHeader(line string) (sshLogHeader, bool) {
}
func loadSSHLogSessionKey(header sshLogHeader, data dto.SSHHistory, line string) string {
if header.Process == "sshd-session" && data.Address != "" && data.Port != "" {
return data.Address + ":" + data.Port
if header.Process == "sshd-session" {
if endpointKey := loadSSHLogEndpointKey(data); endpointKey != "" {
return endpointKey
}
}
if header.PID != "" {
return "pid:" + header.PID
}
if data.Address != "" && data.Port != "" {
return data.Address + ":" + data.Port
if endpointKey := loadSSHLogEndpointKey(data); endpointKey != "" {
return endpointKey
}
return line
}
func loadSSHLogEndpointKey(data dto.SSHHistory) string {
if data.Address == "" || data.Port == "" {
return ""
}
return net.JoinHostPort(data.Address, data.Port)
}
func normalizeSSHLogDate(dateStr string) string {
if t, err := time.Parse(time.RFC3339Nano, dateStr); err == nil {
return t.Format("2006 Jan 2 15:04:05")