"Nginx" Etiketindeki Gönderiler

Nginx etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster


nginx ile "Dağıtık" Saldırılara Karşı Alt Ağ Bazlı Rate Limiting

Ya da: 70 farklı IP'den gelen ama hiçbiri tek başına şüpheli görünmeyen bir saldırıyı nasıl durdururuz?

Bir gün sunucum aniden yavaşlamaya başladı. Log'lara baktığımda klasik bir "tek kötü IP" görüntüsü yoktu — bunun yerine aynı /24 bloğundan (yani "192.168.1.X" tarzı, sadece son rakamı değişen) 70'e yakın farklı IP, her biri kendi başına makul sayıda istek atıyordu. Hiçbiri tek başına alarm verecek kadar agresif değildi, ama hepsi birden sunucuyu dize getiriyordu.

Bu yazıda, bu tarz "dağıtık ama düşük yoğunluklu" saldırılara karşı nginx'in kendi imkanlarıyla (üçüncü parti araç, Cloudflare falan olmadan) nasıl önlem alınacağını, sıfırdan anlatıyorum.


1. Önce temel: nginx'te "hız sınırlama" nasıl çalışır?

nginx, gelen istekleri bir kovaya damlayan su gibi düşünür (buna "leaky bucket" algoritması denir). Şöyle bir kural tanımlarsın:

limit_req_zone $binary_remote_addr zone=mylimit:10m rate=10r/s;

Türkçesi: "Her IP için ayrı bir kova aç (`$binary_remote_addr` = IP adresi). Bu kova saniyede 10 isteklik bir akışı sindirebilsin. Bütün kovaları tutmak için 10MB'lık bir bellek alanı ayır (`zone=mylimit:10m`)."

Sonra bu kuralı bir yere uygulaman lazım:

location / {
    limit_req zone=mylimit burst=20 nodelay;
}
  • burst=20: ani patlamalarda 20 isteklik ekstra tampon tanı (yoksa gerçek bir kullanıcının açtığı sayfadaki 5-6 paralel istek bile anında engellenir).
  • nodelay: bu tampondaki istekleri bekletmeden hemen işle, sadece tampon dolunca reddet. (Bunu koymazsan nginx istekleri sıraya koyup yavaşça işler — bu da gerçek kullanıcıyı fark ettirmeden yavaşlatır.)

Eşik aşılınca nginx varsayılan olarak 503 Service Unavailable döner.

2. Peki neden bu bazen yetmiyor?

Kritik nokta şu: kova, anahtar ne ise ona göre açılır. Yukarıdaki örnekte anahtar $binary_remote_addr, yani IP adresi. Bu demek oluyor ki:

100 farklı IP saldırıyorsa, nginx 100 ayrı kova açar. Hiçbiri diğerini etkilemez. Saldırgan, her IP'yi eşiğin biraz altında tutarak bu korumayı tamamen atlatabilir — ki botnet'ler zaten genelde böyle çalışır.

Benim yaşadığım durum tam olarak buydu: 70 farklı IP, hepsi aynı /24 bloğundan (örnek: 57.141.0.3, 57.141.0.61, 57.141.0.64...), hiçbiri tek başına eşiği geçmiyordu.

3. Çözüm: map ile "sahte" bir ortak anahtar üretmek

İhtiyacımız olan şey: 70 farklı IP'nin aynı kovaya düşmesi. nginx'te "bu IP'nin /24'ü nedir" diye hazır bir değişken yok, ama map direktifiyle kendimiz üretebiliyoruz:

map $remote_addr $subnet_key {
    default                                   $remote_addr;
    "~^(?<net>\d+\.\d+\.\d+)\.\d+$"            $net;
}

Bu satır korkutucu görünebilir ama parça parça çok basit. Bir IP adresi üzerinden (57.141.0.71) soldan sağa gidelim:

ParçaAnlamı
~"Bu bir düz metin değil, bir regex kalıbı" (nginx map'e özel bir işaret)
^ ... $"Metnin başından sonuna kadar tamamen eşleşsin"
\d"Bir rakam" (0-9)
\d+"Bir veya daha fazla rakam"
\."Gerçek bir nokta karakteri" (düz . regex'te "her şey" demek, o yüzden \ ile "gerçekten nokta" yapıyoruz)
(?<net>...)"Bu parantezin içinde eşleşeni bir kenara ayır, adını net koy, sonra $net diye kullanayım"

Şimdi bunu 57.141.0.71 üzerine uygulayalım:

^                → başlangıç
\d+              → "57"
\.               → "."
\d+              → "141"
\.               → "."
\d+              → "0"      ┐
                             ├─ bunlar (?<net>...) İÇİNDE → net = "57.141.0"
\.               → "."      ┘   (parantez burada kapanıyor)
\d+              → "71"     ← SON rakam grubu parantez DIŞINDA, yani atılıyor
$                → bitiş

Sonuç: $net = "57.141.0" — son oktet (.71) kırpıldı. 57.141.0.3 için de aynı işlemi yapsan yine "57.141.0" çıkar. İşte tam istediğimiz şey: son rakamı ne olursa olsun, aynı /24 bloğundaki her IP artık aynı $subnet_key'e sahip.

Genel kural olarak aklında kalsın: \d+ = rakam(lar), \. = gerçek nokta, (?<isim>...) = "bu kısmı işaretle", ^...$ = "baştan sona tam eşleşsin". Bu dört kalıp, günlük regex ihtiyaçlarının büyük kısmını karşılar.

4. Hepsini birleştirmek

Artık $subnet_key değişkenimiz var, onu normal limit_req_zone'da anahtar olarak kullanabiliriz:

http {
    # ... nginx.conf'un http{} bloğu içinde, herhangi bir server{} bloğunun dışında:

    map $remote_addr $subnet_key {
        default                                   $remote_addr;
        "~^(?<net>\d+\.\d+\.\d+)\.\d+$"            $net;
    }

    limit_req_zone $subnet_key zone=subnet_limit:10m rate=30r/s;

    server {
        server_name example.com;

        location / {
            limit_req zone=subnet_limit burst=60 nodelay;
            # ... geri kalan config'in (proxy_pass vb.) aynen kalıyor
        }
    }
}

Not: map ve limit_req_zone sadece http{} seviyesinde tanımlanabilir (server/location içinde olmaz). limit_req (uygulama satırı) ise istediğin server{} veya location{} içine yazılır — böylece sadece saldırı gördüğün siteyi etkiler, diğerlerine dokunmaz.

5. Neden her yere değil, tek tek siteye uyguluyoruz?

Aynı sunucuda birden fazla site barındırıyorsan, bu kuralı hepsine birden http{} seviyesinde yazmak cazip gelebilir — teknik olarak mümkün, çünkü nginx ayarları üstten alta miras alınır. Ama önermiyorum, çünkü:

  • Her sitenin trafik "kişiliği" farklıdır. Bir webmail sayfası sürekli arka planda "yenile" isteği atar, bir blog sayfası öyle çalışmaz — tek bir sayı ikisine de adil oturmaz.
  • Yanlış ayarın etki alanı. Rakamları ilk seferde muhtemelen tam doğru tutturamazsın (kalibrasyon zaman alır). Global yazarsan bir hata TÜM siteleri aynı anda etkiler; tek siteye yazarsan hata payı orada sınırlı kalır.
  • Sadece kanıt olan yere uygula. Saldırı gördüğün siteye özel önlem al; saldırı görmediğin siteye körlemesine kısıtlama koyup gerçek kullanıcıları riske atma.

6. Uygularken dikkat edilecekler

  1. Önce mutlaka syntax testi yap:
    nginx -t
    "syntax is ok" / "test is successful" görmeden asla reload etme.
  2. Reload et (restart değil):
    service nginx reload
    Reload, aktif bağlantıları kesmeden config'i tazeler.
  3. İlk birkaç gün error log'unu izle:
    grep "limiting requests" /var/log/nginx/error.log
    Bu satırlar, kuralın gerçekten kimi/ne zaman engellediğini gösterir. Gerçek kullanıcılar burada görünmeye başlarsa, rate/burst değerlerini yukarı çek. Saldırı hâlâ geçiyorsa aşağı çek.

Özet

Klasik IP-bazlı rate limiting, saldırı tek bir kaynaktan geldiğinde işe yarar. Saldırı onlarca/yüzlerce IP'ye dağıtılmışsa (ki modern botnet'ler genelde böyle çalışır), tek çare, isteği hangi IP attığına değil, hangi ağ bloğuna ait olduğuna göre saymaktır. Bunun için nginx'e hazır bir özellik yok, ama map direktifiyle üç-beş satırlık bir regex'le kendi çözümünü yazabiliyorsun — üçüncü parti bir servise ihtiyaç duymadan, tamamen kendi sunucunda.


Herkese Merhaba,

Bugün sizlere pfx uzantılı dosyalardan bahsetmek istiyorum. pfx uzantılı dosyaların kısa bir tanımı ile başlayıp bu dosya ile neden çalışmam gerektiğini ve üzerinde yaptığım işlemleri ileride yine lazım olur diyerek kayıt altına almak isterim. pfx nedir, ne işe yarar, nerelerde kullanılır gibi sorularla başlayalım;

PFX dosyası (Personal Information Exchange), bir sertifika dosya biçimidir ve genellikle .pfx veya .p12 uzantısıyla kullanılır. Bu dosya türü, dijital sertifikalar ve özel anahtarlar gibi güvenlik bilgilerini saklamak ve taşımak için kullanılır. PFX dosyası genellikle aşağıdaki bileşenleri içerir:

  1. Dijital Sertifikalar:

    • Bu sertifikalar, kimlik doğrulama ve güvenlik için kullanılan genel anahtarları içerir. Bir sunucu veya istemcinin kimliğini doğrulamak için kullanılır.
  2. Özel Anahtarlar:

    • Özel anahtarlar, dijital imzaların oluşturulması ve şifreleme/deşifreleme işlemleri için kullanılır. Bu anahtarlar gizli tutulmalı ve yalnızca yetkili kişiler tarafından erişilebilir olmalıdır.
  3. Kök ve Ara Sertifikalar:

    • Kök sertifikalar, güven zincirinin en üstündeki sertifikalardır ve genellikle bir sertifika otoritesi (CA) tarafından imzalanmıştır. Ara sertifikalar ise kök sertifikalar ile son kullanıcı sertifikaları arasında bir köprü görevi görür.

PFX Dosyasının Kullanım Alanları

  1. Sunucu Kimlik Doğrulaması:

    • Web sunucuları (örneğin, HTTPS üzerinden erişilen web siteleri) genellikle SSL/TLS sertifikalarını kullanarak kimliklerini doğrular. PFX dosyası, bu tür sertifikaları ve özel anahtarları tek bir dosyada saklayarak sunucu yapılandırmasını kolaylaştırır.
  2. İstemci Kimlik Doğrulaması:

    • İstemci sertifikaları, istemcilerin (örneğin, bir web sitesine erişen kullanıcıların) kimliklerini doğrulamak için kullanılır. PFX dosyası, bu sertifikaları ve özel anahtarları içererek güvenli erişimi sağlar.
  3. E-posta Güvenliği:

    • S/MIME protokolü ile e-postaların şifrelenmesi ve dijital olarak imzalanması için sertifikalar kullanılır. PFX dosyası, bu sertifikaları ve özel anahtarları saklayarak e-posta güvenliğini artırır.

PFX Dosyasının İçeriğini İncelemek

PFX dosyasının içeriğini incelemek ve sertifika bilgilerini görüntülemek için openssl gibi araçlar kullanılabilir. Örneğin, aşağıdaki komut ile bir PFX dosyasının içeriği görüntülenebilir:

Bu dosyalar genelde IIS üzerinde SSL tanımlaması için kullanılıyorlar. Bana da böyle bir sunucudan alınarak verildi ve Nginx üzerinde kullanmam gerekti. Bunu yapmak için fullchain.pem ve privkey.pem dosyalarını pfx dosyasından çıkartmak gerekiyor. Şimdi bu işlemi OpenSSL ile nasıl yapabileceğimizi görelim. İhtiyacımız olan şeyler pfx dosyası ve bu dosyanın varsa şifresi. Linux/Unix makinemizde komut satırına giderek şu komutu veriyoruz;

# openssl pkcs12 -in dosya.pfx -out fullchain_and_key.pem -nodes

Bu komut pfx dosyamız şifrelenmiş ise bizden şifre isteyecektir. Şifreyi girip işlemi tamamladığımızda çalışma dizininde fullchain ve privkey bilgilerini taşıyan fullchain_and_key.pem isimli bir dosya oluşacaktır. Bu dosyadan ihtiyacımız olan iki pem dosyasını elde edelim şimdi de;

# openssl pkey -in fullchain_and_key.pem -out privkey.pem

# openssl crl2pkcs7 -nocrl -certfile fullchain_and_key.pem | openssl pkcs7 -print_certs -out fullchain.pem

Evet pfx dosyamızdan fullchain.pem ve privkey.pem ayrılmış oldu. Bu iki dosya ile Apache ve Nginx üzerinde SSL ayarlamalarınızı yapabilirsiniz.

Saygılarımla..

 

Bu dökümanda; Let's Encrypt Wilcard Sertifikasını, DNS sunucu olarak tinydns kullanan bir sistem yöneticisinin nasıl kolayca alabileceğini ve akabinde sertifika yenilenmesinde işi nasıl otomatize edebileceğini kendi deneyimim üzerinden aktarmaya çalışacağım.

 Let's Encrypt nedir, ne işe yarar, nasıl çalışır sorularına bu yazıda çok yer vermek istemiyorum. Eğer Let's Encrypt nedir, nasıl çalışır konusunda bilginiz yetersiz ise öncelikle o konuda biraz araştırma yapmanız yerinde olacaktır.

Let's Encrypt'in sertifika oluşturma için bir kaç "challange" metodu olduğunu biliyorsunuz. Wildcard tipi sertifikalar için bir kısıtlama söz konusu ve bu tip sertifika alabilmek için DNS challange metodunu kullanmak mecburi. Bu metod çok kabaca şöyle çalışıyor; Let's Encrypt sertifika oluşturma aşamasında, txt tipinde bir kayıt üretip bunu DNS sunucunuzdan yayınlamanızı istiyor. Siz DNS kaydını girip sertifika işlemine devam ettiğinizde Let's Encrypt size ilettiği txt kaydının alan adınızda olup olmadığını sorguluyor ve sorgu başarılı olur ise wildcard sertifika üretme işlemi başarı ile gerçekleşiyor. Buradaki amaç elbette sertifika üretmek istediğiniz alan adının gerçekten sahibi olup olmadığını otomatik bir şekilde kontrol etmek. Let's Encrypt süreçleri otomatik hale getirip insan müdahalesini gerektirmeyen bir yapıya sahip.

Bu işlem için Let's Encrypt üzerinde kullanılabilecek plug-in yazılımları mevcut. DNS hizmetini aldığınız büyük oyuncular için işlemi otomatikleştiren plug-in bulabiliyorsunuz. Ancak kendi tinydns sunucusunu çalıştıran benim gibiler için bir kaç düzenleme yapmak gerekiyor. Neyse ki tinydns'in esnek ve basit yapısı nedeni ile bu gayet kolay. Nasıl yapıldığına geçelim.

Öncelikle yaptığım her şey FreeBSD üzerinde bunu belirteyim. GNU/Linux üzerinde de çok yakın bir şekilde aynı yolu izleyebilirsiniz. Bazı dosya yolları ve komutlar farklılık gösterebilir.

Let's Encrypt dosyalarının bulunduğu /usr/local/etc/letsencrypt dizinine girelim. Bu dizinde cli.ini adında bir ayar dosyası göreceksiniz. Bu dosyayı aşağıdaki gibi düzenleyelim;

manual
server = https://acme-v02.api.letsencrypt.org/directory
rsa-key-size = 4096
preferred-challenges = dns
manual-auth-hook = /usr/local/etc/letsencrypt/certbot-dns-auth.sh
manual-cleanup-hook = /usr/local/etc/letsencrypt/certbot-dns-auth-cleanup.sh

certbot yazılımı bize çok rahatlık sağlayan manual-auth-hook ve manual-cleanup-hook tanımlama şansını veriyor. Bu tanımlamalar sayesinde certbot içerisinden shell scriptlerimizi çağırarak sertifika üretimi öncesinde ve sonrasında bazı işlemler yapabiliyoruz. Sertifika oluşturma öncesinde tinydns sunucumuza Let's Encrypt tarafından gönderilen txt kaydını giren shell scripti görelim,

# cat /usr/local/etc/letsencrypt/certbot-dns-auth.sh
#!/usr/local/bin/bash

cd /etc/tinydns/root
cp -f data data-good
echo "'_acme-challenge.$CERTBOT_DOMAIN:$CERTBOT_VALIDATION:120" >> data
/usr/local/etc/letsencrypt/update.sh
# Sleep to make sure the change has time to propagate over to DNS
sleep 25

tinydns için benim kurulumunda root dizini /etc/tinydns/root yolunda, bu dizine geçip data dosyasını data-good olarak her ihtimale karşı yedekliyoruz. certbot tarafından gönderilen txt tipi kaydı data dosyasına yazıyoruz ve update.sh shell scriptini çağırıyoruz. Bu scripti de inceleyelim;

# cat /usr/local/etc/letsencrypt/update.sh
#!/bin/sh

make
rsync -avz -e ssh /etc/tinydns/root/data.cdb root@ikinci_tinydns_ip:/etc/tinydns/root
rsync -avz -e ssh /etc/tinydns/root/data root@ikinci_tinydns_ip:/etc/tinydns/root

make komutu ile data dosyasını cdb formatına çevirip rsync satırları ile varsa ikinci, üçüncü, vb. diğer sunucularımıza da bu değişimi gönderiyoruz. Böylece tüm tinydns sunucularımızda gerekli txt kaydı kaydedilmiş oldu. Bir sonraki aşamada /usr/local/etc/letsencrypt/certbot-dns-auth-cleanup.sh scripti çalışacak ve girilen txt kaydını DNS sunucumuzdan temizleyecek. Bu scriptin içeriği de:

# cat /usr/local/etc/letsencrypt/certbot-dns-auth-cleanup.sh
#!/usr/local/bin/bash

cd /etc/tinydns/root
sed '$d' data > tmp; mv -f tmp data
/usr/local/etc/letsencrypt/update.sh

Yine tinydns root dizinine geçip biraz önce eklediğimiz kaydı silerek update.sh scriptini yeniden çağırıyoruz.

Bu scriptlerin tümü başarı ile çalıştığında cerbot şu şekilde çıktı üretecek;

# certbot certonly -d '*.domain.com'
Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for *.domain.com
Hook '--manual-auth-hook' for domain.com ran with output:
 /usr/local/bin/tinydns-data
 sending incremental file list
 data.cdb

 sent 1,970 bytes  received 137 bytes  1,404.67 bytes/sec
 total size is 11,671  speedup is 5.54
 sending incremental file list
 data

 sent 281 bytes  received 101 bytes  764.00 bytes/sec
 total size is 7,206  speedup is 18.86
Hook '--manual-cleanup-hook' for domain.com ran with output:
 /usr/local/bin/tinydns-data
 sending incremental file list
 data.cdb

 sent 1,848 bytes  received 137 bytes  3,970.00 bytes/sec
 total size is 11,551  speedup is 5.82
 sending incremental file list
 data

 sent 218 bytes  received 101 bytes  638.00 bytes/sec
 total size is 7,121  speedup is 22.32

Successfully received certificate.
Certificate is saved at: /usr/local/etc/letsencrypt/live/domain.com/fullchain.pem
Key is saved at:         /usr/local/etc/letsencrypt/live/domain.com/privkey.pem
This certificate expires on 2022-04-01.
These files will be updated when the certificate renews.

NEXT STEPS:
- The certificate will need to be renewed before it expires. Certbot can automatically renew the certificate in the background, but you may need to take steps to enable that functionality. See https://certbot.org/renewal-setup for instructions.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Sertifikamız başarı ile oluştu. Yapılması gereken tek bir şey var. Let's Encrypt wildcard sertifikaları 3 ay geçerli oalcak şekilde veriyor. Bu nedenle 3 ayda bir bu sertifikaların yenilenmesi lazım. cli.ini dosyasında yaptığımız düzenleme sertifika yenilenirken de işe yarıyor. Tek yapmamız gereken bir cron işi oluşturmak ve her gün düzenli şekilde certbot'a sistemimizde yüklü sertifikaları denetletmek. Örnek bir cron işi;

0 1 * * * root /usr/local/bin/certbot renew >> /var/log/letsencrypt/renew.log --post-hook "service nginx restart"

Tebrikler, Let's Encrypt wildcard sertifikanızı güle güle kullanın..

Author Name

İletişim Formu

Ad

E-posta *

Mesaj *

Blogger tarafından desteklenmektedir.