195 lines
10 KiB
TOML
195 lines
10 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",
|
|
"input_snooper", "credential_access", "stealth_network",
|
|
"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 = []
|
|
# Listeners owned by these process names never alert (e.g. P2P clients that
|
|
# churn ports: qbittorrent, transmission, nicotine, kdeconnectd, syncthing…).
|
|
listener_allow_comms = []
|
|
# Suppress new_listener when the listening binary ships with an installed
|
|
# package (strong provenance signal). The single best knob for a desktop /
|
|
# seedbox that runs lots of legitimate networked apps. Off by default so the
|
|
# detector stays honest; turn on if new_listener is noisy.
|
|
suppress_package_owned_listeners = false
|
|
|
|
# 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",
|
|
]
|
|
|
|
# Processes expected to keep keyboard/HID event devices open.
|
|
input_snooper_allow_comms = [
|
|
"Xorg", "Xwayland", "gnome-shell", "kwin_wayland", "sway", "Hyprland",
|
|
"systemd-logind", "input-remapper", "keyd", "kanata",
|
|
]
|
|
|
|
# Processes expected to read credential stores during normal auth/user activity.
|
|
credential_access_allow_comms = [
|
|
"sshd", "sudo", "su", "login", "gdm-session-worker", "polkitd",
|
|
"gnome-keyring-daemon", "kwalletd5", "kwalletd6", "firefox", "chromium",
|
|
"chrome", "google-chrome", "brave", "brave-browser",
|
|
]
|
|
# Extra exact paths or directory prefixes treated as credential material.
|
|
credential_access_extra_paths = []
|
|
|
|
# Expected owners of packet/raw/special protocol sockets.
|
|
stealth_network_allow_comms = [
|
|
"NetworkManager", "systemd-networkd", "wpa_supplicant", "dhcpcd",
|
|
"tcpdump", "dumpcap", "wireshark", "suricata", "zeek",
|
|
]
|
|
# Socket family names to ignore entirely if they are normal in your environment:
|
|
# raw, sctp, dccp, packet, mptcp, tipc, xdp, vsock.
|
|
stealth_network_allow_kinds = []
|
|
|
|
# --- file integrity monitoring (FIM) ------------------------------------
|
|
# SHA-256 baseline of security-critical files the package manager doesn't track
|
|
# (configs, /usr/local, systemd units, keys). A pacman PostTransaction hook runs
|
|
# `enodia-sentinel fim-update` after every upgrade, so package changes never
|
|
# alert. Package-owned binaries are instead verified against the distro's own
|
|
# checksums via `pacman -Qkk` (fim_pkg_verify).
|
|
fim_enabled = true
|
|
fim_scan_interval = 300 # seconds between hash scans
|
|
fim_paths = [] # [] = built-in critical set; or your own list
|
|
fim_pkg_verify = false # run `pacman -Qkk` (thorough but slow)
|
|
fim_pkg_verify_interval = 21600 # seconds between package verifications
|
|
|
|
# --- tamper-evidence -----------------------------------------------------
|
|
# Detect out-of-band edits to the pacman DB itself (an attacker rewriting a
|
|
# stored checksum to mask a modified binary). The DB anchor is refreshed only
|
|
# by the pacman hook, so a DB change with no logged transaction = tampering.
|
|
pkgdb_verify = true
|
|
pkgdb_interval = 600 # seconds between DB integrity checks
|
|
heartbeat_max_age = 120 # daemon heartbeat older than this = stale/silent
|
|
# Layer 2: compare on-disk files to hashes in cached signed packages. This
|
|
# survives a rewritten local package DB, but needs a populated package cache and
|
|
# costs untar+hash work, so it samples packages on a slow cadence.
|
|
pkgdb_pkgverify = false
|
|
pkgdb_pkgverify_interval = 21600 # seconds between signed-package passes
|
|
pkgdb_pkgverify_sample = 40 # packages verified per pass (rotates)
|
|
# Sentinel's own binaries/config/units/hook are always FIM-watched (self-
|
|
# integrity). For real teeth, make them immutable: chattr +i <path>
|
|
# and run an external `enodia-sentinel watchdog` against this host's dashboard.
|
|
|
|
# --- anti-rootkit cross-view --------------------------------------------
|
|
# Ask the same question two ways and diff the answers: kill(0) vs /proc for
|
|
# hidden processes, /sys/module vs /proc/modules for hidden LKMs, /proc/net/tcp
|
|
# vs ss for hidden TCP/UDP/raw/special-protocol sockets, interface flags for
|
|
# promiscuous sniffing, known rootkit module names, raw ICMP/SCTP-style
|
|
# channels, per-module taint, and global kernel taint.
|
|
rootcheck_enabled = true
|
|
rootcheck_interval = 300 # seconds between cross-view sweeps
|
|
rootcheck_pid_cap = 65536 # upper PID to brute-force via kill(0)
|
|
# Legitimate tainted modules you accept (exact module names), e.g. a vendor GPU
|
|
# or virtualization driver. Known rootkit module names are never suppressed here.
|
|
rootcheck_module_allow = []
|
|
|
|
# --- incident grouping ---------------------------------------------------
|
|
# Collapse related alerts into one incident: process-lineage first (shared PID
|
|
# or ancestor — the web server / SSH session the alerts descend from), then a
|
|
# time fallback for PID-less alerts (FIM drift, package tamper). Each snapshot
|
|
# gets an incident_id; browse with `enodia-sentinel incident list/show/export`.
|
|
incident_tracking = true
|
|
incident_window = 1800 # seconds an incident stays open for new alerts
|
|
incident_lineage_depth = 8 # ancestors walked when correlating
|
|
|
|
# --- host posture --------------------------------------------------------
|
|
# Config-hygiene audit run on demand (`enodia-sentinel posture check`), not in
|
|
# the daemon loop: root SSH login, password auth, passwordless sudo, world-
|
|
# writable PATH dirs, loose perms on sensitive files, and package-signature
|
|
# policy. Paths are overridable mostly for testing on non-standard hosts.
|
|
posture_sshd_config = "/etc/ssh/sshd_config"
|
|
posture_sudoers = "/etc/sudoers"
|
|
posture_sudoers_dir = "/etc/sudoers.d"
|
|
# posture_path = [] # PATH dirs to audit; empty = safe default set
|
|
|
|
# --- 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: desktop ---------------------------------------------
|
|
notify_users = [] # e.g. ["luna"] — desktop notify-send on alert
|
|
notify_urgency = "critical" # low / normal / critical
|
|
|
|
# --- notifications: phone push ------------------------------------------
|
|
# A backend turns on when its required keys are set. Only alerts at or above
|
|
# notify_min_severity are pushed.
|
|
notify_min_severity = "HIGH" # MEDIUM / HIGH / CRITICAL
|
|
|
|
# ntfy (open-source, self-hostable). Install the ntfy app and subscribe to the
|
|
# topic; keep the topic name secret — it is the only access control on ntfy.sh.
|
|
notify_ntfy_url = "" # e.g. "https://ntfy.sh" or your own server
|
|
notify_ntfy_topic = "" # e.g. "enodia-<random>"
|
|
notify_ntfy_token = "" # optional Bearer token (protected topics)
|
|
|
|
# Pushover
|
|
notify_pushover_token = "" # application token
|
|
notify_pushover_user = "" # user/group key
|
|
|
|
# Generic webhook — receives the alert JSON via POST (Discord/Slack/your own).
|
|
notify_webhook_url = ""
|
|
|
|
# Optional dashboard base URL embedded in pushes (e.g. your Tailscale URL).
|
|
dashboard_url = ""
|
|
|
|
# --- web dashboard (read-only) ------------------------------------------
|
|
web_bind = "" # "" = auto-detect Tailscale IP; or an explicit address
|
|
web_port = 8787
|
|
web_token = "" # bearer token; auto-generated + saved if empty on a
|
|
# non-loopback bind. Set explicitly to keep the unit
|
|
# fully read-only.
|
|
# HTTPS is mandatory. If these are blank, the dashboard auto-generates a
|
|
# self-signed cert/key under log_dir. Add a browser exception for that cert, or
|
|
# point these at your own local/private CA certificate.
|
|
web_tls_cert = ""
|
|
web_tls_key = ""
|