feat(go): port FIM/pkgdb/rootcheck integrity engines and add go-sidecar dashboard consumer
Adds sidecar-only, --state-dir-gated FIM, package-DB-anchor, and rootcheck
engines to the Go migration sidecar, wired into agent.Sweep/Initialize
behind the existing baseline lifecycle. Adds a read-only, schema-checked
Python dashboard consumer (go_sidecar_state_dir, /api/go-sidecar/*, Sidecar
tab) that never starts, stops, or mutates the Go sidecar's state.
Also fixes drift found while reconciling this work: enodia_sentinel/web.py
had reinvented local schema constants instead of using the canonical
enodia_sentinel/schemas.py catalog (now registers enodia.go.sidecar.v1
there and reuses ALERT_SNAPSHOT_V1/INCIDENT_V1); docs/RULES.md had drifted
from what `rules docs` actually generates for SIDs 100069-100078 (ruleops.py
was missing metadata for four SIDs and had stale drill text for four more),
now back in sync with a regression test pinning them together.
Updates CLAUDE.md, README.md, go-agent/README.md, and docs/{ROADMAP,
GO_PORT_HANDOFF,SURICATA_ASSIMILATION,THREAT_MODEL,OPERATIONS,SCHEMAS,
COMMAND_REFERENCE}.md to reflect the landed work.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NQivSKBqQJsayz1xcYqWzZ
This commit is contained in:
parent
1d1dee7f6e
commit
d835386381
43 changed files with 3419 additions and 72 deletions
|
|
@ -15,7 +15,10 @@ import (
|
|||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/baseline"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/detectors"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/fim"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/pkgdb"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/rootcheck"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/schema"
|
||||
)
|
||||
|
||||
|
|
@ -37,6 +40,18 @@ type Agent struct {
|
|||
// baseline manager so startup grace and durable first-seen state match the
|
||||
// Python oracle without contaminating parity inputs.
|
||||
Lifecycle *baseline.Manager
|
||||
// FIM is optional even when the sidecar is stateful. It owns slow hashing
|
||||
// and package verification and hands completed alerts back to Sweep, which
|
||||
// keeps the existing cooldown and snapshot pipeline authoritative.
|
||||
FIM *fim.Manager
|
||||
// PackageDB watches the mutable package checksum database itself. It is kept
|
||||
// separate from FIM because a package-manager database edit can otherwise
|
||||
// make pacman-based file verification appear clean.
|
||||
PackageDB *pkgdb.Manager
|
||||
// Rootcheck is likewise a sidecar-only asynchronous integrity engine. It
|
||||
// uses the captured socket state but collects independent proc/sys/tool views
|
||||
// outside this sweep's detector and event-output locks.
|
||||
Rootcheck *rootcheck.Manager
|
||||
|
||||
cooldownMu sync.Mutex
|
||||
cooldowns map[string]time.Time
|
||||
|
|
@ -76,6 +91,21 @@ func (a *Agent) Sweep() ([]map[string]any, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
if a.FIM != nil {
|
||||
// Scheduling is deliberately before detector evaluation, but FIM itself
|
||||
// runs asynchronously so a large hash tree never delays this sweep.
|
||||
a.FIM.MaybeStart(now)
|
||||
}
|
||||
if a.PackageDB != nil {
|
||||
// Like FIM, DB fingerprinting happens away from detector evaluation. A
|
||||
// completed tamper result still returns through the shared alert pipeline.
|
||||
a.PackageDB.MaybeStart(now)
|
||||
}
|
||||
if a.Rootcheck != nil {
|
||||
// Capture provides the socket view rootcheck compares with procfs and ps;
|
||||
// pass this immutable sweep snapshot to avoid a second full capture.
|
||||
a.Rootcheck.MaybeStart(now, state)
|
||||
}
|
||||
host, err := a.Host()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -131,6 +161,17 @@ func (a *Agent) Sweep() ([]map[string]any, error) {
|
|||
return nil, err
|
||||
}
|
||||
}
|
||||
if a.FIM != nil {
|
||||
// Background engines return results here instead of emitting directly, so
|
||||
// they use this Agent's shared cooldown, snapshot, and event ordering.
|
||||
alerts = append(alerts, a.FIM.Drain()...)
|
||||
}
|
||||
if a.PackageDB != nil {
|
||||
alerts = append(alerts, a.PackageDB.Drain()...)
|
||||
}
|
||||
if a.Rootcheck != nil {
|
||||
alerts = append(alerts, a.Rootcheck.Drain()...)
|
||||
}
|
||||
events, err := a.alertEvents(alerts, now, host, timestamp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -223,6 +264,22 @@ func (a *Agent) Initialize() error {
|
|||
// otherwise fixed-clock tests could arm against wall time by accident.
|
||||
a.Lifecycle.Now = a.Now
|
||||
a.initializeErr = a.Lifecycle.Initialize(baseline.CaptureFunc(a.Capture))
|
||||
if a.initializeErr != nil {
|
||||
return a.initializeErr
|
||||
}
|
||||
}
|
||||
if a.FIM != nil {
|
||||
// FIM must establish its immutable baseline before its first scheduled
|
||||
// scan. The manager writes only the caller-selected sidecar LogDir.
|
||||
a.initializeErr = a.FIM.Initialize()
|
||||
if a.initializeErr != nil {
|
||||
return a.initializeErr
|
||||
}
|
||||
}
|
||||
if a.PackageDB != nil {
|
||||
// Establishing the anchor precedes monitoring, just as the Python daemon
|
||||
// does. Later unexplained changes deliberately do not refresh it.
|
||||
a.initializeErr = a.PackageDB.Initialize()
|
||||
}
|
||||
return a.initializeErr
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue