enodia-sentinal/go-agent/README.md

173 lines
7.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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/<pid>/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;
- a shared fixture checked against the Python detector by
`scripts/check-go-parity.py`.
It does not yet provide Python's incident grouping, rich enrichment, assurance
chain, or notification fan-out 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. Enrichment is
currently limited to a Go-source marker plus best-effort process detail;
incident indexing and the Python forensic enrichment/assurance pipeline 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
```