Fail2Ban ile Özel Jail Kuralı Oluşturma

Sunucunu yöneten her sysadmin eninde sonunda şu gerçekle yüzleşir: standart fail2ban kurulumu işe yarar, ama yeterli değildir. Nginx için özel bir API endpoint’ini brute-force’layanlara karşı, kendi yazdığın uygulamanın log’larını izlemek için ya da SSH dışında başka servisleri korumak için mutlaka özel jail kuralları yazman gerekecek. Bu yazıda fail2ban’ın mimarisini kavrayarak sıfırdan özel jail kuralları oluşturacağız.

fail2ban Mimarisini Anlamak

Özel kural yazmadan önce fail2ban’ın nasıl çalıştığını net anlamak şart. Sistem üç temel bileşenden oluşur:

  • Filter: Log dosyasında hangi satırları “başarısız deneme” olarak sayacağını belirleyen regex kuralları
  • Jail: Hangi filter’ın kullanılacağını, kaç başarısız denemeden sonra aksiyon alınacağını ve hangi aksiyon’un tetikleneceğini tanımlayan yapılandırma
  • Action: Ban işleminde ne yapılacağını belirleyen script’ler (iptables kuralı ekle, mail gönder, vb.)

Dosya yapısına bakacak olursak:

/etc/fail2ban/
├── fail2ban.conf          # Ana yapılandırma
├── jail.conf              # Varsayılan jail tanımları
├── jail.local             # Kendi özelleştirmelerini buraya yaz
├── filter.d/              # Filter tanımları
│   ├── sshd.conf
│   ├── nginx-http-auth.conf
│   └── ...
└── action.d/              # Aksiyon tanımları
    ├── iptables.conf
    ├── iptables-multiport.conf
    └── ...

Altın kural: jail.conf ve filter.d/ içindeki orijinal dosyaları asla doğrudan düzenleme. Paket güncellemelerinde değişikliklerinin üzerine yazılır. Bunun yerine jail.local kullan ve kendi filter’larını filter.d/ altına yeni dosyalar olarak ekle.

Senaryo 1: Nginx Özel API Endpoint Koruması

Diyelim ki bir web uygulaması çalıştırıyorsun ve /api/login endpoint’ine brute-force saldırıları geliyor. Nginx log’larına bakıyorsun ve şöyle satırlar görüyorsun:

192.168.1.100 - - [15/Jan/2025:10:23:45 +0300] "POST /api/login HTTP/1.1" 401 45 "-" "python-requests/2.28.0"
192.168.1.100 - - [15/Jan/2025:10:23:46 +0300] "POST /api/login HTTP/1.1" 401 45 "-" "python-requests/2.28.0"
192.168.1.100 - - [15/Jan/2025:10:23:47 +0300] "POST /api/login HTTP/1.1" 401 45 "-" "python-requests/2.28.0"

Önce filter dosyasını oluşturalım:

sudo nano /etc/fail2ban/filter.d/nginx-api-login.conf
[Definition]
# HTTP 401 dönen POST /api/login isteklerini yakala
failregex = ^<HOST> -.*"POST /api/login HTTP/.*" 401

# Test sırasında görmezden gelmek istediğin satırlar
ignoreregex =

# Tarih pattern'i belirt (opsiyonel, genellikle otomatik algılanır)
datepattern = %%d/%%b/%%Y:%%H:%%M:%%S %%z

Şimdi bu filter’ı jail olarak tanımlayalım. jail.local dosyasını açıyoruz:

sudo nano /etc/fail2ban/jail.local
[nginx-api-login]
enabled  = true
port     = http,https
filter   = nginx-api-login
logpath  = /var/log/nginx/access.log
maxretry = 5
findtime = 300
bantime  = 3600
action   = iptables-multiport[name=nginx-api-login, port="http,https", protocol=tcp]

Buradaki parametreleri açıklayalım:

  • enabled: Jail’in aktif olup olmadığı
  • port: Hangi port’lara ban uygulanacağı
  • filter: Kullanılacak filter dosyasının adı (.conf uzantısı olmadan)
  • logpath: İzlenecek log dosyası
  • maxretry: Ban’dan önce kaç başarısız denemeye izin verileceği
  • findtime: Başarısız denemelerin sayılacağı zaman penceresi (saniye)
  • bantime: Ban süresinin saniye cinsinden uzunluğu (-1 = kalıcı)
  • action: Tetiklenecek aksiyon

Filter Test Etme: fail2ban-regex Kullanımı

Jail’i aktif etmeden önce filter’ının doğru çalışıp çalışmadığını test etmek kritik önemde. Bunun için fail2ban-regex aracını kullanıyoruz:

# Gerçek log dosyasına karşı test et
sudo fail2ban-regex /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-api-login.conf

# Veya direkt bir satırı test et
sudo fail2ban-regex '192.168.1.100 - - [15/Jan/2025:10:23:45 +0300] "POST /api/login HTTP/1.1" 401 45 "-" "python-requests/2.28.0"' /etc/fail2ban/filter.d/nginx-api-login.conf

# Verbose modda test (eşleşmeyen satırları da gösterir)
sudo fail2ban-regex --print-no-missed /var/log/nginx/access.log /etc/fail2ban/filter.d/nginx-api-login.conf

Çıktıda “Lines: X matched, Y missed, Z ignored” şeklinde bir sonuç görmelisin. Eğer matched değeri beklediğin gibi değilse regex’ini düzeltmen gerekiyor.

Senaryo 2: Özel Uygulama Log’ları İçin Jail

Daha gerçekçi bir senaryo: kendi Python/PHP/Node.js uygulamanın ürettiği log’ları izlemek istiyorsun. Uygulama şöyle log yazıyor:

2025-01-15 10:23:45 ERROR Login failed for user admin from IP 203.0.113.42
2025-01-15 10:23:46 ERROR Login failed for user root from IP 203.0.113.42
2025-01-15 10:23:47 ERROR Login failed for user test from IP 203.0.113.42

Bu format için filter:

sudo nano /etc/fail2ban/filter.d/myapp-auth.conf
[Definition]
# Uygulamanın ürettiği "Login failed" satırlarını yakala
failregex = ^%(__prefix_line)s.*Login failed for user .* from IP <HOST>$

# Tarih formatını manuel belirt
datepattern = %%Y-%%m-%%d %%H:%%M:%%S

ignoreregex =

Burada özel bir placeholder, fail2ban bunu otomatik olarak IP adresi yakalayan regex ile değiştiriyor. %(__prefix_line)s ise tarih/zaman prefix’i için kullanılan bir macro.

Jail tanımını ekleyelim:

[myapp-auth]
enabled   = true
port      = http,https,8080
filter    = myapp-auth
logpath   = /var/log/myapp/application.log
maxretry  = 10
findtime  = 600
bantime   = 7200
action    = iptables-multiport[name=myapp, port="http,https,8080", protocol=tcp]
           sendmail-whois[name=myapp, [email protected], [email protected]]

Bu örnekte iki aksiyon birden tanımladım: hem iptables ban’ı hem de mail bildirimi. Evet, action parametresine alt alta yazarak birden fazla aksiyon tanımlayabilirsin.

Senaryo 3: Tekrarlayan Saldırganlar İçin Progresif Ban

Standart fail2ban kurulumunda herkes aynı süre ile ban’lanır. Ama bazı IP’ler defalarca saldırıyor ve ban süresi dolunca tekrar geliyor. Bunun için progresif ban stratejisi uygulayabilirsin.

action.d/ altında özel bir aksiyon dosyası oluşturalım:

sudo nano /etc/fail2ban/action.d/iptables-progressive-ban.conf
[Definition]
actionstart = iptables -N f2b-<name>
              iptables -A f2b-<name> -j RETURN
              iptables -I INPUT -p <protocol> -m multiport --dports <port> -j f2b-<name>

actionstop  = iptables -D INPUT -p <protocol> -m multiport --dports <port> -j f2b-<name>
              iptables -F f2b-<name>
              iptables -X f2b-<name>

actioncheck = iptables -n -L INPUT | grep -q 'f2b-<name>[ t]'

actionban   = iptables -I f2b-<name> 1 -s <ip> -j DROP
              # Ban sayısını logla (monitoring için kullanışlı)
              echo "$(date) BAN <ip> count=$(grep -c '<ip>' /var/log/fail2ban.log)" >> /var/log/fail2ban-stats.log

actionunban = iptables -D f2b-<name> -s <ip> -j DROP

[Init]
name     = default
port     = ssh
protocol = tcp

Gerçek anlamda progresif ban için ise jail.local‘de birden fazla jail tanımı yapabilirsin, her biri farklı bantime ile:

# İlk ihlal için kısa ban
[nginx-api-login-short]
enabled   = true
filter    = nginx-api-login
logpath   = /var/log/nginx/access.log
maxretry  = 5
findtime  = 300
bantime   = 600
port      = http,https

# Tekrarlayan ihlaller için uzun ban (fail2ban'ın kendi ban log'unu izle)
[nginx-api-login-repeat]
enabled   = true
filter    = nginx-api-login-repeat
logpath   = /var/log/fail2ban.log
maxretry  = 3
findtime  = 86400
bantime   = 86400
port      = http,https

Bu yaklaşım için fail2ban.log üzerinde çalışan ek bir filter de gerekli:

sudo nano /etc/fail2ban/filter.d/nginx-api-login-repeat.conf
[Definition]
# fail2ban'ın kendi log'unda ban edilmiş IP'leri izle
failregex = fail2ban.actions.*Ban <HOST>

ignoreregex =

Whitelist ve Özel İstisna Kuralları

Üretim ortamında bazen monitoring sistemlerin, load balancer’ların ya da ofis IP’nin yanlışlıkla ban’lanması durumunu yaşarsın. Bu durumlar için ignoreip direktifini kullanıyoruz:

[DEFAULT]
# Tüm jail'ler için geçerli whitelist
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 192.168.1.50 203.0.113.0/24

[myapp-auth]
enabled  = true
filter   = myapp-auth
logpath  = /var/log/myapp/application.log
# Bu jail için ek whitelist (DEFAULT'a eklenir)
ignoreip = %(ignoreip)s 45.67.89.10
maxretry = 10
bantime  = 3600

Filter seviyesinde de istisna kuralı ekleyebilirsin. Örneğin Googlebot ve Bingbot’u görmezden gelmek istiyorsan:

[Definition]
failregex = ^<HOST> -.*"POST /api/login HTTP/.*" 401

# User-agent bazlı istisna
ignoreregex = .*Googlebot.*
              .*bingbot.*
              .*monitoring-agent.*

fail2ban Durumunu Kontrol Etme ve Yönetme

Jail’leri aktif ettikten sonra durumu izlemek için kullanabileceğin komutlar:

# Tüm jail'lerin durumunu gör
sudo fail2ban-client status

# Belirli bir jail'in detaylı durumu
sudo fail2ban-client status nginx-api-login

# Belirli bir IP'yi manuel ban'la
sudo fail2ban-client set nginx-api-login banip 203.0.113.42

# Belirli bir IP'nin ban'ını kaldır
sudo fail2ban-client set nginx-api-login unbanip 203.0.113.42

# Jail'i yeniden yükle (kural değişikliği sonrası)
sudo fail2ban-client reload nginx-api-login

# Tüm yapılandırmayı yeniden yükle
sudo fail2ban-client reload

# fail2ban'ı yeniden başlat
sudo systemctl restart fail2ban

Bir jail’in durumunu kontrol ettiğinde şuna benzer bir çıktı görürsün:

