enodia-sentinal/docs/GO_PORT_HANDOFF.md

145 lines
6.7 KiB
Markdown

# Go Port Handoff
Saved: 2026-07-22T01:40:52-07:00
Branch: `main`
Base commit: `6a06eba` (`docs(behavior): add behavioral spec for sweep + baseline lifecycle`)
Status: implemented and green, but uncommitted
## Worktree warning
The checkout is intentionally dirty and contains work from multiple related
continuations. Do not reset, clean, or broadly restage it.
- At this checkpoint, `git status --short` has 62 entries (105 when every
untracked file is expanded instead of collapsing directories).
- This handoff is itself currently untracked and should be included when the
Go migration tranche is eventually committed.
- `docs/behavior/02-poll-detectors.md` and `docs/behavior/index.md` already had
staged content plus later unstaged edits (`AM` / `MM`). This save did not
change the index.
- The Python GUI files and tests are separate pre-existing work. Preserve them
while continuing the Go port.
- `build/` is ignored and contains only local build output.
## Implemented Go surface
The Go module requires Go 1.25 or newer and is currently verified with
`go1.26.5-X:nodwarf5`. It uses `github.com/cilium/ebpf` v0.22.0 and builds as a
static binary.
### Polling and lifecycle
- All current Python poll detectors have Go parity: `reverse_shell`,
`ld_preload`, `deleted_exe`, `input_snooper`, `credential_access`,
`stealth_network`, `memory_obfuscation`, `egress`, `first_seen`,
`new_listener`, `persistence`, and `new_suid`.
- `--state-dir` enables isolated listener/SUID baselines, startup grace,
persistence scans, and atomic first-seen retention.
- The Go state directory must never be the Python daemon's
`/var/log/enodia-sentinel` directory.
### Event rules and native sources
- Exec rules: built-in SIDs `100001`-`100004` plus configured rules.
- Syscall rules: SIDs `100060`-`100066`.
- Typed host rules: SIDs `100067`-`100073` and `100076`-`100078`.
- Mixed JSONL routing, rule catalog operations, correlation SID `100080`, and
one shared cooldown gate are implemented.
- Embedded CO-RE exec and filtered raw-syscall probes use `cilium/ebpf` and
fail open to polling on load/attach/read failures.
- Native typed transports include UID/GID changes, chmod/chown variants,
`capset`, `finit_module`, confirmed interpreter `write`/`writev`/`pwrite64`/
`pwritev`/`pwritev2`, and successful TCP connect/bind/listen/accept calls
with `/proc` endpoint resolution.
- Generated little- and big-endian BPF objects live under
`go-agent/internal/ebpfsource/`. Run `make generate-go` only after changing
`go-agent/internal/ebpfsource/bpf/*.bpf.c`.
### Production validation path
- `make build-go` creates a stripped, static, VCS-independent binary.
- `make install-go` installs the separate binary and
`enodia-sentinel-go-sidecar.service` without replacing or enabling the
Python service.
- The hardened systemd unit has a current `systemd-analyze security` exposure
score of 4.3 (`OK`). It preserves the host `/proc`, `/tmp`, and network view
because detectors require them, while hiding the Python state tree.
- The unit uses `Type=notify`; READY is sent only after baseline setup, event
log preflight, and both requested probe-load attempts.
- JSONL remains visible in journald and is also retained as private bounded
`/var/lib/enodia-sentinel-go/events.jsonl` plus `events.jsonl.1` (64 MiB per
active segment boundary). Non-status events are synchronized to disk.
- `/var/lib/enodia-sentinel-go/heartbeat` is atomically refreshed after each
successful status emission.
- `enodia-sentinel-go --health` returns `enodia.go.health.v1` JSON and a
watchdog-friendly exit status.
- `enodia-sentinel-go --events-tail N` reads active and rotated retained events
chronologically, validates JSON, fails closed on corruption, and deliberately
works without loading the agent configuration.
- The unit now retains private `alert-YYYYMMDD-HHMMSS.{json,log}` pairs using
the required `enodia.alert.snapshot.v1` fields. Same-second alerts merge into
one pair, process context is collected best-effort from `/proc`, and shared
`max_snapshots` / `max_snapshot_age_days` limits are applied at startup,
capture, and status time.
- `enodia.status.v1` totals, severity counts, and last-alert time are populated
only from parseable retained Go snapshots. Python's unchanged `list_alerts`
and `get_alert` data layer successfully consumed a Go-produced pair.
- Snapshot incident IDs remain null and enrichment currently contains only a
Go-source marker plus process detail; incident indexing, rich enrichment,
assurance chaining, and notification fan-out are not yet ported.
- No live system service was installed or enabled during development.
## Last green verification
Run from the repository root:
```bash
python3 -m unittest discover -s tests -q
cd go-agent
GOCACHE=/tmp/enodia-go-cache go test ./...
GOCACHE=/tmp/enodia-go-cache go test -race ./...
GOCACHE=/tmp/enodia-go-cache go vet ./...
go mod verify
cd ..
make parity-go
make check-go-service
git diff --check
```
Results at save time:
- Python: 377 tests passed.
- Go unit and race suites: all packages passed.
- `go vet` and `go mod verify`: passed.
- Parity: 22 poll alerts, 5 exec alerts, 7 syscall alerts, 10 typed-host
alerts, 22 catalog records, 3 mixed-stream alerts, and 2 correlations.
- `systemd-analyze verify` and the three Go sidecar packaging/isolation tests:
passed.
- Static builds: `linux/amd64`, `linux/arm64`, `linux/riscv64`, and
`linux/s390x` passed.
- Integration smoke tests passed for static binary identity, JSON emission,
READY/STOPPING notification, heartbeat freshness/staleness, retained event
equivalence, tail selection, corruption rejection, and evidence reading with
malformed TOML. Snapshot smoke tests also passed for same-second batching,
severity aggregation, private JSON/text pairs, truthful status counts,
startup/quiet-period retention, process context, and Python dashboard-reader
compatibility.
The Python suite prints three expected CLI error lines from negative tests and
one existing `ResourceWarning` for an implicitly cleaned-up HTTP 401 object;
the suite still exits successfully.
## Resume here
1. Add Python-compatible incident grouping/index persistence so retained Go
snapshots can carry real `incident_id` values and correlation timelines.
2. Port bounded post-alert enrichment and local assurance chaining without
adding destructive response behavior or blocking the event loop on slow
collectors.
3. Connect the isolated Go snapshot/event state to explicit read-only
management consumers while keeping Python authoritative.
4. Continue broader Phase 3 rule metadata/state parity before considering any
default-service or package cutover.
Keep the validation sidecar opt-in, preserve its separate state tree, and keep
new attack/event primitives disconnected from destructive response paths.