Webhook Güvenlik Duvarı: IP Beyaz Liste Yönetimi

Üretim ortamında bir webhook endpoint’i açtığınızda, aslında internete doğrudan bir kapı aralıyorsunuz. Bu kapıdan sadece beklediğiniz misafirler girsin diye IP beyaz listesi yönetimi tam bu noktada devreye giriyor. GitHub, Stripe, Slack veya herhangi bir üçüncü taraf servis ile entegrasyon kurarken webhook güvenliğini doğru yapılandırmak, sistemin bütünlüğü açısından kritik önem taşıyor.

Bu yazıda gerçek dünya senaryoları üzerinden, webhook trafiğini nasıl güvenli hale getireceğinizi, IP beyaz listesi yönetimini hem uygulama katmanında hem de sistem katmanında nasıl uygulayacağınızı ele alacağız.

Neden IP Beyaz Listesi Yetmez, Ama Yine de Şart?

Önce bir gerçeği kabul edelim: IP beyaz listesi tek başına yeterli bir güvenlik önlemi değil. Saldırganlar IP sahteciliği yapabilir, CDN arkasındaki servisler IP değiştirebilir, NAT’lı ortamlarda beklenmedik IP’ler görünebilir. Ama buna rağmen IP beyaz listesi, derinlemesine savunma stratejisinin vazgeçilmez bir katmanı.

Bunu şöyle düşünebilirsiniz: HMAC imza doğrulaması bir kilit gibidir, IP beyaz listesi ise kapıya bakan güvenlik görevlisi gibi. İmzayı bile bilse, tanımadığın biri kapıdan giremez.

Gerçek dünyada karşılaştığım tipik bir senaryo: Bir e-ticaret şirketinin Stripe webhook’u, rakip şirketin geliştirici ortamından gelen test istekleriyle dolup taşmıştı. Endpoint tahmin edilebilir bir URL’deydi ve rate limiting yoktu. Güvenlik duvarı kuralları eklenene kadar sunucu gereksiz yere işlem yapıyordu.

Webhook Sağlayıcılarının IP Aralıklarını Bulmak

Her büyük webhook sağlayıcısı, IP aralıklarını yayınlar. Bu aralıklar değişebileceğinden dinamik bir yönetim sistemi kurmak gerekir.

GitHub’ın IP aralıklarını çekmek:

curl -s https://api.github.com/meta | jq -r '.hooks[]'

Stripe’ın IP listesini çekmek:

curl -s https://stripe.com/files/ips/ips_webhooks.txt

Slack webhook IP’lerini almak:

curl -s https://slack.com/api/auth.test | head -5
# Slack genellikle AS36459 (Slack Technologies) AS numarasını kullanır
# whois ile AS bloklarını sorgulayabilirsiniz:
whois -h whois.radb.net -- '-i origin AS36459' | grep route

Bu listeleri bir script ile düzenli olarak çekip güncellemelisiniz. Sağlayıcılar yeni veri merkezleri açtıklarında IP aralıkları değişiyor ve bunu takip etmemek ciddi kesintilere yol açabiliyor.

iptables ile Temel IP Beyaz Listesi

En klasik ve kontrol edilebilir yöntem, doğrudan iptables kuralları yazmak. Önce yapıyı anlayalım, sonra gerçekçi bir konfigürasyon oluşturalım.

#!/bin/bash
# webhook-whitelist.sh
# Webhook endpoint'i için IP beyaz listesi yönetimi

WEBHOOK_PORT=8443
CHAIN_NAME="WEBHOOK_ALLOW"

# Özel chain oluştur (yoksa)
iptables -N $CHAIN_NAME 2>/dev/null || iptables -F $CHAIN_NAME

# GitHub webhook IP'leri (örnek - güncel listeyi API'dan çekin)
GITHUB_IPS=(
    "192.30.252.0/22"
    "185.199.108.0/22"
    "140.82.112.0/20"
    "143.55.64.0/20"
)

# Stripe webhook IP'leri
STRIPE_IPS=(
    "3.18.12.63/32"
    "3.130.192.231/32"
    "13.235.14.237/32"
    "13.235.122.149/32"
)

# GitHub IP'lerini ekle
for ip in "${GITHUB_IPS[@]}"; do
    iptables -A $CHAIN_NAME -s $ip -p tcp --dport $WEBHOOK_PORT -j ACCEPT
    echo "GitHub IP eklendi: $ip"
done

# Stripe IP'lerini ekle
for ip in "${STRIPE_IPS[@]}"; do
    iptables -A $CHAIN_NAME -s $ip -p tcp --dport $WEBHOOK_PORT -j ACCEPT
    echo "Stripe IP eklendi: $ip"
done

# Beyaz listede olmayan tüm bağlantıları logla ve reddet
iptables -A $CHAIN_NAME -p tcp --dport $WEBHOOK_PORT -j LOG 
    --log-prefix "WEBHOOK_BLOCKED: " --log-level 4
iptables -A $CHAIN_NAME -p tcp --dport $WEBHOOK_PORT -j DROP

# Ana INPUT chain'e bağla
iptables -I INPUT -p tcp --dport $WEBHOOK_PORT -j $CHAIN_NAME

echo "Webhook güvenlik duvarı kuralları uygulandı."

Bu script’i cron ile düzenli çalıştırabilirsiniz, ancak dikkatli olun: her çalıştırmada chain’i temizliyorsunuz ve kısa süreliğine kural boşluğu oluşabiliyor. Production ortamı için daha atomik bir yaklaşım gerekiyor.

ipset ile Dinamik ve Performanslı Yönetim

Yüzlerce veya binlerce IP ile uğraşıyorsanız, iptables’ın her kuralı sırayla kontrol etmesi performans sorunu yaratır. ipset bu problemi hash tablosu kullanarak çözer.

#!/bin/bash
# webhook-ipset-manager.sh
# ipset ile yüksek performanslı webhook IP yönetimi

SETNAME_V4="webhook-allowed-v4"
SETNAME_V6="webhook-allowed-v6"
WEBHOOK_PORT=8443
IP_LIST_FILE="/etc/webhook/allowed-ips.txt"
TEMP_SETNAME="${SETNAME_V4}-tmp"

# ipset kurulu mu kontrol et
if ! command -v ipset &> /dev/null; then
    echo "ipset kurulu değil. Kuruluyor..."
    apt-get install -y ipset || yum install -y ipset
fi

# Geçici set oluştur (atomik swap için)
ipset create $TEMP_SETNAME hash:net family inet hashsize 1024 maxelem 65536 2>/dev/null

# IP listesini oku ve geçici sete ekle
while IFS= read -r ip; do
    # Yorum satırlarını ve boş satırları atla
    [[ "$ip" =~ ^#.*$ ]] && continue
    [[ -z "$ip" ]] && continue
    
    # IP'yi geçici sete ekle
    ipset add $TEMP_SETNAME "$ip" 2>/dev/null || 
        echo "Uyarı: $ip sete eklenemedi"
done < "$IP_LIST_FILE"

# Ana set yoksa oluştur, varsa swap et
if ! ipset list $SETNAME_V4 &>/dev/null; then
    ipset rename $TEMP_SETNAME $SETNAME_V4
else
    # Atomik swap - kural boşluğu yok
    ipset swap $TEMP_SETNAME $SETNAME_V4
    ipset destroy $TEMP_SETNAME
fi

# iptables kuralını ekle (yoksa)
if ! iptables -C INPUT -m set --match-set $SETNAME_V4 src 
    -p tcp --dport $WEBHOOK_PORT -j ACCEPT 2>/dev/null; then
    iptables -I INPUT -m set --match-set $SETNAME_V4 src 
        -p tcp --dport $WEBHOOK_PORT -j ACCEPT
    iptables -A INPUT -p tcp --dport $WEBHOOK_PORT -j DROP
fi

echo "$(date): ipset güncellendi. $(ipset list $SETNAME_V4 | grep -c '/|[0-9]+.[0-9]') IP aktif."

Bunu cron ile her saat başı çalıştırabilirsiniz:

# crontab -e
0 * * * * /usr/local/bin/webhook-ipset-manager.sh >> /var/log/webhook-firewall.log 2>&1

Nginx Üzerinde Uygulama Katmanı Koruması

Sistem katmanı korumasının yanında uygulama katmanında da filtreleme yapmak iyi bir pratik. Nginx burada hem reverse proxy hem de güvenlik duvarı görevi görebilir.

# /etc/nginx/conf.d/webhook.conf

# IP beyaz listesi geo modülü ile
geo $webhook_allowed {
    default         0;
    
    # GitHub
    192.30.252.0/22     1;
    185.199.108.0/22    1;
    140.82.112.0/20     1;
    143.55.64.0/20      1;
    
    # Stripe
    3.18.12.63/32       1;
    3.130.192.231/32    1;
    
    # Slack
    54.209.236.152/32   1;
    54.158.246.245/32   1;
    
    # Kendi ofis IP'niz (test için)
    203.0.113.10/32     1;
}

# Rate limiting zone
limit_req_zone $binary_remote_addr zone=webhook_ratelimit:10m rate=30r/m;

server {
    listen 443 ssl http2;
    server_name webhooks.sirketiniz.com;
    
    ssl_certificate /etc/letsencrypt/live/webhooks.sirketiniz.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/webhooks.sirketiniz.com/privkey.pem;
    
    # Webhook endpoint
    location /webhooks/ {
        # IP kontrolü
        if ($webhook_allowed = 0) {
            access_log /var/log/nginx/webhook-blocked.log;
            return 403 '{"error":"IP not allowed","ip":"$remote_addr"}';
        }
        
        # Rate limiting
        limit_req zone=webhook_ratelimit burst=10 nodelay;
        
        # Sadece POST kabul et
        limit_except POST {
            deny all;
        }
        
        # Proxy ayarları
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Original-URI $request_uri;
        
        # Timeout ayarları
        proxy_connect_timeout 10s;
        proxy_read_timeout 30s;
        proxy_send_timeout 30s;
        
        # Request boyutu limiti
        client_max_body_size 5M;
    }
    
    # Diğer tüm istekleri reddet
    location / {
        return 404;
    }
}

Dikkat etmeniz gereken nokta: Nginx’in geo modülü, IP listesi büyüdükçe yapılandırma dosyasının da büyümesine yol açar. Dinamik güncelleme için lua-nginx modülü veya ayrı bir servis düşünebilirsiniz.

Fail2ban ile Otomatik Engelleme

Beyaz listede olmayan IP’lerden tekrarlayan istekler geldiğinde bunları otomatik engellemek için fail2ban kullanabilirsiniz.

# /etc/fail2ban/filter.d/webhook-abuse.conf

[Definition]
# Nginx blocked log'undan pattern eşleştir
failregex = ^<HOST> .* "POST /webhooks/.*" 403
            ^<HOST> .* "GET /webhooks/.*" 405
            WEBHOOK_BLOCKED.*SRC=<HOST>

ignoreregex =

# Webhook'tan beklenmeyen methodlar da şüpheli
datepattern = %%d/%%b/%%Y:%%H:%%M:%%S %%z
# /etc/fail2ban/jail.d/webhook.conf

[webhook-abuse]
enabled     = true
port        = http,https,8443
filter      = webhook-abuse
logpath     = /var/log/nginx/webhook-blocked.log
             /var/log/kern.log
maxretry    = 5
findtime    = 300
bantime     = 3600
action      = iptables-multiport[name=webhook, port="80,443,8443"]
              sendmail-whois[name=webhook, [email protected]]
# fail2ban'ı yeniden başlat ve durumu kontrol et
systemctl restart fail2ban
fail2ban-client status webhook-abuse

Otomatik IP Listesi Güncelleme Sistemi

Gerçek dünyada en büyük operasyonel sorun şu: sağlayıcılar IP değiştiriyor ve siz fark etmiyorsunuz, servis kesilıyor. Bu senaryoyu otomatize eden bir sistem kuralım.

#!/bin/bash
# /usr/local/bin/update-webhook-ips.sh
# Sağlayıcı IP listelerini çekip günceller

LOG_FILE="/var/log/webhook-ip-update.log"
IP_FILE="/etc/webhook/allowed-ips.txt"
TEMP_FILE="/tmp/webhook-ips-new.txt"
NOTIFY_EMAIL="[email protected]"

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a $LOG_FILE
}

# Geçici dosyayı temizle
> $TEMP_FILE

# GitHub IP'lerini çek
log "GitHub IP'leri çekiliyor..."
GITHUB_HOOKS=$(curl -sf --max-time 30 https://api.github.com/meta | 
    jq -r '.hooks[]' 2>/dev/null)

if [ $? -eq 0 ] && [ -n "$GITHUB_HOOKS" ]; then
    echo "# GitHub - $(date '+%Y-%m-%d')" >> $TEMP_FILE
    echo "$GITHUB_HOOKS" >> $TEMP_FILE
    log "GitHub: $(echo "$GITHUB_HOOKS" | wc -l) IP/CIDR eklendi"
else
    log "HATA: GitHub IP listesi çekilemedi! Mevcut liste korunuyor."
    # Kritik hata - eskiyi kullanmaya devam et
    GITHUB_FAILED=1
fi

# Stripe IP'lerini çek
log "Stripe IP'leri çekiliyor..."
STRIPE_IPS=$(curl -sf --max-time 30 
    https://stripe.com/files/ips/ips_webhooks.txt 2>/dev/null)

if [ $? -eq 0 ] && [ -n "$STRIPE_IPS" ]; then
    echo "# Stripe - $(date '+%Y-%m-%d')" >> $TEMP_FILE
    echo "$STRIPE_IPS" >> $TEMP_FILE
    log "Stripe: $(echo "$STRIPE_IPS" | wc -l) IP eklendi"
else
    log "HATA: Stripe IP listesi çekilemedi!"
    STRIPE_FAILED=1
fi

# Herhangi bir kaynak başarısız olduysa güncelleme yapma
if [ -n "$GITHUB_FAILED" ] || [ -n "$STRIPE_FAILED" ]; then
    log "Bir veya daha fazla kaynak başarısız. Güncelleme iptal edildi."
    echo "Webhook IP güncelleme başarısız - manuel kontrol gerekiyor" | 
        mail -s "UYARI: Webhook IP Güncelleme Hatası" $NOTIFY_EMAIL
    exit 1
fi

# Yeni ve eski listeyi karşılaştır
OLD_COUNT=$(grep -v '^#' $IP_FILE | grep -v '^$' | wc -l)
NEW_COUNT=$(grep -v '^#' $TEMP_FILE | grep -v '^$' | wc -l)

log "Eski IP sayısı: $OLD_COUNT, Yeni IP sayısı: $NEW_COUNT"

# Sayı dramatik düştüyse (>%30) alarm ver ve güncelleme
if [ $NEW_COUNT -lt $((OLD_COUNT * 70 / 100)) ]; then
    log "UYARI: IP sayısı %30'dan fazla düştü! Manuel onay gerekiyor."
    echo "IP sayısı beklenmedik şekilde düştü: $OLD_COUNT -> $NEW_COUNT" | 
        mail -s "KRİTİK: Webhook IP Listesi Anomalisi" $NOTIFY_EMAIL
    exit 1
fi

# Dosyayı güncelle ve ipset'i yenile
cp $TEMP_FILE $IP_FILE
/usr/local/bin/webhook-ipset-manager.sh

log "Güncelleme başarıyla tamamlandı. Aktif IP: $NEW_COUNT"

CloudFront veya CDN Arkasındaki Webhook’lar İçin Gerçek IP Yönetimi

CDN kullanıyorsanız, gerçek kaynak IP’yi X-Forwarded-For başlığından almanız gerekiyor. Ama dikkat: bu başlık sahte olabilir. Sadece güvendiğiniz CDN IP’lerinden gelen taleplerdeki bu başlığa inanmalısınız.

# /etc/nginx/conf.d/realip-webhook.conf

# CloudFront IP'leri (güncel listeyi AWS'den alın)
set_real_ip_from 120.52.22.96/27;
set_real_ip_from 205.251.249.0/24;
set_real_ip_from 180.163.57.128/26;
set_real_ip_from 204.246.168.0/22;

# Cloudflare IP'leri
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;

real_ip_header X-Forwarded-For;
real_ip_recursive on;

# Gerçek IP alındıktan sonra geo modülü doğru çalışır

Bu konfigürasyonla Nginx önce bağlanan IP’nin güvenilen CDN IP’si olup olmadığını kontrol eder, güveniliyorsa X-Forwarded-For başlığındaki gerçek kaynak IP’yi kullanır.

Webhook IP Değişikliklerini İzleme ve Alarm

Sağlayıcıların IP listelerindeki değişiklikleri takip etmek için basit ama etkili bir monitoring kurabilirsiniz.

#!/bin/bash
# /usr/local/bin/monitor-webhook-ips.sh
# IP listesi değişikliklerini izler ve raporlar

PREVIOUS_HASH_FILE="/var/lib/webhook/ip-list-hashes"
NOTIFY_EMAIL="[email protected]"

declare -A SOURCES
SOURCES["github"]="https://api.github.com/meta"
SOURCES["stripe"]="https://stripe.com/files/ips/ips_webhooks.txt"

mkdir -p /var/lib/webhook

for source in "${!SOURCES[@]}"; do
    url="${SOURCES[$source]}"
    
    # Mevcut içeriği çek ve hash'le
    if [ "$source" = "github" ]; then
        current=$(curl -sf "$url" | jq -r '.hooks[]' | sort)
    else
        current=$(curl -sf "$url" | sort)
    fi
    
    current_hash=$(echo "$current" | md5sum | cut -d' ' -f1)
    previous_hash=$(grep "^$source:" $PREVIOUS_HASH_FILE 2>/dev/null | cut -d: -f2)
    
    if [ "$current_hash" != "$previous_hash" ]; then
        echo "DEĞİŞİKLİK TESPİT EDİLDİ: $source webhook IP listesi güncellendi!" | 
            mail -s "Webhook IP Değişikliği: $source" $NOTIFY_EMAIL
        
        # Hash'i güncelle
        sed -i "/^$source:/d" $PREVIOUS_HASH_FILE 2>/dev/null
        echo "$source:$current_hash" >> $PREVIOUS_HASH_FILE
        
        echo "$(date): $source IP listesi değişti, güncelleme tetikleniyor..."
        /usr/local/bin/update-webhook-ips.sh
    fi
done
# Her 4 saatte bir kontrol et
0 */4 * * * /usr/local/bin/monitor-webhook-ips.sh >> /var/log/webhook-monitor.log 2>&1

Gözden Kaçılan Detaylar ve Pratik Öneriler

Yıllar içinde fark ettiğim ve özellikle vurgulamak istediğim konular şunlar:

IPv6 desteğini unutmayın. Bazı webhook sağlayıcıları IPv6 üzerinden de gönderim yapabiliyor. ip6tables ve ipset family inet6 ile ayrı bir konfigürasyon gerekiyor.

Egress filtrelemeyi ihmal etmeyin. Gelen istekleri kontrol ettiğiniz kadar giden istekleri de kontrol edin. Webhook handler’ınız bir callback URL’e istek atacaksa, bu IP’lerin de kontrol altında olması gerekiyor.

Test ortamı için ayrı kurallar. Production beyaz listesini staging ortamına taşımayın. Staging’de daha geniş IP aralıkları kullanmak mantıklı olabilir ama bu gevşekliğin production’a sızmaması gerekiyor.

Log rotasyonu. Webhook güvenlik duvarı logları hızla büyür. /etc/logrotate.d/webhook dosyası oluşturarak günlük rotasyon yapın.

HMAC doğrulaması ile birleştirin. IP filtresi geçen her isteği HMAC imzasıyla da doğrulayın. Çift katman güvenlik şartsız.

Beyaz liste değişikliklerini versiyon kontrolüne alın. IP listesi dosyalarınızı Git’te tutun. Hangi IP’nin ne zaman eklendiğini ve neden eklendiğini commit message‘da belirtin.

Acil durum bypass mekanizması. Bir sağlayıcı IP değiştirdi ve webhook’larınız gelmiyor. Bu durumda ne yaparsınız? Acil erişim için bir prosedür belgelendirin. Örneğin, bastion host üzerinden geçici IP ekleme workflow’u.

Sonuç

Webhook güvenlik duvarı yönetimi, “bir kere yap ve unut” kategorisinde değil. IP listeleri değişir, sağlayıcılar yeni veri merkezleri açar, CDN migraasyonları olur. Bu dinamik ortamda manuel yönetim sürdürülemez.

İyi bir webhook IP yönetimi sistemi şu üç özelliğe sahip olmalı: otomatik güncelleme (cron tabanlı veya event tabanlı), anomali tespiti (IP sayısı dramatik düştüğünde alarm), ve katmanlı savunma (iptables + nginx + uygulama katmanı birlikte).

Anlattığım yöntemler ölçek olarak farklı: küçük bir uygulama için sadece Nginx geo modülü yeterliyken, yüksek trafikli bir sistemde ipset + fail2ban + otomatik güncelleme kombinasyonu gerekiyor. Kendi ortamınızın ihtiyaçlarına göre bu katmanları birleştirin.

Son olarak şunu hatırlatayım: Bu kontroller ne kadar sıkı olursa olsun, webhook endpoint’inizin URL’ini gereksiz yere yayınlamayın. Gizlilik kendi başına bir güvenlik katmanı değildir, ancak saldırı yüzeyini azaltır. Anlamlı bir path kullanın, tahmin edilemez bir token ekleyin, ve bu URL’i sadece ihtiyaç duyan ekiplerle paylaşın.

Bir yanıt yazın

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