Status for the jail: nginx-api-login
|- Filter
|  |- Currently failed: 3
|  |- Total failed: 47
|  `- File list:    /var/log/nginx/access.log
`- Actions
   |- Currently banned: 5
   |- Total banned: 23
   `- Banned IP list: 203.0.113.1 203.0.113.2 ...

Gelişmiş Regex Yazma Teknikleri

fail2ban filter’larında regex yazarken dikkat etmen gereken bazı noktalar var.

IPv6 adresleri artık çok daha yaygın. placeholder’ı hem IPv4 hem IPv6 adreslerini yakalar ama bazen özel durumlar olabilir. Karmaşık log formatları için:

[Definition]
# Hem IPv4 hem IPv6 adresleri için çalışan örnek
failregex = Authentication failure for .* from <HOST> port d+

# JSON formatında log üreten uygulamalar için
# {"time":"2025-01-15T10:23:45","level":"error","msg":"login failed","ip":"203.0.113.42"}
failregex = .*"msg":"login failed","ip":"<HOST>".*

# Çoklu pattern tanımlamak için (herhangi biri eşleşirse yeter)
failregex = ^<HOST> -.*"POST /api/login.*" 401
            ^<HOST> -.*"POST /api/reset.*" 429
            ^<HOST> -.*"GET /admin.*" 403

ignoreregex =

Önemli not: fail2ban regex’leri Python’un re modülünü kullanır. PCRE değil, Python regex sözdizimi geçerli. (?P...) gibi named group’lar da kullanabilirsin ama macro’su çoğu durumda yeterlidir.

Performans Optimizasyonu

Yoğun trafikli sunucularda fail2ban aşırı CPU kullanabilir. Birkaç optimizasyon tekniği:

findtime ve bantime değerlerini log hacmine göre ayarla. Çok kısa findtime değerleri fail2ban’ın log dosyasını daha sık taramasına neden olur.

backend ayarını doğru seç:

[DEFAULT]
# polling: Varsayılan, her platform'da çalışır
# gamin: inotify tabanlı, daha verimli (gamin paketi gerekir)
# pyinotify: inotify tabanlı (python-pyinotify gerekir)
# systemd: systemd journal'ı okur
backend = systemd  # Systemd kullanan modern distro'lar için ideal

Systemd journal ile çalışmak için filter’larda journalmatch direktifini kullanabilirsin:

[sshd-custom]
enabled      = true
filter       = sshd
backend      = systemd
journalmatch = _SYSTEMD_UNIT=sshd.service
maxretry     = 3
bantime      = 86400

Üretim Ortamında Dikkat Edilmesi Gerekenler

Log rotation sonrası fail2ban’ın log dosyasını kaybetme sorunu: Çoğu distro’da logrotate ile hallolur ama kendi uygulamaların için copytruncate yerine create kullanman ve fail2ban’a SIGHUP göndermen önerilir.

iptables vs nftables: Debian 11+, Ubuntu 22.04+ ve RHEL 9+ sistemlerde nftables varsayılan. Bu sistemlerde iptables action’ları yerine nftables action’larını kullanman gerekebilir:

[myapp-auth]
enabled  = true
filter   = myapp-auth
logpath  = /var/log/myapp/application.log
maxretry = 10
bantime  = 3600
action   = nftables-multiport[name=myapp, port="8080", protocol=tcp]

Monitoring entegrasyonu: Prometheus ile fail2ban metriklerini toplamak için fail2ban-exporter kullanabilirsin. Ban olaylarını merkezi log sistemine göndermek için aksiyon dosyasına syslog veya webhook çağrısı eklemek iyi bir pratik.

Jail’lerin ilk çalıştırılması: fail2ban-client reload komutu sonrası jail’in gerçekten aktif olduğunu doğrulamayı unutma:

sudo fail2ban-client status | grep "Jail list"

Çıktıda yeni jail’inin adını görmelisin. Görmüyorsan /var/log/fail2ban.log dosyasını kontrol et, syntax hatası olabilir.

Sonuç

Özel fail2ban jail kuralları yazmak ilk başta karmaşık görünebilir ama mimariyi kavradıktan sonra son derece güçlü ve esnek bir araç haline geliyor. Önemli olan nokta: her servis, her uygulama farklı log formatı üretiyor ve standart jail’ler bunların çoğunu kapsayamıyor.

Geliştirdiğin özel filter’ları fail2ban-regex ile test etmeyi, değişikliklerini her zaman jail.local‘de yapmayı ve production’a almadan önce bantime‘ı düşük tutarak birkaç gün gözlemlemeyi alışkanlık haline getir. Yanlışlıkla kendi IP’ni ban’lamak, saat 03:00’de sunucu konsoluna erişmeye çalışmak demek.

Son olarak: fail2ban tek başına yeterli değil. Rate limiting (nginx limit_req_zone), WAF kuralları ve düzenli log analizi ile birlikte katmanlı bir güvenlik mimarisi oluşturmak en doğru yaklaşım. fail2ban bu savunmanın reaktif katmanı, önleyici katmanları da ihmal etme.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir