feat(go): expose retained incident views

This commit is contained in:
Luna 2026-07-22 03:12:34 -07:00
parent eff31b3a82
commit 6355fb403d
No known key found for this signature in database
6 changed files with 166 additions and 7 deletions

View file

@ -10,8 +10,11 @@ import (
"errors"
"flag"
"fmt"
"io"
"os"
"os/signal"
"path/filepath"
"sort"
"strings"
"sync"
"syscall"
@ -25,6 +28,7 @@ import (
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/eventlog"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/events"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/health"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/incident"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/schema"
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/sdnotify"
@ -57,6 +61,8 @@ func run() error {
rulesList := flag.Bool("rules-list", false, "emit the active event-rule catalog as JSON and exit")
rulesShow := flag.Int("rules-show", 0, "emit one event rule by SID as JSON and exit")
correlateFile := flag.String("correlate", "", "correlate a JSON array of incident summaries and exit")
incidentsList := flag.Bool("incidents-list", false, "list retained Go incidents as JSON and exit")
incidentShow := flag.String("incident-show", "", "show one retained Go incident timeline as JSON and exit")
ebpfExec := flag.Bool("ebpf-exec", false, "enable the native execve eBPF source (fails open to polling)")
ebpfSyscall := flag.Bool("ebpf-syscall", false, "enable the native security-syscall eBPF source (fails open to polling)")
checkHealth := flag.Bool("health", false, "check the state heartbeat as JSON and exit")
@ -91,6 +97,19 @@ func run() error {
}
return nil
}
if *incidentsList || *incidentShow != "" {
directory := *snapshotDir
if directory == "" {
directory = *stateDir
}
if directory == "" {
directory = "/var/lib/enodia-sentinel-go"
}
if *incidentsList {
return writeIncidentList(directory, os.Stdout)
}
return writeIncidentView(directory, *incidentShow, os.Stdout)
}
cfg, err := config.Load(*configPath)
if err != nil {
@ -307,6 +326,62 @@ func run() error {
return runner.Run(ctx, *once, emit)
}
func writeIncidentList(directory string, output io.Writer) error {
index, err := incident.LoadIndex(directory)
if err != nil {
return err
}
items := make([]*incident.Record, 0, len(index))
for _, item := range index {
items = append(items, item)
}
sort.Slice(items, func(i, j int) bool { return items[i].LastTS > items[j].LastTS })
return json.NewEncoder(output).Encode(items)
}
func writeIncidentView(directory, id string, output io.Writer) error {
index, err := incident.LoadIndex(directory)
if err != nil {
return err
}
item := index[id]
if item == nil {
return fmt.Errorf("no such incident: %s", id)
}
timeline := make([]map[string]any, 0, len(item.Snapshots))
snapshots := make([]snapshot.Report, 0, len(item.Snapshots))
for _, name := range item.Snapshots {
path := filepath.Join(directory, strings.TrimSuffix(name, ".log")+".json")
report, err := snapshot.LoadReport(path)
if err != nil {
timeline = append(timeline, map[string]any{"snapshot": name, "time": "?", "missing": true})
continue
}
snapshots = append(snapshots, report)
signatures := make([]string, 0, len(report.Alerts))
pids := make([]int, 0, len(report.Processes))
seen := map[string]bool{}
for _, alert := range report.Alerts {
if !seen[alert.Signature] {
seen[alert.Signature] = true
signatures = append(signatures, alert.Signature)
}
}
for _, process := range report.Processes {
pids = append(pids, process.PID)
}
timeline = append(timeline, map[string]any{
"snapshot": name, "time": report.Time, "severity": report.Severity,
"signatures": signatures, "pids": pids,
})
}
sort.Slice(timeline, func(i, j int) bool { return timeline[i]["time"].(string) < timeline[j]["time"].(string) })
return json.NewEncoder(output).Encode(map[string]any{
"schema": "enodia.incident.view.v1", "incident": item,
"timeline": timeline, "snapshots": snapshots,
})
}
func monitorExecSource(
ctx context.Context,
source execEventReader,