enodia-sentinal/config/enodia-sentinel.toml
Luna 0eb5077551 Add event-driven eBPF execve layer with a Snort-style rule engine
Closes polling's blind spot (processes that exit between sweeps) with a real
eBPF probe and a declarative, data-driven detection engine — inspired by Snort
(rule language, signature IDs) and OSSEC (host-IDS framing).

New events/ subpackage:
- bcc_source.py : eBPF C tracing execve (filename, argv[1..2], ppid, uid,
                  parent comm) over a perf buffer, loaded via bcc; lazy import
                  + available()/try-except so it fails closed to poll-only when
                  bcc/root/BTF are absent — a broken probe never downs the daemon
- exec_event.py : the ExecEvent type
- rules.py      : ExecRule (sid/msg/severity/classtype + path/exec/parent/argv
                  conditions) and ExecRuleEngine; 4 shipped rules (fileless exec
                  100001, reverse-shell argv 100002, web/DB→shell RCE 100003,
                  curl|sh 100004); operators add more via exec_rules_file TOML
- monitor.py    : runs the source on a thread, routes events through the engine

Integration:
- daemon starts the monitor, shares a lock-guarded cooldown with the sweep
  loop, and feeds event alerts into the same snapshot pipeline
- Alert gains Snort-style sid + classtype; retrofitted onto all 7 poll
  detectors; snapshots and JSON now carry them
- config: ebpf_exec_monitor (default on, degrades), exec_rules_file
- systemd: opt-in ebpf.conf drop-in (relaxes MemoryDenyWriteExecute + widens
  caps for bcc's JIT) so the base unit stays hardened for poll-only
- sentinel-redteam: ebpf_exec drill (short-lived /tmp exec + /dev/tcp argv the
  poller can't see); footer now uses the Python CLI

Tests: +14 cases for the rule engine (each default rule match/non-match, rule
validation, parent-exclude). 39/39 pass. Graceful non-root degradation verified.

NOTE: the eBPF C follows bcc's execsnoop pattern but could not be run here
(BPF needs root); it wants a root smoke-test on a real host.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 07:16:53 -07:00

68 lines
3.1 KiB
TOML

# Enodia Sentinel configuration (TOML).
# Read at startup from /etc/enodia-sentinel.toml (or $ENODIA_CONFIG).
# Restart after editing: sudo systemctl restart enodia-sentinel.service
#
# Every key is optional; omitted keys keep their built-in default.
# --- timing --------------------------------------------------------------
sample_interval = 4.0 # seconds between detector sweeps
cooldown = 60 # min seconds before re-alerting the same signature
baseline_grace = 10 # seconds after start before new-listener/suid arm
suid_refresh = 3600 # seconds between SUID baseline refreshes
suid_scan_interval = 60 # seconds between (backgrounded) SUID filesystem scans
# --- detectors (omit one to disable it) ----------------------------------
detectors = [
"reverse_shell", "ld_preload", "deleted_exe",
"new_listener", "new_suid", "persistence", "egress",
]
# --- tuning --------------------------------------------------------------
# Process names treated as "interpreters" for reverse-shell / egress checks.
interpreters = [
"bash", "sh", "dash", "zsh", "ksh", "ash", "nc", "ncat", "netcat",
"socat", "telnet", "python", "python2", "python3", "perl", "ruby",
"php", "lua", "awk",
]
# Trusted public CIDRs that should NOT trip the egress detector
# (your VPS, VPN exit, monitoring endpoints, …).
egress_allow_cidrs = []
# Listener ports that never alert even if they appear after baseline.
listener_allow_ports = []
# Dirs where any SUID binary is treated as CRITICAL (attacker-writable).
suid_hot_dirs = ["/tmp", "/dev/shm", "/var/tmp", "/home", "/run/user"]
# Writable dirs always scanned for SUID even if on a separate filesystem
# (a tmpfs /tmp would otherwise be skipped by the on-device walk).
suid_scan_extra_dirs = ["/tmp", "/dev/shm", "/var/tmp", "/run/user"]
# Persistence files/dirs watched for modification.
watch_persistence = [
"/etc/cron.d", "/etc/crontab", "/etc/cron.daily", "/etc/cron.hourly",
"/etc/cron.weekly", "/var/spool/cron", "/etc/systemd/system",
"/etc/ld.so.preload", "/etc/passwd", "/etc/sudoers", "/etc/sudoers.d",
"/root/.ssh/authorized_keys", "/root/.bashrc", "/root/.profile",
]
# --- eBPF event layer ----------------------------------------------------
# Event-driven execve monitor: catches short-lived processes the poll loop
# misses, matched against the Snort-style rule engine. Requires python-bpfcc
# and root; degrades gracefully to polling otherwise.
ebpf_exec_monitor = true
# Optional path to a TOML file of extra [[exec_rules]] (sid/msg/severity/
# classtype + path_prefixes/exec_comm/parent_comm/argv_regex).
exec_rules_file = ""
# Add a 3s bpftrace execve trace to each snapshot (requires the bpftrace pkg).
capture_execve_bpftrace = false
# --- retention -----------------------------------------------------------
max_snapshots = 300 # auto-delete oldest beyond this count (0 = no cap)
max_snapshot_age_days = 60 # auto-delete older than this (0 = no age cap)
# --- notifications -------------------------------------------------------
notify_users = [] # e.g. ["luna"] — desktop notify-send on alert
notify_urgency = "critical" # low / normal / critical