97 lines
2.9 KiB
Go
97 lines
2.9 KiB
Go
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package baseline
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/config"
|
|
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
|
|
)
|
|
|
|
func TestManagerGraceAndDurableFirstSeenLifecycle(t *testing.T) {
|
|
directory := t.TempDir()
|
|
watched := filepath.Join(directory, "cron-entry")
|
|
if err := os.WriteFile(watched, []byte("job"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
start := time.Date(2026, 7, 20, 12, 0, 0, 0, time.UTC)
|
|
now := start
|
|
cfg := config.Default()
|
|
cfg.LogDir = directory
|
|
cfg.BaselineGrace = 10 * time.Second
|
|
cfg.WatchPersistence = []string{watched}
|
|
manager := New(cfg, func() []string { return []string{"/usr/bin/sudo"} })
|
|
manager.Now = func() time.Time { return now }
|
|
p := 42
|
|
capture := func() (model.State, error) {
|
|
return model.State{Sockets: []model.Socket{{
|
|
State: "LISTEN", Local: "[::]:22", Comm: "sshd", PID: &p,
|
|
}}}, nil
|
|
}
|
|
if err := manager.Initialize(capture); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
now = start.Add(5 * time.Second)
|
|
state := model.State{}
|
|
if err := manager.Prepare(&state, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if state.ListenerBaseline != nil || state.PersistSince != nil {
|
|
t.Fatalf("baseline detectors armed during grace: %#v", state)
|
|
}
|
|
if err := manager.Commit(state, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
now = start.Add(11 * time.Second)
|
|
state = model.State{}
|
|
if err := manager.Prepare(&state, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(state.ListenerBaseline) != 1 || state.ListenerBaseline[0] != "22/sshd" {
|
|
t.Fatalf("unexpected listener baseline: %#v", state.ListenerBaseline)
|
|
}
|
|
if state.PersistSince == nil || *state.PersistSince != float64(start.Add(5*time.Second).Unix()) {
|
|
t.Fatalf("unexpected persistence boundary: %#v", state.PersistSince)
|
|
}
|
|
state.FirstSeenPublicDestinations["bash"] = []string{"8.8.8.8:443"}
|
|
state.FirstSeenListenerPorts["nc"] = []string{"31337"}
|
|
if err := manager.Commit(state, now); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
raw, err := os.ReadFile(filepath.Join(directory, "first-seen.json"))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var stored firstSeenStore
|
|
if err := json.Unmarshal(raw, &stored); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !stored.Initialized || stored.PublicDestinations["bash"][0] != "8.8.8.8:443" {
|
|
t.Fatalf("unexpected first-seen store: %#v", stored)
|
|
}
|
|
for _, name := range []string{"listener-baseline.json", "suid-baseline.json"} {
|
|
if _, err := os.Stat(filepath.Join(directory, name)); err != nil {
|
|
t.Fatalf("missing %s: %v", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestLoadFirstSeenFailsOpenOnCorruptStore(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "first-seen.json")
|
|
if err := os.WriteFile(path, []byte("{"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
store := loadFirstSeen(path)
|
|
if store.Initialized || store.Schema != firstSeenSchema ||
|
|
store.PublicDestinations == nil || store.ListenerPorts == nil {
|
|
t.Fatalf("unexpected fallback: %#v", store)
|
|
}
|
|
}
|