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.
