# Enodia Sentinel Go Agent (Migration Sidecar) This directory is the parallel Go implementation described in `docs/SURICATA_ASSIMILATION.md`. Python remains the production agent and the behavioral oracle until every subsystem reaches fixture and red-team parity. Current scope: - standard-library-only flat TOML loading for the settings used so far; - an injectable `/proc` process/file-descriptor/memory-map snapshot plus fail-open `ss` socket collection; - a cancellable sweep loop that emits JSONL `enodia.event.v1` records; - exact `enodia.alert.v1` parity for `ld_preload` (SID `100011`), `deleted_exe` (`100012`), `input_snooper` (`100032`), and `credential_access` (`100033`), plus `reverse_shell` (`100010`), `egress` (`100016`), `stealth_network` (`100036`), and the `/proc//maps` `memory_obfuscation` cohort (`100039`, `100048`, `100049`), and `first_seen` (`100074`, `100075`), `new_listener` (`100013`), `persistence` (`100015`), and `new_suid` (`100014`); - an opt-in live baseline lifecycle (`--state-dir`) that builds listener/SUID baselines, honors the startup grace gate, scans persistence paths, and atomically retains `first-seen.json` using the Python-compatible schema; - pure exec and syscall event models plus replayable rule engines: built-in exec SIDs `100001`–`100004`, configured `[[exec_rules]]`, and syscall SIDs `100060`–`100066` now emit Python-identical alert envelopes without requiring a kernel probe; - typed host-event SIDs `100067`–`100073` and `100076`–`100078`, a unified mixed JSONL pipeline, Python-compatible `rules-list`/`rules-show` metadata, correlation SID `100080`, and a shared per-key cooldown gate; - opt-in native exec (`--ebpf-exec`) and security-syscall (`--ebpf-syscall`) tracepoint sources built with `cilium/ebpf` v0.22.0 and CO-RE parent-process lookup. They feed the same rule/cooldown/envelope paths as replay and degrade to polling when loading, attaching, or reading is unavailable. The syscall probe filters its security-relevant syscall numbers in-kernel and also routes `setuid`/`setgid` into typed host-event SIDs `100071`/`100072`, plus `chmod`/`chown` variants into SID `100070`, `capset` into SID `100077`, and `finit_module` into SID `100078` after resolving the module FD path. Confirmed interpreter `write`, `writev`, `pwrite64`, `pwritev`, and `pwritev2` calls are enter/exit-correlated for SID `100069`. Successful IPv4/IPv6 `connect`, `bind`, `listen`, and `accept` calls feed SIDs `100067`, `100073`, `100068`, and `100076`; - `enodia.status.v1` heartbeat records whose alert totals, severity counts, and last-alert time are derived from the retained Go snapshot store; - optional bounded JSONL retention (`--event-log`) with private permissions, one rotated segment, and synchronous flushes for non-status records; - optional `enodia.alert.snapshot.v1` JSON/text pairs (`--snapshot-dir`) with same-second alert batching, best-effort `/proc` context, and Python-compatible count/age pruning; - Python-compatible `enodia.incident.v1` grouping and atomic `incidents.json` persistence, using process lineage first and the shared time-window fallback, including additive SID `100080` correlation evidence; - a shared fixture checked against the Python detector by `scripts/check-go-parity.py`. It does not yet provide Python's rich enrichment, assurance chain, notification fan-out, or management consumers for those snapshots, and it does not replace `enodia-sentinel`. The module requires Go 1.25 or newer; development is currently verified with Go 1.26.5. Run a single terminal-visible stateless sweep: ```bash cd go-agent GOCACHE=/tmp/enodia-go-cache go run ./cmd/enodia-sentinel-go --once ``` Print the packaged agent identity with `enodia-sentinel-go --version`. Exercise the live baseline lifecycle in an isolated directory (do not point the migration sidecar at the production Python agent's state directory): ```bash GOCACHE=/tmp/enodia-go-cache go run ./cmd/enodia-sentinel-go \ --state-dir /tmp/enodia-sentinel-go-state ``` Development verification from the repository root: ```bash make generate-go # only after editing internal/ebpfsource/bpf/*.bpf.c make test-go make parity-go ``` Build and install the opt-in production validation sidecar from the repository root: ```bash make build-go sudo make install-go sudo make enable-go make status-go make logs-go ``` `enodia-sentinel-go-sidecar.service` writes JSONL to the system journal and keeps baseline lifecycle files under `/var/lib/enodia-sentinel-go`. It also retains the same envelopes in `events.jsonl`, rotating to `events.jsonl.1` before the active file exceeds 64 MiB. Both files are mode `0600`; alert and incident records are synchronized before emission succeeds, while routine status records use normal kernel writeback. It never shares the Python daemon's `/var/log/enodia-sentinel` state. Both native probes are requested by the unit; a probe load failure is visible in status events and the polling loop remains active. Installing the sidecar does not enable it or alter `enodia-sentinel.service`. The unit reaches `active` only after baseline initialization, event-log and snapshot-store preflight, and both requested probe load attempts finish. Alert envelopes are also batched into private `alert-YYYYMMDD-HHMMSS.{json,log}` pairs under the same isolated directory. The JSON side implements the required `enodia.alert.snapshot.v1` fields and the service applies shared `max_snapshots` / `max_snapshot_age_days` limits. Status counts are populated only from parseable retained snapshots. Each snapshot is linked to an incident in a private atomic `incidents.json` index. Grouping uses the configured `incident_tracking`, `incident_window`, and `incident_lineage_depth` settings; same-second alert batches update one incident and one snapshot name idempotently. Enrichment is currently limited to a Go-source marker plus best-effort process detail; the Python forensic enrichment/assurance and notification pipelines remain future parity work. After every successfully emitted status record, the service atomically updates `/var/lib/enodia-sentinel-go/heartbeat` with the current Unix timestamp (mode `0600`). The file remains after shutdown so external watchdogs can identify a stale sensor. Check that signal with JSON output and a watchdog-friendly exit status: ```bash enodia-sentinel-go --health # For an isolated development run: enodia-sentinel-go --health --state-dir /tmp/enodia-sentinel-go-state ``` The command uses `heartbeat_max_age` from the shared configuration, defaults to the service state path when `--state-dir` is omitted, and exits nonzero for a missing, malformed, or stale heartbeat. For an ad-hoc retained run, set an explicit path and byte bound: ```bash enodia-sentinel-go --state-dir /tmp/enodia-go-state \ --event-log /tmp/enodia-go-state/events.jsonl \ --event-log-max-bytes 8388608 ``` Inspect recent retained records across both the active and rotated segments: ```bash sudo enodia-sentinel-go --events-tail 50 # Or inspect an ad-hoc state tree: enodia-sentinel-go --events-tail 50 --state-dir /tmp/enodia-go-state ``` The reader returns chronological JSONL, validates every source record, and fails nonzero rather than forwarding corrupted evidence. It does not load the agent configuration, so retained evidence remains available while a broken configuration prevents the service from starting. `--fixture`, `--host`, `--timestamp`, `--proc-root`, and `--suid-root` exist for deterministic parity/testing. Fixture mode never activates the live baseline manager. Production work should continue to use the default live `/proc`, hostname, and local timestamp. The kernel sources are explicitly opt-in while this remains a sidecar. They require the privileges needed to load BPF programs and create maps. A load failure is reported on stderr and in `enodia.status.v1`, then the poll loop continues: ```bash sudo go run ./cmd/enodia-sentinel-go --ebpf-exec --ebpf-syscall ``` Offline event-rule replays remain available without BPF or root: ```bash go run ./cmd/enodia-sentinel-go --exec-events ../tests/fixtures/go/exec-events.json go run ./cmd/enodia-sentinel-go --syscall-events ../tests/fixtures/go/syscall-events.json go run ./cmd/enodia-sentinel-go --host-events ../tests/fixtures/go/host-events.json go run ./cmd/enodia-sentinel-go --event-stream ../tests/fixtures/go/mixed-events.jsonl go run ./cmd/enodia-sentinel-go --rules-list ```