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
64 lines
1.8 KiB
Go
64 lines
1.8 KiB
Go
// 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")
|
|
}
|
|
}
|