enodia-sentinal/go-agent/internal/correlation/incident.go

78 lines
1.9 KiB
Go

// SPDX-License-Identifier: GPL-3.0-or-later
package correlation
// LineageOf walks each starting PID through parentOf, excluding pid 0/init so
// unrelated process trees never correlate merely because they share init.
func LineageOf(pids []int, parentOf func(int) int, depth int) map[int]bool {
seen := make(map[int]bool)
for _, start := range pids {
current := start
steps := 0
for current != 0 && current != 1 && steps <= depth {
if seen[current] {
break
}
seen[current] = true
current = parentOf(current)
steps++
}
}
return seen
}
type IndexIncident struct {
ID string `json:"id"`
Host string `json:"host"`
LastTimestamp float64 `json:"last_ts"`
Lineage []int `json:"lineage"`
}
// Assign returns the most recently active incident compatible with this host
// and lineage. Empty-lineage evidence may join any open incident on the host,
// reproducing Python's fallback for FIM/package evidence without a live PID.
func Assign(index []IndexIncident, lineage map[int]bool, when float64, host string, window int) string {
best := ""
bestTimestamp := -1.0
for _, incident := range index {
if incident.Host != host || when-incident.LastTimestamp > float64(window) {
continue
}
if len(lineage) > 0 && !intersects(incident.Lineage, lineage) {
continue
}
if incident.LastTimestamp > bestTimestamp {
best, bestTimestamp = incident.ID, incident.LastTimestamp
}
}
return best
}
func MaxSeverity(left, right string) string {
if severityRank(left) >= severityRank(right) {
return left
}
return right
}
func severityRank(value string) int {
switch value {
case "MEDIUM":
return 1
case "HIGH":
return 2
case "CRITICAL":
return 3
default:
return 0
}
}
func intersects(values []int, wanted map[int]bool) bool {
for _, value := range values {
if wanted[value] {
return true
}
}
return false
}