Skip to content

Monitors overview

Fenrir runs nine monitors in parallel. Each one is a small async task that produces RawEvent objects on a single shared queue. The rule engine consumes from the queue, classifies, and persists.

The 9 monitors

Monitor Type Source Cadence Typical events
auth_monitor log tailer /var/log/auth.log real-time SSH success/fail, sudo, brute force
honeypot_monitor log tailer /var/log/nginx/honeypot.log real-time Hits on fake admin paths
fail2ban_monitor log tailer /var/log/fail2ban.log real-time Bans, unbans, repeat offenders
nginx_monitor log tailer /var/log/nginx/access.log real-time Suspicious paths, scanners
ufw_monitor log tailer /var/log/ufw.log real-time Firewall drops, port scans
kernel_monitor log tailer /var/log/kern.log real-time USB, OOM, segfault, AppArmor deny
package_monitor log tailer /var/log/dpkg.log real-time apt install/upgrade/remove
baseline_monitor snapshotter ss, systemctl, /etc/passwd, find tunable New port, new service, new user, new setuid
cve_monitor snapshotter apt list --upgradable tunable Pending -security upgrades

Two flavors of monitor

Log tailers

These extend BaseMonitor and read a single file via LogTailer (an aiofiles-based tail -f with rotation detection). They:

  1. Open the file, seek to end-of-file (so previously-existing lines are NOT replayed).
  2. readline() in a loop. New lines are passed to the monitor's parse_line() method.
  3. Detect log rotation (inode change) and re-open transparently.

Latency: typically < 1 second from log write to RawEvent on the queue.

Periodic snapshotters

These run a snapshot function on a timer, save the current state to a JSON file, compare to the previous snapshot, and emit one event per delta. They:

  1. Sleep for interval seconds (configurable per monitor).
  2. Call all snapshotter functions (e.g. snapshot_listening_ports, snapshot_services).
  3. Diff against the saved JSON state file in data/.
  4. Emit one RawEvent per added/removed entry.
  5. Save the new state.

The first run after install only establishes the baseline — no alerts are produced. Drift detection starts on run #2.

Event flow

Monitor.parse_line(line)  →  RawEvent(source=..., parsed_data={...})
                              event_queue.put()
                          rule_engine.classify(raw)
                              ThreatEvent(severity, ...)
                                ┌──────┴──────┐
                                ↓             ↓
                            DB write    HIGH/CRITICAL?
                                        Investigation

Telemetry per monitor

Each monitor exposes:

  • name — short identifier
  • log_path (or [periodic:<seconds>s] for snapshotters)
  • events_count — number of RawEvent produced since startup

You can see the count on the web dashboard under "Monitor health" (coming soon — for now grep the journal: journalctl -u p3guardian | grep events_count).

Disabling a monitor

Comment out the line in p3guardian/app.py:

self.monitors = [
    AuthMonitor("auth", settings.auth_log, self.event_queue),
    # HoneypotMonitor("honeypot", ...),  ← disabled
    Fail2banMonitor("fail2ban", settings.fail2ban_log, self.event_queue),
    ...
]

Then systemctl restart p3guardian. Future Fenrir versions will move this to a config flag.

Adding a custom monitor

Subclass BaseMonitor (for tail-based) or follow the periodic-monitor pattern (see cve_monitor.py as a template). Implement parse_line() (returns RawEvent | None). Add a corresponding handler in analyzers/rule_engine.py for your parsed_data["type"] values. Register the monitor in app.py.

Read on: