Log-Rotation

Der Daemon handhabt beide gängigen Rotation-Muster:

  • Rotate-and-Rename (Access-Log wird umbenannt, eine neue Datei nimmt seinen Platz). Erkannt über Inode-/Device-Wechsel auf dem überwachten Pfad.
  • Copytruncate (Datei-Inhalt wird woandershin kopiert, dann das Original auf Größe Null gestutzt). Erkannt über tell() > stat().

In beiden Fällen öffnet der Daemon die Datei vom Anfang neu und schreibt info reopen after rotate dev=… ino=… (Rotate-and-Rename) oder info truncate file shrank (Copytruncate) auf stderr.

Wenn rund um die Rotation Einträge fehlen, prüfe die Rotation-Tool-Konfiguration:

  • Auf OpenBSD newsyslog ist die Standard-Rotation Rotate-and-Rename — keine spezielle Konfiguration nötig.
  • Auf Linux logrotate lieber den Default (rotieren + SIGHUP an den Webserver) statt copytruncate-Modus nehmen. Beides funktioniert, aber Rotate-and-Rename ist günstiger und verliert keine Zeilen.

rc.d-Skript (OpenBSD)

Minimal-Variante /etc/rc.d/abusive_http_watch:

#!/bin/ksh
daemon="/usr/local/sbin/abusive_http_watch"
daemon_flags="/var/www/logs/access.log"
daemon_user="_www"
. /etc/rc.d/rc.subr
rc_bg=YES
rc_reload=NO
rc_cmd $1

Dann:

doas rcctl enable abusive_http_watch
doas rcctl start abusive_http_watch
doas rcctl check abusive_http_watch

Auf Linux mit systemd siehe die Distributions-Doku; eine einfache Type=simple-Unit, die auf /usr/local/sbin/abusive_http_watch mit denselben Argumenten zeigt, reicht.

Syslog-Events

Der Daemon emittiert eine Event-Klasse — added — wenn eine neue IP die Hit-Schwelle überschreitet. Format:

<syslog-tag>[<pid>]: added ip=<ip> path=<blocklist-pfad>

Beispiel:

abusive_http_watch[12345]: added ip=198.51.100.42 path=/var/www/run/abusive_http_hosts

Per syslog.conf in eine eigene Datei für Review routen:

local0.notice                                          /var/log/abusive_http.log

Zusammen mit ABUSIVE_SYSLOG_FACILITY=local0 ergibt das ein sauberes Audit-Log jeder geblockten IP.

Monitoring-Signale

Für automatisiertes Monitoring sind zwei Indikatoren nützlich:

  • Blocklist-Wachstumsratewc -l /var/www/run/abusive_http_hosts über die Zeit. Auf einem öffentlichen Webserver sollte das stetig wachsen (mehrere IPs pro Tag bis viele pro Stunde, je nach Exposure). Eine 24h-Flatline auf einer Public-Site deutet darauf hin, dass der Daemon nicht läuft oder das falsche Log liest.
  • Prozess-Healthpgrep -f abusive_http_watch sollte immer genau eine PID liefern. Alert auf null.

Für Prometheus-Setups reicht ein winziger Exporter, der einfach die Blocklist-Datei wc -lt und die Zahl als Gauge exponiert. Kein Metrics-Endpunkt im Daemon selbst — by design, um ihn dependency-frei zu halten.

Troubleshooting

Die Blocklist-Datei bleibt leer

Mögliche Ursachen, nach Wahrscheinlichkeit:

  1. Daemon liest die falsche Datei. Im Vordergrund mit ABUSIVE_DEBUG=1 starten. Prüfen, dass er start following dev=… ino=… für den erwarteten Pfad loggt. Falls nicht, das Argument korrigieren.
  2. Kein echter Angriffstraffic. Unwahrscheinlich auf einem öffentlichen Webserver, aber bei internem möglich. Mit ABUSIVE_DEBUG=1 laufen lassen und nach [dbg] ip=…-Zeilen ohne entsprechendes [dbg] token match suchen.
  3. Whitelist verschluckt alles. /etc/abusive_http_whitelist prüfen — ein irrtümlicher /0-CIDR-Eintrag whitelisted das ganze Internet. Entfernen und neu starten.
  4. Hit-Schwelle zu hoch. ABUSIVE_MIN_HITS testweise auf 1 senken und Verhalten beobachten.

Die Blocklist wächst, aber die Firewall blockt nicht

Der Daemon schreibt die Datei. Die Firewall muss angewiesen werden, sie zu lesen. Prüfen:

  • Cron lädt die pf-Tabelle / das nftables-Set alle paar Minuten neu (der Daemon ruft pfctl nicht selbst auf).
  • Die Firewall-Regel, die auf die Tabelle verweist, ist in der richtigen Chain und quick genug, um vor späteren pass-Regeln zu greifen.

Legitime Nutzer werden geblockt

Ihre öffentliche IP oder CIDR-Range zu /etc/abusive_http_whitelist hinzufügen. Der Daemon liest die Whitelist bei jeder Zeile neu — Änderungen greifen binnen Sekunden.

Um eine einzelne IP aus der bestehenden Blocklist zu entfernen, die Datei direkt editieren und die Firewall-Tabelle neu laden. Der Daemon braucht keinen Neustart — er trägt die IP nur dann wieder ein, wenn der Nutzer erneut Token trifft.

Daemon crasht nach Log-Rotation

Sollte mit der oben beschriebenen Rotation-Handhabung nicht passieren. Wenn doch, im Vordergrund mit ABUSIVE_DEBUG=1 laufen lassen, eine Rotation auslösen und stderr mitschreiben. Das mitgeschriebene Log per Kontakt-Seite schicken — das ist ein echter Bug, den wir fixen wollen.

Sauberer Shutdown

Der Daemon fängt SIGTERM ab und beendet sich sauber nach der aktuellen Iteration:

doas rcctl stop abusive_http_watch

Loggt [info] TERM received, exiting, schließt das Logfile-Handle, flusht alle ausstehenden Writes und beendet sich mit Status 0. Keine State-Korruption, auch bei einem stark genutzten Log.