Ausgehende Mails kommen nicht an. Seit einer Woche. Niemand merkt es.
Kein Bounce, kein Alert. Der Absender sieht nichts. Der Empfänger sieht nichts. Irgendwo zwischen Postfix und dem Internet: Stille.
Das Symptom
Die Postfix-Queue auf dem Mail-Gateway wächst still vor sich hin:
postqueue -p
-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
EC2F61C747 79181 Mon Mar 10 15:23:39 luke@rebellion.local
(connect to smtp.darkside.bz[192.168.66.76]:587: Connection timed out)
leia@alderaan.de
Der Smarthost smtp.darkside.bz:587 — Connection timed out. Seit einer Woche.
Intern läuft alles: Zabbix-Mails kommen an, BCC-Kopien werden zugestellt. Nur nach draußen geht nichts. Postfix retried brav, alle paar Stunden, immer dasselbe Ergebnis.
Die Diagnose
Erster Reflex: Ist der Smarthost down? Ping vom Gateway:
ssh root@gw-todesstern
ping -c 2 192.168.66.76
2 packets transmitted, 0 received, 100% packet loss
Vom Gateway aus nicht erreichbar. Aber von einem anderen Server im selben Netzwerk:
ping -c 2 192.168.66.76
64 bytes from 192.168.66.76: icmp_seq=1 ttl=53 time=9.84 ms
Ping geht. Der Smarthost lebt. Nur Traffic durch die Firewall kommt nicht an.
snort2c — die stille Blocktabelle
pfSense nutzt die pf-Tabelle snort2c für Suricata-Blocks. Steht die Smarthost-IP drin?
pfctl -t snort2c -T test 192.168.66.76
1/1 addresses match.
Treffer. Suricata hat den eigenen Smarthost geblockt.
Welche Regel hat getriggert?
grep "192.168.66.76" /var/log/suricata/suricata_vtnet0*/eve.json | tail -3
{
"alert": {
"signature_id": 2260002,
"signature": "SURICATA Applayer Detect protocol only one direction",
"category": "Generic Protocol Command Decode",
"severity": 3
},
"src_ip": "192.168.66.76",
"dest_port": 587
}
SID 2260002: “Applayer Detect protocol only one direction.” Suricata erkennt bei der SMTP-STARTTLS-Verbindung das Protokoll nur in einer Richtung — ein klassischer False Positive bei asymmetrischen TLS-Handshakes.
Jeder einzelne Zustellversuch von Postfix triggert den Alert neu. Die IP wird sofort wieder geblockt. Ein Teufelskreis.
Die Überraschung: Passlist schützt nicht vor Blocking
Die Passlist war korrekt konfiguriert. Die Smarthost-IP stand drin:
cat /usr/local/etc/suricata/suricata_*/passlist
10.0.0.0/24
192.168.66.76 # Smarthost
...
Trotzdem wurde sie geblockt. Warum?
Im Legacy IPS pcap-Mode unterdrückt die Passlist nur die Alert-Generierung — sie verhindert NICHT das Eintragen in die
snort2c-Blocktabelle.
Das ist der entscheidende Punkt: Im pcap-Mode liest Suricata den Traffic passiv mit. Die Blocking-Entscheidung und die Alert-Generierung sind zwei getrennte Pfade. Die Passlist wirkt nur auf den Alert-Pfad.
Im Inline-IPS-Mode (Netmap/NFQ) wäre die Passlist wirksam gewesen. Aber auf einem Hetzner VPS steht Inline-Mode nicht zur Verfügung — es bleibt nur pcap.
Der Fix — dreistufiger Schutz
Ein einzelner Fix reicht nicht. Im pcap-Mode braucht man mehrere Schutzebenen:
1. Suppress-Regel für die spezifische SID
Die Suppress-Regel verhindert, dass der Alert überhaupt erzeugt wird — kein Alert, kein Block-Kandidat:
suppress gen_id 1, sig_id 2260002, track by_src, ip 192.168.66.76
In pfSense ist die Suppress-Liste base64-encoded in der config.xml. Änderung per PHP:
require_once("config.inc");
require_once("suricata/suricata_defs.inc");
require_once("suricata/suricata.inc");
parse_config(true);
global $config; // PFLICHT — write_config() nutzt globale Variable
$suppress = $config["installedpackages"]["suricata"]["suppress"]["item"][0]["suppresspassthru"];
$decoded = base64_decode($suppress);
$decoded .= "\nsuppress gen_id 1, sig_id 2260002, track by_src, ip 192.168.66.76\n";
$config["installedpackages"]["suricata"]["suppress"]["item"][0]["suppresspassthru"] = base64_encode($decoded);
write_config("Suppress SID 2260002 for smarthost");
$rebuild_rules = true;
sync_suricata_package_config();
Danach Suricata neu starten — die Suppress-Regel wird erst beim Neustart geladen:
kill $(pgrep suricata)
sleep 3
/usr/local/bin/suricata -c /usr/local/etc/suricata/suricata_*/suricata.yaml --pcap=vtnet0 -D
2. Cron-Guard: Passlist-IPs jede Minute aus snort2c entfernen
Der Fallback-Schutz — falls eine andere SID die IP doch wieder blockt:
#!/bin/sh
# /usr/local/bin/snort2c_passlist_guard.sh
PASSLIST="/usr/local/etc/suricata/suricata_*/passlist"
for f in $PASSLIST; do
[ -f "$f" ] || continue
while IFS= read -r line; do
ip=$(echo "$line" | sed "s/#.*//" | tr -d " \t")
[ -z "$ip" ] && continue
result=$(pfctl -t snort2c -T test "$ip" 2>/dev/null)
case "$result" in
*"1/1"*)
pfctl -t snort2c -T delete "$ip" 2>/dev/null
echo "$(date +%Y-%m-%dT%H:%M:%S) UNBLOCKED: $ip" >> /var/log/snort2c_guard.log
;;
esac
done < "$f"
done
Als Cron-Job (jede Minute):
crontab -l
* * * * * /usr/local/bin/snort2c_passlist_guard.sh
Wichtig auf pfSense: Nicht in /etc/crontab eintragen — pfSense überschreibt diese Datei bei jeder Config-Änderung. Stattdessen crontab -e als root verwenden (User-Crontab in /var/cron/tabs/root).
3. Postfix: IPv4-Preference
Nebenbefund: Der Mail-Gateway hatte kein routbares IPv6. Postfix versuchte trotzdem zuerst IPv6, wartete 30 Sekunden auf den Timeout, und probierte erst dann IPv4. Das verdoppelte die Retry-Zeit.
postconf -e 'smtp_address_preference=ipv4'
postfix reload
Monitoring: Nie wieder 7 Tage Stille
Zabbix UserParameter: Kritische IPs in snort2c?
Auf der Firewall (zabbix_agentd.conf.d/):
UserParameter=custom.snort2c.critical_blocked,/usr/local/bin/check_snort2c_critical.sh
Das Script prüft eine Liste kritischer IPs gegen die snort2c-Tabelle:
#!/bin/sh
CRITICAL_IPS="192.168.66.76 192.168.66.133"
BLOCKED=0
for ip in $CRITICAL_IPS; do
result=$(pfctl -t snort2c -T test $ip 2>/dev/null)
case "$result" in
*"1/1"*) BLOCKED=$((BLOCKED + 1)) ;;
esac
done
echo $BLOCKED
Trigger: Wert > 0 → Disaster (Severity 5). Da gibt es nichts zu diskutieren — wenn eine kritische IP geblockt ist, brennt es.
Zabbix UserParameter: Smarthost erreichbar?
Auf dem Mail-Gateway:
UserParameter=custom.smarthost.reachable,timeout 5 bash -c 'echo QUIT | openssl s_client -connect smtp.darkside.bz:587 -starttls smtp 2>/dev/null | grep -c 250' || echo 0
Trigger: 3 aufeinanderfolgende Checks = 0 → High (Severity 4).
Deferred Queue Aging
UserParameter=custom.postfix.deferred.age,find /var/spool/postfix/deferred -type f -mmin +60 2>/dev/null | wc -l
Trigger: Wert > 0 → Average (Severity 3). Mails die länger als eine Stunde in der deferred Queue liegen, deuten auf ein systematisches Problem hin.
TL;DR
| Problem | Ursache | Fix |
|---|---|---|
Smarthost in snort2c geblockt | SID 2260002 False Positive bei STARTTLS | Suppress-Regel für SID + IP |
| Passlist wirkungslos | pcap-Mode: Passlist schützt nur vor Alerts, nicht vor Blocking | Cron-Guard entfernt Passlist-IPs aus snort2c |
| 7 Tage unbemerkt | Kein Monitoring auf snort2c-Inhalt | Zabbix: Critical IP Check + Smarthost Reachability |
| Doppelte Retry-Zeit | IPv6 nicht routbar, Postfix probiert trotzdem | smtp_address_preference=ipv4 |
Lessons Learned
IDS/IPS auf dem Gateway kann stillschweigend kritische Dienste lahmlegen. Es gibt keine Fehlermeldung nach außen. Postfix retried still. Der Smarthost antwortet nicht mit einem SMTP-Fehler. Die Queue wächst. Niemand bekommt eine Bounce-Mail, weil die Mail nie den eigenen Server verlassen hat.
Drei Erkenntnisse für pfSense + Suricata im Legacy pcap-Mode:
- Passlist ≠ Block-Schutz. Im pcap-Mode unterdrückt die Passlist nur Alerts, nicht das Blocking. Das steht nirgends prominent in der Dokumentation.
- Monitoring auf
snort2c-Inhalt ist Pflicht. Ohne Zabbix-Alert auf kritische IPs ist der Silent Failure vorprogrammiert. - Suppress vor Passlist. Für bekannte False Positives ist die Suppress-Regel der direkte Weg — sie verhindert den Alert an der Quelle.
Für produktive Umgebungen: Entweder Inline-IPS-Mode mit funktionierender Passlist, oder pcap-Mode mit Cron-Guard und aktivem Monitoring auf alle kritischen IPs. Ein “die Passlist wird das schon regeln” reicht im pcap-Mode nicht.