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
64
go-agent/internal/rootcheck/manager_test.go
Normal file
64
go-agent/internal/rootcheck/manager_test.go
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package rootcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
|
||||
)
|
||||
|
||||
func TestManagerRunsAtMostOneBoundedCheckAndDrainsResults(t *testing.T) {
|
||||
cfg := config.Default()
|
||||
cfg.RootcheckInterval = time.Hour
|
||||
manager := New(cfg)
|
||||
started := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
var calls atomic.Int32
|
||||
var sawDeadline atomic.Bool
|
||||
manager.Run = func(ctx context.Context, state model.State) []model.Alert {
|
||||
_, ok := ctx.Deadline()
|
||||
sawDeadline.Store(ok)
|
||||
calls.Add(1)
|
||||
close(started)
|
||||
<-release
|
||||
return []model.Alert{{SID: SIDHiddenModule, Key: "rk:test", PIDs: []int{}}}
|
||||
}
|
||||
now := time.Date(2026, 7, 22, 12, 0, 0, 0, time.UTC)
|
||||
manager.MaybeStart(now, model.State{})
|
||||
<-started
|
||||
manager.MaybeStart(now.Add(2*time.Hour), model.State{})
|
||||
if got := calls.Load(); got != 1 {
|
||||
t.Fatalf("concurrent checks = %d, want 1", got)
|
||||
}
|
||||
close(release)
|
||||
for deadline := time.Now().Add(time.Second); time.Now().Before(deadline); {
|
||||
if alerts := manager.Drain(); len(alerts) == 1 {
|
||||
if !sawDeadline.Load() {
|
||||
t.Fatal("rootcheck did not receive a bounded context")
|
||||
}
|
||||
if alerts[0].SID != SIDHiddenModule {
|
||||
t.Fatalf("alerts = %+v", alerts)
|
||||
}
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
t.Fatal("completed rootcheck result was not drained")
|
||||
}
|
||||
|
||||
func TestDisabledManagerNeverSchedules(t *testing.T) {
|
||||
cfg := config.Default()
|
||||
cfg.RootcheckEnabled = false
|
||||
manager := New(cfg)
|
||||
var calls atomic.Int32
|
||||
manager.Run = func(context.Context, model.State) []model.Alert { calls.Add(1); return nil }
|
||||
manager.MaybeStart(time.Now(), model.State{})
|
||||
if calls.Load() != 0 {
|
||||
t.Fatal("disabled rootcheck scheduled work")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue