feat(go): report integrity anchor context
This commit is contained in:
parent
88d7a6e3b8
commit
a25c73ab1a
6 changed files with 68 additions and 8 deletions
|
|
@ -10,10 +10,12 @@ import (
|
|||
"encoding/hex"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/model"
|
||||
"codeberg.org/anassaeneroi/enodia-sentinal/go-agent/internal/netutil"
|
||||
|
|
@ -45,6 +47,7 @@ type Process struct {
|
|||
type SlowResult struct {
|
||||
Processes map[int]map[string]any
|
||||
Files map[string]map[string]any
|
||||
Integrity map[string]any
|
||||
}
|
||||
|
||||
func CollectSlow(processes []Process, paths []string, owner ...func(string) string) SlowResult {
|
||||
|
|
@ -68,6 +71,31 @@ func CollectSlow(processes []Process, paths []string, owner ...func(string) stri
|
|||
return result
|
||||
}
|
||||
|
||||
// IntegrityAnchors reports durable state presence without running FIM, package
|
||||
// verification, or rootcheck on the alert path. Those engines stay disabled
|
||||
// until their own Go parity tranches land.
|
||||
func IntegrityAnchors(directory string) map[string]any {
|
||||
return map[string]any{
|
||||
"fim_baseline": pathStatus(filepath.Join(directory, "fim-baseline.json")),
|
||||
"pkgdb_anchor": pathStatus(filepath.Join(directory, "pkgdb-anchor.json")),
|
||||
"package_verify": map[string]any{"enabled": false, "sample": 0},
|
||||
"rootcheck": map[string]any{"enabled": false, "interval": 0},
|
||||
"go_assurance": pathStatus(filepath.Join(directory, "hash-chain.jsonl")),
|
||||
}
|
||||
}
|
||||
|
||||
func pathStatus(path string) map[string]any {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return map[string]any{"path": path, "status": "missing", "age": nil}
|
||||
}
|
||||
age := int64(time.Since(info.ModTime()).Seconds())
|
||||
if age < 0 {
|
||||
age = 0
|
||||
}
|
||||
return map[string]any{"path": path, "status": "present", "age": age}
|
||||
}
|
||||
|
||||
func boundedHash(path string) any {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil || !info.Mode().IsRegular() || info.Size() > MaxHashBytes {
|
||||
|
|
|
|||
|
|
@ -74,3 +74,19 @@ func TestCollectSlowAddsInjectedPackageOwnership(t *testing.T) {
|
|||
t.Fatalf("result=%#v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrityAnchorsAreTruthfulAndReadOnly(t *testing.T) {
|
||||
directory := t.TempDir()
|
||||
anchors := IntegrityAnchors(directory)
|
||||
if anchors["fim_baseline"].(map[string]any)["status"] != "missing" ||
|
||||
anchors["rootcheck"].(map[string]any)["enabled"] != false {
|
||||
t.Fatalf("anchors=%#v", anchors)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(directory, "hash-chain.jsonl"), []byte("{}\n"), 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
anchors = IntegrityAnchors(directory)
|
||||
if anchors["go_assurance"].(map[string]any)["status"] != "present" {
|
||||
t.Fatalf("anchors=%#v", anchors)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -291,6 +291,7 @@ func (s *Store) applySlowEnrichment(job slowJob) {
|
|||
processes := enrichmentProcesses(report.Processes)
|
||||
paths := enrichmentPaths(report.Enrichment)
|
||||
updates := enrich.CollectSlow(processes, paths, provenance.PackageOwner)
|
||||
updates.Integrity = enrich.IntegrityAnchors(s.Dir)
|
||||
|
||||
// All slow I/O has completed. Take the lock only to merge into the newest
|
||||
// same-second report, so a concurrent alert capture never loses evidence.
|
||||
|
|
@ -361,6 +362,9 @@ func mergeSlow(report *Report, updates enrich.SlowResult) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if updates.Integrity != nil {
|
||||
report.Enrichment["integrity"] = updates.Integrity
|
||||
}
|
||||
}
|
||||
|
||||
func nullToNil(value any) any {
|
||||
|
|
|
|||
|
|
@ -171,6 +171,11 @@ func TestSlowEnrichmentAddsBoundedHashAndFileMetadata(t *testing.T) {
|
|||
if file["exists"] != true || file["mode"] != float64(0o600) {
|
||||
t.Fatalf("file enrichment=%#v", file)
|
||||
}
|
||||
integrity := report.Enrichment["integrity"].(map[string]any)
|
||||
if integrity["go_assurance"].(map[string]any)["status"] != "present" ||
|
||||
integrity["rootcheck"].(map[string]any)["enabled"] != false {
|
||||
t.Fatalf("integrity enrichment=%#v", integrity)
|
||||
}
|
||||
logRaw, err := os.ReadFile(filepath.Join(store.Dir, "alert-20260722-101112.log"))
|
||||
if err != nil || !strings.Contains(string(logRaw), "ENODIA SENTINEL ALERT") {
|
||||
t.Fatalf("slow log err=%v text=%q", err, logRaw)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue