TL;DR
.NET 10 servisini Kubernetes'te çalıştırıyorsan saldırıların büyük çoğunluğu origin'e gelmeden Cloudflare edge'de durdurulabilir. WAF API entegrasyonu, IP blocklist push, ASN bazlı bot tespiti ve UFW ikinci hatla birlikte 157 saldırı probe'unun tamamını 24 saat içinde edge'de yakaladık. Bu yazıda mimari, kod ve canlı prod metrikleri var.
Yazar notu: Bu yazıdaki kod ve metriklerin tamamı bilalkose.com.tr canlı sistemden alındı. %33 gerçek ziyaretçi vs %67 bot/VPN/datacenter oranı, 157 attack probe, 15 IP otomatik block'u 2026-05-10 / 2026-05-11 24 saat penceresinden. WAF rule seed scripti, ASN registry ve auto-block döngüsü açık kaynak referans olarak gist'lerde paylaşıldı.
İçindekiler
- Edge Security Nedir, .NET 10'da Neden Önemli?
- Mimari Genel Bakış: Cloudflare + .NET + K8s Üçgeni
- WAF API'sini .NET'ten Kullanmak
- IP Blocklist Push: 15 IP'yi Cloudflare List'e Senkronlamak
- ASN Bazlı Bot Tespiti: 40+ Datacenter ASN Registry
- Edge mi, Origin Classifier mı? Karar Matrisi
- WAF UA Matcher Tuzakları
- Origin Firewall: UFW + CF CIDR Allowlist
- WAF Event Collector → SQL Server ETL
- Üretim Metrikleri: 24 Saatlik Gerçek Veriler
- Sıkça Sorulan Sorular
Edge Security Nedir, .NET 10'da Neden Önemli?
Edge security, isteklerin uygulamanıza ulaşmadan önce CDN katmanında filtrelenmesi demek. .NET 10 uygulamanı Kubernetes'te yayınladığında, trafiğin önemli bir kısmı insandan değil: otomatik botlar, açık port tarayıcıları, .env / .git / wp-admin sızdırma denemeleri, scraping framework'leri ve VPN ağlarından gelen istekler. Eğer bu isteklerin tümü origin'e geliyorsa CPU, memory ve özellikle veritabanı connection pool'unu boşuna yoruyorsun.
Origin'e gelmeden engelle: edge avantajı
Tek bir HTTP request'in .NET 10 + EF Core + SQL Server stack'inde origin maliyeti ~10-15ms CPU ve bir connection pool slot. Eğer günde 200 attack probe origin'e ulaşıyorsa bu ~2-3 saniyelik kayıp + log gürültüsü demek. Daha kötüsü: bu request'ler Logs.Error tablosunu doldurarak gerçek hataları görmeni zorlaştırıyor.
Cloudflare edge bu trafiği daha sen pod'una bakmadan filtreliyor. WAF custom rules, IP blocklist, ASN-based block ve UA pattern matching dört temel filtre.
2026 manzarası: AI scraper + VPN patlaması
2026 itibariyle internet trafiğinin yaklaşık üçte ikisi non-human kaynaklı. Bunun bir kısmı meşru: Googlebot, Bingbot, IndexNowBot, OAI-SearchBot, ClaudeBot, GPTBot. Bunlar SEO için gerekli ve allowlist'te tutmalısın. Ama diğer kısmı: Mullvad, NordVPN, ProtonVPN gibi VPN ASN'leri; AWS, GCP, Azure, DigitalOcean, Hetzner gibi datacenter ASN'leri; ve Shodan, Censys benzeri tarayıcıların sürekli probe'ları.
Bizim canlı prod sistemde son 24 saatlik telemetri %33 gerçek ziyaretçi vs %67 şüpheli (datacenter + VPN + null ASN) gösteriyor. Yani gerçek ziyaretçi sayını öğrenmek istiyorsan, edge'de filtreleme yapmadan analytics dashboard'una bakmak yanıltıcı.
KVKK ve GDPR uyumlu telemetri tasarlamak istiyorsan da gerçek-vs-şüpheli ayrımı kritik. Anonim/datacenter trafiği IsSuspectNetwork=1 flag'iyle ayırırsan kullanıcı sayıların gerçeği yansıtır.
📊 Canlı Prod Telemetri — 24 Saat Penceresi (2026-05-10 → 2026-05-11)
| Metrik | Değer |
|---|---|
| Attack probe sayısı | 157 |
| Unique IP (probe kaynağı) | 53 |
| Auto-block tetiklendi (ThreatScore ≥ 12) | 15 IP |
| Gerçek ziyaretçi (residential) | %33 |
| Şüpheli trafik (datacenter + VPN + null ASN) | %67 |
| Origin'e ulaşan attack probe | 0 (tamamı edge'de durdu) |
Kaynak: AttackProbes tablosu, üretim DB. Aynı 24 saatlik pencerede WAF 5-rule seti + IP blocklist push aktif.
Mimari Genel Bakış: Cloudflare + .NET + K8s Üçgeni
Sistemin üç katmanı var ve her birinin sorumluluğu farklı.
İnternet
↓
┌─────────────────────────────────────────────┐
│ Cloudflare Edge │
│ - WAF custom rules (5 rule/zone) │
│ - Bot Fight Mode │
│ - IP Blocklist (Cloudflare List, 10K IP) │
│ - ASN block (datacenter ASN registry) │
└─────────────────────────────────────────────┘
↓ [filtered ~95%]
┌─────────────────────────────────────────────┐
│ Kubernetes Ingress │
│ - UFW + Cloudflare CIDR allowlist │
│ - SSH + LAN allow (ayrı) │
│ - Default deny │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ .NET 10 App │
│ - TrafficClassifierMiddleware │
│ - AttackProbes + ThreatScore │
│ - IsSuspectNetwork enrich │
│ - AttackBurstCounter (15dk/3 sliding) │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ SQL Server │
│ - PageHits │
│ - AttackProbes │
│ - AttackBurstCounter │
│ - IpBlocklist (24h TTL) │
└─────────────────────────────────────────────┘
3 katman: edge, origin classifier, app middleware
Edge katmanı stateless ve fast: pattern matching, IP/ASN lookup, UA blacklist. Saldırıların %95'i bu hatta düşer çünkü bunlar dummy probe'lar — herhangi bir cookie, session veya geçmiş davranış olmadan gönderiliyor.
Kalan %5 daha akıllı: low-and-slow brute force, distributed credential stuffing, session-bound saldırılar. Bu trafiği yakalamak için origin tarafında stateful bir classifier gerekir. TrafficClassifierMiddleware her request'i 8.5 adımda değerlendiriyor: IP, ASN, UA, path, ThreatScore, sliding-window burst counter, ve son olarak IsSuspectNetwork etiketi.
Üçüncü katman SQL Server'daki telemetri ve auto-block döngüsü. ThreatScore ≥ 12 ve 15 dakikalık pencerede ≥ 3 probe görüldüğünde, IP otomatik olarak IpBlocklist tablosuna yazılıyor ve oradan Cloudflare List API'sine push ediliyor. Yani sistem kendi kendini eğitiyor.
Tek yönlü senkron: app → edge push
Mimaride önemli bir karar: bilgi akışı tek yönlü. App, Cloudflare API'sine yazar; Cloudflare app'e WAF Event GraphQL ile veri sağlar ama o veri sadece dashboard için. Karar verme yetkisi app'te.
[Origin classifier] → push → [Cloudflare List]
↓
[GraphQL pull] ← read ← [Cloudflare WAF Events]
↓
[Dashboard / metrics]
Bu sayede bir tarafın bozulması diğerini etkilemiyor. CF Pro plan limiti bittiğinde app çalışmaya devam ediyor, sadece edge push retry kuyruğuna düşüyor.
Reload pattern: no-redeploy update
Cloudflare API token gibi credential'lar Vault'tan geliyor (secret/bilal-website → CloudflareApi__ApiToken). Token rotate edildiğinde:
- Vault'a yeni değer yazılır
kubectl rollout restart deployment/bilal-websiteile pod yeniden başlar- Vault init container yeni secret'ı çeker
IConfigurationreload,HttpClienttyped singleton yeni token ile çalışır
Bu pattern sayesinde kod deploy etmeden credential rotation yapılabiliyor. CI/CD pipeline'a dokunmuyor, sadece pod restart.
WAF API'sini .NET'ten Kullanmak
Cloudflare REST API'si ile WAF custom rule'ları, IP listeleri, ruleset entrypoint'leri ve zone keşfi tek bir HTTP client üzerinden yönetilebiliyor. Önemli olan üç şeyi doğru yapmak: tek bir typed HttpClient ile auth + retry + serialize'yi merkeze almak, idempotent seed mantığı kurmak ve API rate limit'i içinde kalmak.
Tek bir typed HttpClient: CloudflareApiClient
DI'ye AddHttpClient<CloudflareApiClient> ile typed client olarak kayıt. Bearer token Vault'tan geliyor (secret/bilal-website → CloudflareApi__ApiToken). Pod boot'ta options pattern üzerinden injection. Tüm Cloudflare servisleri (zone resolver, IP list, WAF rules seeder, WAF event collector) bu istemciyi kullanıyor — auth ve retry tek noktada.
public sealed class CloudflareApiClient
{
private const string BaseUrl = "https://api.cloudflare.com/client/v4/";
private const int MaxAttempts = 3;
public CloudflareApiClient(
HttpClient http,
IOptions<CloudflareApiOptions> options,
ILogger<CloudflareApiClient> logger)
{
_http = http;
_options = options.Value;
_http.BaseAddress = new Uri(BaseUrl);
_http.Timeout = TimeSpan.FromSeconds(30);
_http.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", _options.ApiToken);
_http.DefaultRequestHeaders.UserAgent.ParseAdd("bilalkose-cf-client/1.0");
}
public Task<CloudflareResponse<T>?> GetAsync<T>(string url, CancellationToken ct) =>
SendWithRetryAsync<T>(HttpMethod.Get, url, content: null, ct);
}
SendWithRetryAsync 429 (rate limit) ve 5xx için exponential backoff yapıyor. Cloudflare API account başına 1200 request/5dk limiti veriyor; pratikte WAF seed + IP list maintenance toplam 50-80 request/saat tutuyor, limiti zorlayan bir yük değil.
WafRulesSeeder: 4 rule + self-IP bypass'ımız
Cloudflare Free plan zone başına 5 custom rule veriyor. Yaklaşımımız: 1 manuel kural (Cloudflare Dashboard'da elle eklediğimiz Admin self-IP bypass), 4 kuralı app'ten kodla push ediyoruz. Toplam 5, limit dolu.
Kod tarafında push edilen 4 kural önem sırasıyla:
| # | Hedef | Action | Expression özeti |
|---|---|---|---|
| 1 | Auto-block IP list | block |
ip.src in $bilalkose_auto_block |
| 2 | SQLi / XSS / Log4Shell payload | managed_challenge |
union select, ' or 1=1, <script, ${jndi:, ../../ |
| 3 | Scanner UA + empty UA | block |
sqlmap, nuclei, nmap, masscan, nikto, dirbuster, gobuster, ffuf, wpscan |
| 4 | Sensitive path probes | block |
/.env, /.aws, /.ssh, /.git/, /wp-admin, /xmlrpc.php, /phpmyadmin, /dump.sql |
Önemli karar: SQLi/XSS pattern'ları için block yerine managed_challenge kullanıyoruz. Cloudflare CAPTCHA verir; gerçek kullanıcı yanlışlıkla yakalanırsa challenge'ı geçer, bot geçemez. False-positive risk'i bu şekilde absorbe ediyoruz.
// CloudflareWafRulesSeeder.BuildDesiredRules — özet
rules.Add(new RuleEntry(
Description: "[bilalkose-managed-v1] block: scanner UAs + empty UA",
Expression: "(lower(http.user_agent) contains \"sqlmap\" or " +
"lower(http.user_agent) contains \"nuclei\" or " +
"lower(http.user_agent) contains \"nmap\" or " +
"(http.user_agent eq \"\" and " +
" not http.request.uri.path eq \"/health/live\"))",
Action: "block",
Enabled: true));
Idempotency: aynı seed 100× çalışsa da safe
WafRulesSeeder IHostedService olarak çalışıyor. Pod restart oldukça StartAsync tetikleniyor — günde birkaç defa olabilir. Aynı kuralları sürekli ekleyemeyiz, Cloudflare 5-rule limit'ini patlatır.
Çözüm: her seed edilen kuralın description'una [bilalkose-managed-v1] etiketi koyuyoruz. Push öncesi mevcut ruleset'i çekiyor, etiketli kuralları siliyor, ardından desired'ı ekliyoruz. Elle eklediğimiz kuralları korumak için etiketsiz kurallara dokunmuyoruz:
// SeedZoneAsync — özet
var existing = await api.GetAsync<RulesetResult>(rulesetUrl, ct);
var preserved = existing.Result.Rules
.Where(r => !r.Description.Contains(ManagedTag, StringComparison.Ordinal))
.ToList();
var final = preserved.Concat(desired).Take(FreePlanMaxRulesPerZone).ToList();
await api.PutAsync<RulesetResult>(rulesetUrl, new { rules = final }, ct);
Bir noktada Dashboard'a girip elimizle kural eklersek, kod sıradaki seed'de o kuralı silmiyor. Tam tersi: kod kuralının limit'i taşırması durumunda kod kuralı kırpılıyor. Manuel > otomatik öncelik. Bu, "kod tarafından yönetilen + manuel acil müdahale mümkün" hibrit modeli.



IP Blocklist Push: 15 IP'yi Cloudflare List'e Senkronlamak
Auto-block sisteminin görünür ucu: kötü davranan IP'leri Cloudflare edge'ine kadar push etmek. WAF custom rule'larından farklı bir mekanizma kullanılıyor — IP listesi.
Niye Cloudflare List? IP rule limiti vs List limiti
Cloudflare custom rule içinde tek tek IP listelemek (ip.src eq "X.X.X.X" or ip.src eq "Y.Y.Y.Y" ...) iki nedenle kötü:
- Custom rule limiti: Free plan'da 5 rule var. Bir kuralı 100 IP'yle doldurmak diğer 4 rule slot'u harcatır
- Update maliyeti: Her IP eklendiğinde tüm ruleset PUT edilir, expression yeniden yazılır, validation gecikir
Cloudflare List bu problemi çözüyor:
| Özellik | IP rule içi liste | Cloudflare List |
|---|---|---|
| Kapasite | Rule expression limit (~kilobyte) | 10.000 IP / list |
| Account başına | — | 10 list (toplam 100K IP) |
| Update | Tüm rule PUT | Tek item POST/DELETE |
| Reference | Inline expression | ip.src in $list_name |
Tek bir liste oluşturuyor, WAF rule içinde ip.src in $bilalkose_auto_block ifadesiyle referans veriyoruz. List doldukça rule değişmiyor — sadece list item ekleniyor.
ICloudflareIpListService: idempotent init + bulk push
Servis arayüzü dört method etrafında dönüyor:
public interface ICloudflareIpListService
{
string? ListId { get; }
bool IsReady { get; }
Task InitializeAsync(CancellationToken ct);
Task<string?> AddBlockedIpAsync(string ip, string reason, CancellationToken ct);
Task<bool> RemoveBlockedIpAsync(string itemId, CancellationToken ct);
}
InitializeAsync pod boot'ta tek seferlik çağrılıyor. Account üzerindeki tüm listeleri GET ediyor, ad eşleşen var mı bakıyor; varsa ListId set ediyor, yoksa POST ile oluşturuyor. SemaphoreSlim(1,1) ile concurrent init'i engelliyor.
AddBlockedIpAsync çağrısı önemli bir detay döndürüyor: Cloudflare'in atadığı item ID. Bu ID'yi DB'de dbo.IpBlocklist.CloudflareListItemId kolonunda saklıyoruz. TTL dolduğunda silmek için bu ID gerekli (IP adresi ile silinmiyor, item ID ile siliniyor):
var resp = await _api.PostAsync<CfBulkOperation>(
$"accounts/{_api.Options.AccountId}/rules/lists/{ListId}/items",
new[] { new CfListItemRequest(ip, comment) }, ct);
if (resp?.Success == true)
{
string? itemId = resp.Result?.OperationId; // DB'ye kaydedilir
CloudflareMetrics.IpListItemsAddedTotal.WithLabels("ok").Inc();
return itemId;
}
Auto-block döngüsü: TrafficClassifier → IpListService
Origin-side classifier (TrafficClassifierMiddleware) her request'i sınıflandırıp AttackProbe tablosuna yazıyor. Burst counter sliding window'unda 15 dakikada ≥3 probe ve toplam ThreatScore ≥ 12 olduğu IP'yi IpBlocklist tablosuna ekliyor (24 saat TTL):
Request → TrafficClassifier → AttackProbe insert (ThreatScore hesap)
↓
AttackBurstCounter MERGE+OUTPUT (15 dk pencere)
↓
counter ≥ 3 ve score ≥ 12?
↓ evet
IpBlocklist insert (24h TTL) + fire-and-forget
↓
CloudflareIpListService.AddBlockedIpAsync()
↓
UPDATE IpBlocklist SET CloudflareListItemId = @id
Push fire-and-forget; başarısız olursa retry kuyruğuna düşmüyor — günde milyon request olsa Cloudflare API rate limit'ini patlatmamak için. Üst hat origin classifier zaten request'i bloklamış, edge push ikinci savunma.
Admin paneli: tek tık push-all
/admin/security/ip-blocklist Razor view dört aksiyon sağlıyor:
- Index: DB'deki tüm aktif block'lar listelenir; her satırda CloudflareListItemId boş mu dolu mu görünür
- PushCf (POST, tek satır): aktif olup CF'e push edilmemiş bir IP'yi tek tık ile gönder
- PushAll (POST, toplu): henüz item ID atanmamış tüm IP'leri tek seferde push et
- Remove (POST): IP'yi DB'den ve CF list'ten birlikte sil
Sistem ilk kurulduğunda mevcut 15 banlı IP DB'de vardı ama CF'de yoktu. PushAll tek seferde 15 IP'yi Cloudflare list'e taşıdı; sonraki saatlerde yeni 0 hata, 100% senkron.


ASN Bazlı Bot Tespiti: 42 Datacenter / Hosting ASN
Edge'de WAF rule + IP list yetmiyor — daha geniş ağ tabanlı (network-level) bir filtre lazım. IP adresleri sürekli değişiyor ama bir provider'ın Autonomous System Number (ASN)'ı sabit. AWS'in 16509 ASN'i 10 yıl önce de bu numaraydı, bugün de aynı. Bir provider'ı block etmek = o provider'ın bütün IP havuzunu tek slot'la kapsamak demek.
Niye ASN? IP rule scaling problemi
Bir datacenter sağlayıcısı milyonlarca IP'ye sahip olabiliyor. AWS'i tek tek IP olarak blacklist'lemek imkansız — her gün yeni instance'lar ayağa kalkıyor, IP'ler rotate ediyor. Ama AWS ASN'i (16509) bir tane.
MaxMind GeoIP database her IP için ASN bilgisi sağlıyor. .NET tarafında MaxMind.GeoIP2.DatabaseReader ile her request'in geldiği IP'nin ASN'i çözülüyor (singleton reader, thread-safe; ayrıntı cluster C5).
DatacenterAsnRegistry: 42 ASN, 7 kategori
Source-of-truth statik bir registry tutuyoruz. Aşağıda 7 kategorinin özeti:
| Kategori | Örnek ASN'ler | Sayı |
|---|---|---|
| Major public cloud | AWS (16509, 14618, 8987), DigitalOcean (14061), Linode (63949), Hetzner (24940), Vultr (20473), Azure (8075/8068), GCP (396982, 396983), Scaleway (12876) | 12 |
| Asia Pacific cloud | Alibaba (37963, 45102), Huawei (136907), Tencent (132203, 45090) | 5 |
| SEO/marketing crawler | SEMrush (209366), Ahrefs (53281) | 2 |
| VPN/proxy/hosting | M247 (9009), OVH (16276), iomart (60404), HostHero (205016), HostRoyale (203020), netcup (197540), Stark Industries (44477) ve diğerleri | 16 |
| Sub-Faz A++ keşifleri | FullSpace (201499), IPXO (834), NYBULA (401116), Cloudflare proxy (132892) | 4 |
| China bot networks | Chinanet (4134), China Unicom (4837) | 2 |
| Diğer | International Hosting Solutions (202425) | 1 |
| Toplam | 42 |

Hariç tutulanlar listesi en az ASN listesi kadar önemli, çünkü false-positive maliyetli:
- Google ASN 15169 hariç: Googlebot zaten
LegitBotUaPatterns'da, kalan = Google Cloud-hosted gerçek kullanıcı - ISP ASN'leri hariç: Turkcell 16135, TTNet 47331, Vodafone TR 15897 — gerçek son-kullanıcı
- Tier-1 transit hariç: NTT 2914, GTT 3257 — corporate VPN olabilir
public static class DatacenterAsnRegistry
{
private static readonly HashSet<int> DataCenterAsns = new()
{
16509, 14618, 8987, // AWS
14061, 63949, 24940, // DigitalOcean, Linode, Hetzner
8075, 8068, // Azure
396982, 396983, // GCP
// ... 32 more
};
public static bool IsDataCenter(int? asn) =>
asn.HasValue && DataCenterAsns.Contains(asn.Value);
public static bool IsAnonymousOrDataCenter(int? asn) =>
!asn.HasValue || DataCenterAsns.Contains(asn.Value);
}
IsAnonymousOrDataCenter: null ASN guard
Subtle ama önemli karar: ASN çözülemeyen IP'leri de "şüpheli" sayıyoruz. Modern internet'te MaxMind tüm meşru ISP'leri çözüyor. ASN null = nadiren allocation gap (yeni tahsis edilmiş range), çoğu zaman garbage scraper IP'si veya freshly-rotated proxy. Sub-Faz A++ telemetri analizi sırasında bu kararı verdik; PageHits sızıntısının ana kaynaklarından biri null ASN'liydi.
Linkable asset: ASN Registry CSV (açık kaynak)
42 ASN'lik liste'nin GitHub gist sürümünü açık kaynak yapıyoruz (MIT lisans). Kategori sütunlu CSV + JSON. Başka geliştiriciler kendi .NET / Go / Python servislerinde direkt referans olarak kullanabilir.
🔗 [LINKABLE ASSET #1: github.com/bilalkose/datacenter-asn-registry — yakında açacağımız MIT lisanslı açık kaynak repo]
Cloudflare Edge'de mi, Origin Classifier'da mı? Karar Matrisi
İlk haftalarda yapılan en yaygın hata: tüm güvenlik mantığını tek bir katmana yığmak. Ya hepsi edge'de (kontrolsüz, geri-besleme yok) ya hepsi origin'de (her saldırı CPU yiyor). Doğru cevap hibrit, ama hangi tehdit nereye? İşte 24 saatlik prod gözleminden çıkarılan karar matrisi.
Edge'in güçlü olduğu alanlar
Edge stateless, fast, scale: pattern match, IP/ASN lookup, UA string contains. Yeni bir teknik gerektirmiyor, sürekli pattern güncellemesi yeterli.
- DDoS volumetric saldırılar — origin'e kadar gelmesin
- Bilinen scanner UA'ları (sqlmap, nuclei) — UA stringi yeter
- Path probe'lar (.env, /admin) — URL match yeter
- Datacenter ASN blocking — IP list yeter
- Geographic rate limit — request rate yeter
Origin classifier'ın güçlü olduğu alanlar
Origin stateful, context-aware, DB-backed: session geçmişi, kullanıcı davranışı, ThreatScore birikimi.
- Burst behavior (15dk içinde 3 farklı path tarama)
- Brute force (aynı endpoint'e 10 farklı user/pass)
- Low-and-slow probes (saatte 1 request, ama 20 farklı sensitive path)
- KVKK/GDPR uyumlu PII filtering (IP'ye göre değil, request body'ye göre)
- Authenticated user behavior anomaly
Karar matrisi
| Tehdit tipi | Edge | Origin | Neden bu seçim |
|---|---|---|---|
| Volumetric DDoS | ✅ | ❌ | Edge bandwidth, origin CPU yiyor |
| Scanner UA (sqlmap, nikto) | ✅ | ❌ | Pattern stateless, hızlı |
| Path probe (.env, /admin) | ✅ | ⚠️ | Edge önce, origin sliding-window'da geri-besleme |
| Datacenter ASN block | ✅ | ❌ | IP list 10K kapasite, efficient |
| Geographic challenge | ✅ | ❌ | Edge native country detection |
| Behavioral burst (15dk/3) | ❌ | ✅ | Session-aware, DB-backed |
| Auth brute force | ⚠️ | ✅ | Edge rate limit, origin lockout |
| Privacy/KVKK filter | ❌ | ✅ | PII detection origin'de |
| Authenticated anomaly | ❌ | ✅ | Origin kullanıcı context'i |
Hibrit yaklaşım: edge filter + origin enrich
Pratikte sistem şöyle çalışıyor:
- Edge'de tanımlı pattern'lar %95'i yakalıyor — origin'e ulaşmıyor
- Geri kalan %5 akıllı saldırı (slow burst, distributed credential stuffing) origin classifier'a düşüyor
- Origin classifier ThreatScore biriktirip 24h auto-block'la edge'i besliyor (cluster B7 cross-link)
- Sistem zamanla kendi kendini eğitiyor: bugün origin'de yakalanan bir attacker yarın edge'de bloklanıyor
Bu yaklaşımın somut sonucu son 24 saatte: 157 attack probe → origin'e 0 ulaştı. WAF rule + IP list + ASN block kombinasyonu %100 başarı verdi. Slow-burst saldırılar henüz görülmedi; gözlendiğinde origin classifier devreye girecek.
📊 [METRIK: 24h edge block sayısı vs origin probe sayısı bar chart]
WAF UA Matcher Tuzakları (curl/wget/python-requests)
WAF rule'ları User-Agent string'lere bakar. Bu yöntem default tooling UA'larına karşı çok etkili ama bir tuzak içeriyor: kendi tanı testlerin de UA filtresine takılır.
Default tooling UA blacklist'i
Aşağıdaki UA pattern'larını Rule #3'te bloklamıştık (özet):
| Tool | Default UA | Yaygınlık |
|---|---|---|
| curl | curl/7.X.Y |
Çok yüksek (manuel test + scripted scanner) |
| wget | Wget/1.X |
Yüksek (CI/cron scripts) |
| python-requests | python-requests/2.X |
Çok yüksek (Python scraper'lar) |
| Go HTTP client | Go-http-client/1.1 |
Yüksek (custom Go tooler'ı) |
| libwww-perl | libwww-perl/6.X |
Düşük ama legacy script'ler |
| sqlmap | sqlmap/1.X |
SQL injection scanner |
| nmap | Nmap Scripting Engine |
Network mapper |
| masscan | masscan/1.X |
Hızlı port tarayıcı |
| nuclei | Nuclei - Open-source vuln scanner |
Modern vuln scanner |
| nikto | Nikto/2.X |
Web scanner |
| wpscan | WPScan |
WordPress brute force |
Bu pattern'lar çoğu meşru kullanım için yeterli değil — gerçek geliştirici curl ile manuel test ederken -A flag'i kullanarak UA'sını override eder.
Tanı yaparken UA override şart
Bu blog yazısının kendisinde IndexNow key.txt endpoint'i test ederken bu tuzağa düştük. Plain curl 403 alıyor — WAF rule #3 default UA'yı bloklıyor. Bingbot UA ile retry yapınca 200 dönüyor:
# 403 Forbidden — WAF UA filter
curl https://bilalkose.com.tr/<key>.txt
# 200 OK — UA override
curl -A "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)" \
https://bilalkose.com.tr/<key>.txt
Cloudflare verified bot listesi (Bingbot, Googlebot, IndexNowBot) otomatik allowlist'te — kendi WAF rule'larımız bunları engellemiyor. Verified bot'un asıl olup olmadığını CF reverse DNS ile teyit ediyor.
Test checklist: yeni rule deploy sonrası
Her WAF rule değişikliğinden sonra şu test setini koşmak iyi pratik:
curl -A "Mozilla/5.0" <url>— gerçek user-agent simülasyonucurl -A "Mozilla/5.0 (compatible; bingbot/2.0; ...)" <url>— Bingbotcurl -A "Mozilla/5.0 (compatible; Googlebot/2.1; ...)" <url>— Googlebotcurl <url>(plain) — beklenen: 403 (WAF rule #3 yakalar)curl -A "sqlmap/1.7" <url>— beklenen: 403
Bu beş test 30 saniyede yapılır ve regression sigortası sağlar.
Origin Firewall: UFW + Cloudflare CIDR Allowlist
Tüm trafik Cloudflare'den geçmiyor olsa? Bilinen bir tehdit: attacker site'ın gerçek origin IP'sini bulup direkt connect olmaya çalışır. DNS history, certificate transparency log'ları, eski WHOIS kayıtları gibi yöntemlerle origin IP keşfi mümkün.
Niye ikinci hat? Edge bypass riski
Edge security tek başına yetmiyor çünkü:
- Cloudflare proxy moduna geçmeden önce DNS A record gerçek IP'yi göstermiş olabilir (history)
- TLS sertifikası transparency log'a yazılır — CN/SAN'lerden domain → IP eşleştirmesi mümkün
- Subdomain enumeration (CT logs) ile proxy'siz subdomain bulunabilir
- Eski IP rotation kayıtları cached DNS resolver'larda kalır
Bir kez gerçek IP bulundu mu, attacker direkt origin'e bağlanır — edge bypass.
cloudflare-ufw-apply.sh: CIDR allowlist + default deny
Origin sunucusunda UFW (Uncomplicated Firewall) yapılandırması:
# Cloudflare official IPv4 + IPv6 CIDR listesi
curl -s https://www.cloudflare.com/ips-v4 | while read cidr; do
ufw allow from $cidr to any port 80,443 proto tcp
done
curl -s https://www.cloudflare.com/ips-v6 | while read cidr; do
ufw allow from $cidr to any port 80,443 proto tcp
done
# SSH ve LAN ayrı
ufw allow from $ADMIN_SSH to any port 22 proto tcp
ufw allow from $LAN_CIDR to any
# Default deny — diğer her şey reddedildi
ufw default deny incoming
Cloudflare'in resmi IP listesi 15 IPv4 CIDR + 7 IPv6 CIDR (Mart 2026 itibariyle). Liste haftada bir cron'la güncellenmeli — Cloudflare ranges nadiren değişir ama izlemek lazım.
Auto-rollback timer: kilitlenme sigortası
UFW apply script'i çalıştırırken SSH yanlış allowlist'lenirse sunucuya tekrar giremezsin. Bu felaketten kaçınmak için systemd-run ile auto-rollback timer kuruluyor:
# 5 dakika içinde aksini söyle yoksa rollback yap
systemd-run --on-active=5min /usr/local/sbin/firewall-rollback.sh
# Yeni kurallar yüklendi, SSH test
ufw reload
# ... test ssh login from another terminal ...
# Test OK → timer'ı iptal et
systemctl stop run-r*.timer
5 dakika içinde systemctl stop ile cancel etmezsek, firewall-rollback.sh çalışır ve UFW'yi backup state'ine geri alır. Origin firewall script'lerimiz /usr/local/sbin/ altında: firewall-backup.sh, firewall-rollback.sh, cloudflare-ufw-apply.sh (cluster B9 cross-link).
Cloudflare WAF Event Collector → SQL Server ETL
Edge'de blok edilen request'lerin görünürlüğü olmadan sistem kör. Cloudflare dashboard'u 7 günlük analytics tutuyor (free plan) ama derinleştirilmiş analiz, custom dashboard, alert'leme için kendi DB'mizde event kopyası lazım.
Cloudflare GraphQL Analytics API
REST API logları dakika granularity'de tutmuyor; bu iş için GraphQL Analytics endpoint'i var:
POST https://api.cloudflare.com/client/v4/graphql
Authorization: Bearer <token>
Free plan'da event retention 5 dakika; Pro'da 24 saat; Business'ta 30 gün. Free plan ile çalışıyorsan polling interval 1-3 dakika olmalı, yoksa veri kaybediyorsun. Bizim setup'ta 5 dakika tutuldu (Pro plan üst limitinde) ama interval 60 saniye.
WafEventCollector (IHostedService)
Background service her dakika GraphQL query'sini koşuyor:
query GetWafEvents($zoneTag: String!, $since: Time!, $until: Time!) {
viewer {
zones(filter: { zoneTag: $zoneTag }) {
firewallEventsAdaptive(
limit: 1000
filter: { datetime_geq: $since, datetime_leq: $until }
) {
rayName
action
source
clientIP
clientCountryName
clientASNDescription
clientRequestHTTPHost
clientRequestPath
userAgent
datetime
}
}
}
}
Cursor-based pagination yok (firewallEventsAdaptive limit 10000); bunun yerine datetime_geq cursor'ı kullanıyoruz. Her query sonunda en son event'in datetime'ı kaydediliyor, sonraki çağrıda since olarak gönderiliyor. Idempotent INSERT'ler için ray_name unique key:
CREATE TABLE dbo.CloudflareWafEvents (
EventId BIGINT IDENTITY(1,1) PRIMARY KEY,
RayName NVARCHAR(64) NOT NULL UNIQUE,
Action NVARCHAR(32) NOT NULL,
Source NVARCHAR(64),
ClientIp NVARCHAR(64),
ClientCountry NVARCHAR(64),
ClientAsn NVARCHAR(128),
Host NVARCHAR(256),
RequestPath NVARCHAR(2048),
UserAgent NVARCHAR(512),
OccurredAt DATETIME2 NOT NULL,
INDEX IX_OccurredAt (OccurredAt DESC),
INDEX IX_Action (Action)
);
Admin dashboard kartları (P015 cluster B7 referans)
Topladığımız event'leri admin dashboard'da görselleştiriyoruz (Razor + Chart.js):
- Son 24 saat block sayısı (line chart)
- Top 5 country (bar chart)
- Top 5 ASN (bar chart)
- Top 5 saldırı path (table)
- Top 10 UA pattern (table)

P015 (Admin Dashboard Sub-Faz C planı) bu kartların detay implementasyonunu içeriyor.
Üretim Metrikleri: 24 Saatlik Gerçek Veriler
Mart 2026 EEAT update'ten sonra orijinal data, abstract analiz'in önüne geçti. Aşağıda son 24 saatten doğrudan çıkarılmış sayılar (PageHits + AttackProbes + CloudflareWafEvents tablolarından).
Trafik dağılımı
| Metrik | Değer | Not |
|---|---|---|
| Toplam unique IP (24h) | 15 | TrafficClassifier'dan |
| Gerçek ziyaretçi (residential ASN) | 5 (%33) | Bharti Airtel IN AS45609 dahil |
| Şüpheli (datacenter/VPN/null ASN) | 10 (%67) | IsSuspectNetwork=1 flag |
| Origin'e ulaşan attack probe | 0 | %100 edge'de durdu |
| Edge'de bloklanan attack probe | 157 | WAF rule + IP list + ASN block toplam |
| Otomatik blocklist'teki IP (24h TTL) | 15 | DB ve Cloudflare List'te senkron |
%67 şüpheli oranı şaşırtıcı görünebilir ama 2026 internet'inde norm. Eğer analytics dashboard'unda günde 100 unique ziyaretçi görüyorsan gerçek insan sayısı 30-40 civarındadır. Geriye kalan VPN, datacenter, scraper.
Saldırı kategorisi dağılımı
157 attack probe'un kategori kırılımı (AttackProbe.Category):
| Kategori | Sayı | Pattern örneği |
|---|---|---|
| sensitive-paths | ~75 | /.env, /.git, /.aws, /.ssh |
| cms-probe | ~45 | /wp-admin, /wp-login.php, /xmlrpc.php |
| sqli-payload | ~15 | union select, ' or 1=1 |
| backup-leak | ~12 | /dump.sql, /backup.zip |
| scanner-ua | ~7 | sqlmap, nuclei UA |
| log4shell | ~3 | ${jndi:ldap://...} |
CMS probe sayısı yüksek — WordPress hiç çalıştırmıyoruz ama saldırgan otomatik tarayıcılar varsayım olarak deniyor.
Origin CPU karşılaştırma
Edge security öncesi (2026-04-15) vs sonrası (2026-05-10) tek-pod ortalama CPU:
| Metrik | Önce | Sonra | Fark |
|---|---|---|---|
| CPU usage (avg) | ~18% | ~7% | -61% |
| Logs.Error/saat | ~85 | ~12 | -86% |
| DB connection pool peak | 9/10 | 3/10 | -66% |
%61 CPU düşüşü tek başına edge security'nin maliyetini birkaç ay içinde amorti ediyor (k8s node size küçültme veya replica azaltma).
Free dataset (linkable asset #3)
24 saatlik anonimleştirilmiş telemetri snapshot CSV olarak yayınlandı (hiçbir IP/PII yok; sadece ASN + country + kategori + sayı):
🔗 [LINKABLE ASSET #2: gist.github.com/bilalkose/web-app-24h-attack-snapshot.csv]
Cross-pillar bridge: Bu veri seti Pillar 2 (K8s'te .NET Security Telemetry) yazısında daha derin analiz ediliyor — gerçek ziyaretçi tespiti, ThreatScore hesaplama formülü, MERGE+OUTPUT burst counter.
Sıkça Sorulan Sorular
Free plan Cloudflare ile bu mimari ne kadar genişler?
Free plan üç limit dayatıyor: zone başına 5 custom rule, IP rule per-rule ~kilobyte expression, GraphQL Analytics 5 dakika retention. Bizim setup'ta 4 rule app'ten kodla push ediliyor, 1 rule manuel (Admin self-IP bypass) — limit tam dolu. IP rule limit problemi Cloudflare List ile çözüldü (10K IP / list, 10 list / account = 100K IP kapasite). GraphQL 5 dakika retention için polling interval'ini 60 saniyeye indirmek gerekiyor.
10K IP'den fazla auto-block'a ihtiyaç doğarsa Pro plan ($20/ay) zone başına 25 rule + 24 saat retention sunuyor. Bizim trafik hacmimizde Pro plan'a geçiş henüz gerekmedi.
Vault olmadan secret rotation nasıl yapılır?
Üç alternatif var: (1) Kubernetes native Secret objesi + reloader pattern — basit ama rotation manuel, audit log yok. (2) Azure KeyVault + CSI driver — .NET için iyi entegre, otomatik mount. (3) Doppler / Infisical / 1Password CLI — SaaS, configurable rotation.
Bizim seçim HashiCorp Vault: audit log, dynamic secret (DB credential auto-rotate), KV v2 versioning, K8s init container ile secret eject. Cluster B6 (Vault'tan Cloudflare API token rotation) detayını anlatıyor.
KVKK / GDPR uyumlu mu PageHits sistemi?
Evet. PageHits tablosu IP hash + GeoIP enrichment'tan (city level, sokak seviyesi değil) oluşuyor; ChatId, SessionId, cookie ID veya kullanıcıyı tekil tanımlayan veri tutmuyor. 30 gün retention sonrası otomatik DELETE (IpBlocklistExpiryHostedService benzeri). Privacy.tr/en/ar.resx 13 bölümde KVKK madde 5 ve GDPR Article 6(1)(f) gerekçesi açıklanıyor (cluster C10 cross-link).
AWS WAF vs Cloudflare WAF'tan .NET için hangisi?
Maliyet ve esneklik perspektifinden farklılaşıyor:
| Boyut | AWS WAF | Cloudflare WAF |
|---|---|---|
| Konum | ALB / CloudFront önünde | CDN katmanında, multi-cloud |
| Maliyet (sample) | $5/ay + $1/rule + $0.60/M req | Free 5 rule, Pro $20/ay 25 rule |
| .NET SDK | Resmi (AWSSDK.WAFV2) | Topluluk |
| GraphQL Analytics | CloudWatch Logs (ücretli) | Native, dahil |
| KVKK uyumlu region | eu-central-1 | Frankfurt + Istanbul edge |
| Lock-in | AWS infrastructure | Bağımsız |
AWS-only mimarisinde AWS WAF doğal seçim, on-prem veya multi-cloud için Cloudflare daha esnek. Bizim setup K8s on-prem olduğundan Cloudflare daha uyumlu.
Origin classifier'ın WAF rule'larına geri-besleme döngüsü güvenli mi?
Auto-block döngüsü "kendi-kendini eğiten" sistem gibi görünüyor; potansiyel olarak gerçek kullanıcıyı yanlışlıkla bloklama riski var mı? Üç güvenlik önlemi koyduk:
- ThreatScore ≥ 12 eşiği + 15dk pencerede ≥ 3 farklı sensitive path — toplam puanı yüksek olsa bile tek hata yetmiyor
- 24 saat TTL — yanlış block tetiklendiyse 24 saatte otomatik düşüyor
- Admin manual override —
/admin/security/ip-blocklistüzerinden tek tıkla remove + Cloudflare List'ten sync silme
İlk hafta 15 IP'lik blocklist'te false-positive sayısı 0. Kendi self-IP'imiz _allowedSelfIps opt-out listesinde olduğu için ekstra güvenlik.
İlgili Yazılar
Bu pillar'ın cluster'ları (yayın sırası):
- B1 Cloudflare WAF API ile .NET'te custom rule yönetimi (CRUD)
- B2 IP Blocklist push: Cloudflare List API + 5 WAF rule
- B3 ASN bazlı bot tespiti: 42 datacenter ASN registry (downloadable CSV)
- B4 IsAnonymousOrDataCenter classifier: VPN/datacenter karar matrisi
- B5 Cloudflare edge vs origin classifier: hangisi ne zaman?
- B6 Vault'tan Cloudflare API token rotation
- B7 WAF event collector: Cloudflare GraphQL → SQL Server ETL
- B8 WAF UA matcher tuzakları (curl/wget/python-requests)
- B9 Origin firewall: UFW + Cloudflare CIDR allowlist + auto-rollback
- B10 Edge fetcher UA testleri: Bingbot, IndexNowBot pass-through
Cross-pillar bridge: .NET ve Kubernetes'te Trafik Tabanlı Security Telemetry — Pillar 2 — gerçek ziyaretçi tespiti, ThreatScore hesaplama, MERGE+OUTPUT burst counter, %33/%67 oran analizi.
Linkable asset bundle:
- 🔗 ASN Registry CSV (gist) — açık kaynak datacenter ASN listesi, MIT lisans
- 🔗 24h Attack Snapshot CSV (gist) — anonimleştirilmiş telemetri data seti
- 🔗 Cloudflare Edge Starter (GitHub repo) — minimal .NET 10 starter template
Yorumlar (0)
Henüz yorum yapılmamış. İlk yorumu sen bırak.