Sunucularında standart fail2ban filtrelerinin yakalayamadığı saldırılar görmeye başladığında, iş kendi ellerinle özel filtre yazmaya geliyor. Bu yazıda fail2ban’ın regex motorunu derinlemesine inceleyecek, gerçek dünya log örnekleriyle özel filtreler oluşturacak ve bunu production ortamında nasıl test edeceğini göreceğiz.
fail2ban Filtre Mimarisini Anlamak
fail2ban temelde üç bileşenden oluşuyor: filtreler, jaillar ve aksiyonlar. Filtreler /etc/fail2ban/filter.d/ dizininde .conf uzantılı dosyalar olarak duruyor. Her filtre, log satırlarını taramak için bir veya birden fazla regex deseni içeriyor.
Bir filtre dosyasının iskeletine bakalım:
cat /etc/fail2ban/filter.d/sshd.conf | head -40
Temel yapı şu şekilde:
[INCLUDES]
before = common.conf
[Definition]
_daemon = sshd
failregex = ^%(__prefix_line)s(?:error: PAM: )?[aA]uthentication (?:failure|error|failed) for .* from <HOST>
ignoreregex =
Buradaki anahtar kelimesi çok önemli. fail2ban bu placeholder’ı otomatik olarak IP adresini yakalayan bir regex bloğuyla değiştiriyor. %(__prefix_line)s ise common.conf dosyasından gelen önek deseni, yani tarih/saat ve servis adını kapsıyor.
common.conf’tan Gelen Değişkenler
/etc/fail2ban/filter.d/common.conf dosyası birçok hazır değişken sunuyor. Bunları kendi filtrende kullanabilirsin:
%(__prefix_line)s: Standart syslog ön eki (tarih, hostname, servis adı)%(__line_prefix)s: Daha basit prefix, bazı log formatları için kullanışlı: IPv4 ve IPv6 adreslerini otomatik yakalayan desen: Sadece IP adresini yakalar, port olmadan
Regex Sözdizimi Temelleri
fail2ban Python’un re modülünü kullanıyor. Bu yüzden Python regex sözdizimini bilmek kritik. İşte en sık kullanacağın desenler:
.: Herhangi bir karakter.*: Sıfır veya daha fazla herhangi karakter (greedy).*?: Sıfır veya daha fazla herhangi karakter (lazy/non-greedy)d+: Bir veya daha fazla rakams+: Bir veya daha fazla boşlukw+: Harf, rakam veya alt çizgi(?:...): Yakalamayan grup(?P...): İsimli yakalama grubu^: Satır başı$: Satır sonu
Önemli bir nokta: fail2ban regex’lerinde MULTILINE modunu desteklemiyor, her satır ayrı ayrı değerlendiriliyor.
Senaryo 1: Özel Web Uygulaması Log Filtresi
Diyelim ki şirket içi geliştirdiğiniz bir Python/Flask uygulaması var ve authentication hatalarını şu formatta loglıyor:
2024-01-15 14:23:45 ERROR [auth] Failed login attempt from 192.168.1.105 - username: admin
2024-01-15 14:23:47 ERROR [auth] Failed login attempt from 192.168.1.105 - username: root
2024-01-15 14:23:49 ERROR [auth] Failed login attempt from 192.168.1.105 - username: administrator
Bu format için filtre yazalım:
cat > /etc/fail2ban/filter.d/flask-auth.conf << 'EOF'
[INCLUDES]
before = common.conf
[Definition]
# Uygulama adini tanimlayalim
_appname = flask-app
# Ana basarisizlik deseni
failregex = ^d{4}-d{2}-d{2} d{2}:d{2}:d{2} ERROR [auth] Failed login attempt from <HOST>
# Ignore listesi - ic agdan gelen denemeler
ignoreregex = ^.*Failed login attempt from 127.0.0.1
^.*Failed login attempt from 10.d+.d+.d+
[Init]
# Log dosyasinin yolu jail.conf'ta da belirtilmeli
logpath = /var/log/flask-app/auth.log
EOF
Şimdi bu filtreyi test edelim:
fail2ban-regex /var/log/flask-app/auth.log /etc/fail2ban/filter.d/flask-auth.conf --print-all-matched
fail2ban-regex aracı, filtreni production’a almadan önce ne kadar eşleşme bulduğunu görmeni sağlıyor. Çıktıda Lines: X matched satırını görmen gerekiyor.
Senaryo 2: Nginx Custom Error Logları
Nginx’in varsayılan error logları fail2ban tarafından zaten işleniyor ama custom access log formatı kullanıyorsan durum farklı. Diyelim ki Nginx’i şu log formatıyla yapılandırdın:
192.168.1.200 - - [15/Jan/2024:14:30:22 +0300] "POST /wp-login.php HTTP/1.1" 403 162 "SCANNER/1.0"
10.0.0.5 - - [15/Jan/2024:14:30:25 +0300] "GET /admin/config.php HTTP/1.1" 404 0 "Mozilla/5.0"
185.220.101.45 - - [15/Jan/2024:14:30:28 +0300] "GET /.env HTTP/1.1" 404 0 "curl/7.68.0"
.env, config.php ve benzeri hassas dosyalara erişim denemelerini engellemek için:
cat > /etc/fail2ban/filter.d/nginx-sensitive-files.conf << 'EOF'
[INCLUDES]
before = common.conf
[Definition]
# Hassas dosyalara erisim deneyenleri yakala
failregex = ^<HOST> .+ "(?:GET|POST|HEAD) /(?:.env|.git|config.php|wp-config.php|.htaccess|.htpasswd|server-status|phpinfo.php) HTTP/d.d" d{3}
^<HOST> .+ "(?:GET|POST) /(?:admin|administrator|phpmyadmin|pma|mysql|myadmin)/? HTTP/d.d" 40[034]
ignoreregex = ^127.0.0.1
^::1
[Init]
maxlines = 1
EOF
Bu filtreyle birden fazla failregex deseni kullanabilirsin. fail2ban bunları OR mantığıyla değerlendiriyor, yani herhangi biri eşleşse yeterli.
Senaryo 3: Postfix ve SMTP Brute Force
Mail sunucuları sürekli saldırı altında. Standart Postfix filtresi bazı özel durumları kaçırabiliyor. Özellikle AUTH komutunu döngüsel olarak deneyen saldırıları yakalamak için:
cat > /etc/fail2ban/filter.d/postfix-sasl-custom.conf << 'EOF'
[INCLUDES]
before = common.conf
[Definition]
_daemon = postfix/smtpd
failregex = ^%(__prefix_line)swarning: [-._w]+[<HOST>]: SASL (?:LOGIN|PLAIN|(?:CRAM|DIGEST)-MD5) authentication failed(: [ A-Za-z0-9+/]*={0,2})?$
^%(__prefix_line)sNOQUEUE: reject: RCPT from S+[<HOST>]: 554
^%(__prefix_line)sNOQUEUE: reject: RCPT from S+[<HOST>]: 550 5.1.1
ignoreregex =
[Init]
# Daha buyuk log penceresine ihtiyac var
journalmatch = _SYSTEMD_UNIT=postfix.service
EOF
Bu filtreyi hem log dosyası hem de systemd journal ile kullanabilirsin.
fail2ban-regex ile Kapsamlı Test Yöntemi
Filtreyi yazdıktan sonra test aşaması en kritik kısım. Yanlış yazılmış bir regex, meşru kullanıcıları banlayabilir:
# Temel test - eslesen satirlari say
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-files.conf
# Detayli cikti - hangi satirlar eslesti
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-files.conf -v
# Eslesen satirlari goster
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-files.conf --print-all-matched
# Eslesmeyenleri de goster (debug icin)
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-files.conf --print-all-missed
# Debug modunda calistir
fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-sensitive-files.conf -D
Test çıktısında dikkat etmen gereken değerler:
- Lines: X matched – Kaç satır yakalandı
- Lines: Y missed – Kaç satır atlandı
- Lines: Z ignored – Kaç satır ignoreregex ile filtrelendi
Önemli bir ipucu: Gerçek log dosyası yoksa test için örnek log satırları içeren geçici bir dosya oluşturabilirsin:
cat > /tmp/test-auth.log << 'EOF'
2024-01-15 14:23:45 ERROR [auth] Failed login attempt from 192.168.1.105 - username: admin
2024-01-15 14:23:47 ERROR [auth] Failed login attempt from 203.0.113.42 - username: root
2024-01-15 14:24:00 INFO [auth] Successful login from 10.0.0.5 - username: johndoe
2024-01-15 14:24:05 ERROR [auth] Failed login attempt from 203.0.113.42 - username: test
EOF
fail2ban-regex /tmp/test-auth.log /etc/fail2ban/filter.d/flask-auth.conf --print-all-matched
Jail Yapılandırması ile Filtreyi Aktive Etmek
Filtren hazır ve test edildi. Şimdi jail tanımlaması yapman gerekiyor. /etc/fail2ban/jail.local dosyasına ekle:
cat >> /etc/fail2ban/jail.local << 'EOF'
[flask-auth]
enabled = true
filter = flask-auth
logpath = /var/log/flask-app/auth.log
maxretry = 5
findtime = 300
bantime = 3600
action = iptables-multiport[name=flask-auth, port="80,443,8080"]
[nginx-sensitive]
enabled = true
filter = nginx-sensitive-files
logpath = /var/log/nginx/access.log
maxretry = 3
findtime = 120
bantime = 86400
ignoreip = 127.0.0.1/8 10.0.0.0/8 192.168.0.0/16
[postfix-sasl-custom]
enabled = true
filter = postfix-sasl-custom
logpath = /var/log/mail.log
maxretry = 3
findtime = 600
bantime = 7200
EOF
Değişiklikleri uygulamak için:
# Servisi yeniden yukle (ban listesini silmeden)
systemctl reload fail2ban
# Ya da tam yeniden baslat
systemctl restart fail2ban
# Jail durumunu kontrol et
fail2ban-client status nginx-sensitive
# Aktif ban listesini gor
fail2ban-client status nginx-sensitive | grep "Banned IP"
Gelişmiş Regex Teknikleri
Named Groups Kullanımı
fail2ban, log satırından sadece IP değil başka bilgiler de çekebiliyor. Bu özellik özellikle %(username)s gibi ek bilgileri loglamak istediğinde kullanışlı:
cat > /etc/fail2ban/filter.d/app-advanced.conf << 'EOF'
[INCLUDES]
before = common.conf
[Definition]
# Hem IP hem kullanici adini yakala
failregex = ^d{4}-d{2}-d{2}Td{2}:d{2}:d{2} WARN auth: Invalid credentials for user '(?P<user>w+)' from <HOST>$
# Bircok farkli hata mesajini ayni filtrede yakala
failregex = ^%(__prefix_line)s(?:Invalid user|User not found|Bad password): .+ from <HOST>$
^[d+] (?:d{4}-d{2}-d{2} )?d{2}:d{2}:d{2} - auth_error - ip=<HOST>
ignoreregex = ^.*healthcheck.*$
^.*monitoring.*$
EOF
Multiline Log Desteği
fail2ban 0.10+ sürümlerinde sınırlı multiline desteği var. Bazı uygulamalar stack trace’i birden fazla satıra yazıyor. maxlines parametresiyle bunu kontrol edebilirsin:
cat > /etc/fail2ban/filter.d/java-app.conf << 'EOF'
[INCLUDES]
before = common.conf
[Definition]
failregex = ^<ADDR> .* AuthenticationException: Bad credentials$
<SKIPLINES>^ at org.springframework.security
ignoreregex =
[Init]
maxlines = 10
EOF
direktifi aradaki satırları atlayarak sonraki regex’i uyguluyor.
Log Rotasyonu ve fail2ban Entegrasyonu
Özel filtreler yazarken sıkça karşılaşılan bir sorun, log rotasyonu sırasında fail2ban’ın log dosyasını kaybetmesi. Bunu çözmek için:
# logrotate yapilandirmasi
cat > /etc/logrotate.d/flask-app << 'EOF'
/var/log/flask-app/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 www-data adm
postrotate
fail2ban-client set flask-auth addlogpath /var/log/flask-app/auth.log
endscript
}
EOF
Alternatif olarak, logpath direktifinde glob pattern kullanabilirsin:
logpath = /var/log/flask-app/auth.log
/var/log/flask-app/auth.log.1
Debug ve Sorun Giderme
Filtreler bazen beklenmedik davranışlar gösteriyor. İşte en sık karşılaşılan sorunlar ve çözümleri:
IP adresi yakalanmıyor: placeholder’ının tam olarak IP adresinin bulunduğu yere denk geldiğinden emin ol. fail2ban-regex çıktısında Lines: X matched, 0 IPs found görüyorsan bu sorun var demek.
Regex çok geniş yazılmış: Meşru kullanıcıları da ban listesine alıyorsan ignoreregex bölümüne kendi IP aralığını ekle.
Encoding sorunları: Özellikle Türkçe karakter içeren log dosyalarında sorun çıkabiliyor:
# fail2ban'in log dosyasini hangi encoding ile okudugunu gormek icin
fail2ban-regex /var/log/app.log /etc/fail2ban/filter.d/myfilter.conf -D 2>&1 | grep -i encoding
# Encoding problemini asmak icin jail.conf'ta
[myapp]
logencoding = utf-8
Performans sorunu: Çok karmaşık regex’ler yüksek trafikli sunucularda CPU kullanımını artırıyor. .* yerine daha spesifik desenler kullan:
# Yavas (greedy)
failregex = ^.*Failed.*<HOST>.*$
# Hizli (spesifik)
failregex = ^d{4}-d{2}-d{2} d{2}:d{2}:d{2} ERROR .{1,50}Failed.{1,50}<HOST>
Gerçek Dünya Örneği: API Rate Limiting Filtresi
Bir REST API’yi brute force’tan korumak için kapsamlı bir örnek:
cat > /etc/fail2ban/filter.d/api-bruteforce.conf << 'EOF'
[INCLUDES]
before = common.conf
[Definition]
# JWT token hatasi - gecersiz veya suresi dolmus token
_api_prefix = d{4}-d{2}-d{2}Td{2}:d{2}:d{2}(?:.d+)?(?:Z|[+-]d{2}:d{2})?
failregex = ^%(_api_prefix)s [ERROR] api.auth: (?:Invalid|Expired) token from <HOST>$
^%(_api_prefix)s [WARN] api.ratelimit: Rate limit exceeded for <HOST>$
^%(_api_prefix)s [ERROR] api.auth: Authentication failed - IP: <HOST>, attempts: d+$
# Monitoring araclari ve load balancer'lari yoksay
ignoreregex = ^.*[INFO].*healthcheck.*$
^.*user_agent="(?:Prometheus|Datadog|NewRelic).*".*$
^.*<HOST>.*[DEBUG].*$
[Init]
# API loglari icin UTF-8 zorunlu
logencoding = utf-8
EOF
Bu filtreyi daha da güçlü kılmak için action tarafında özel bir script çalıştırabilirsin, örneğin ban edilen IP’yi bir Slack kanalına bildirebilirsin.
Filtreleri Versiyonlamak ve Yönetmek
Birden fazla özel filtre yazmaya başlayınca versiyon kontrolü hayat kurtarıcı oluyor:
# Filtre dizinini git ile yonet
cd /etc/fail2ban/filter.d/
git init
git add *.conf
git commit -m "Initial filter configurations"
# Degisiklik yaptiktan sonra
git add flask-auth.conf
git commit -m "Flask auth filtresi guncellendi - encoding duzeltmesi"
# Eski versiyona don
git checkout HEAD~1 flask-auth.conf
systemctl reload fail2ban
Değişiklikleri canlıya almadan önce staging ortamında test etmek için basit bir wrapper script yazabilirsin:
#!/bin/bash
# /usr/local/bin/test-filter.sh
FILTER=$1
LOGFILE=$2
if [ -z "$FILTER" ] || [ -z "$LOGFILE" ]; then
echo "Kullanim: $0 <filtre_adi> <log_dosyasi>"
exit 1
fi
echo "=== Filtre Test Ediliyor: $FILTER ==="
echo ""
RESULT=$(fail2ban-regex "$LOGFILE" "/etc/fail2ban/filter.d/${FILTER}.conf" 2>&1)
MATCHED=$(echo "$RESULT" | grep "Lines: " | awk '{print $2}')
IPS=$(echo "$RESULT" | grep "IPs found" | awk '{print $1}')
echo "Eslesen satirlar: $MATCHED"
echo "Bulunan IP sayisi: $IPS"
echo ""
echo "Detay icin -v parametresi ekleyin"
Sonuç
fail2ban’ın gerçek gücü, hazır filtrelerle değil kendi uygulamalarına özel yazdığın filtrelerle ortaya çıkıyor. Bir filtrenin kalitesi, yakaladığı saldırı sayısıyla değil yanlış pozitif üretmemesiyle ölçülüyor. Bu yüzden her yeni filtre yazdığında önce fail2ban-regex ile kapsamlı test yap, ignoreregex bölümünü özenle doldur ve production’da ilk birkaç gün log’ları yakından takip et.
Regex yazarken acele etme. Karmaşık bir deseni tek seferde doğru yazmak yerine kademeli olarak geliştir: önce en basit haliyle yaz, test et, giderek daha spesifik hale getir. fail2ban-regex --print-all-missed çıktısı, yakalamayı umduğun ama kaçırdığın satırları göstererek filtreyi nasıl geliştireceğine dair en iyi ipuçlarını veriyor.
Son olarak, özel filtrelerini sistem güncellemelerinden etkilenmeyecek şekilde /etc/fail2ban/filter.d/ altında tutmaya devam et ve düzenli aralıklarla yedekle. fail2ban paket güncellemesi /etc/fail2ban/ dizinini ezmez ama bir şeyler ters gidebilir, git ile versiyon kontrolü bu riski minimize ediyor.