Detectify’da müşterilerin saldırı yüzeylerini güvence altına almalarına yardımcı oluyoruz. Varlıklarını etkili ve kapsamlı bir şekilde test etmek için sistemlerine çok yüksek miktarda istek göndermemiz gerekiyor, bu da sunucularının aşırı yüklenmesi potansiyel riskini beraberinde getiriyor. Doğal olarak, testlerimizin hız sınırlayıcımızla güvenli bir şekilde yürütülürken müşterilerimize maksimum değer sunmasını sağlamak için bu zorluğun üstesinden geldik.
Tanıttıktan sonra motor çerçevemiz Ve Müşterilerimizin saldırı yüzeyini izleyen teknolojinin derinliklerine inmeBu Kaputun Altında blog yazısı bulmacanın önemli bir parçasını ortaya çıkarıyor: hız sınırlayıcı hizmetimizin tüm Detectify taramalarını güvenli hale getirmek için nasıl çalıştığı.
Hız sınırlayıcıya duyulan ihtiyaç
Önceki yazımızda postalamakbir motorun müşterilerimizin sistemlerinde gerçekleştirdiği güvenlik testlerinin eğrisini düzleştirerek, sunucularının aşırı yüklenmesine neden olabilecek ani artışlardan kaçınmak için uyguladığımız teknikleri açıkladık. Araştırma envanterimize sürekli olarak daha fazla güvenlik testi ekledikçe ve aynı anda çalışan birçok motorla birlikte, tüm testlerden elde edilen toplam yükün önemli seviyelere ulaşabileceğini ve potansiyel olarak müşterilerimiz için sorun yaratabileceğini hayal edebiliriz.
Bu sorunu çözmek için, herhangi bir hedefe yönelik saniye başına maksimum istek sayısını sınırlamak üzere tasarlanmış bir küresel hız sınırlayıcıyı kullanıma sunduk. Mantıklı varsayılanlar mevcuttur ve müşteriler bu sınırı kendi ihtiyaçlarına göre yapılandırma esnekliğine sahiptir.
Ürün gereksinimleri
Hız sınırlayıcı çözümüne geçmeden önce hız sınırlayıcımızın gereksinimlerini anlamak önemliydi.
Konsept olarak hız sınırlama, yazılım mühendisliğinde yeni bir şey değildir ve hız sınırlama zorluklarının üstesinden gelmek için pek çok araç mevcuttur. Ancak durumumuzla ilgili özel bir çözüm gerektiren benzersiz bir şey olup olmadığını veya kullanıma hazır bir ürünün yeterli olup olmadığını belirlememiz gerekiyordu.
Birçok popüler açık kaynak aracı ve bulut tabanlı çözümü araştırdık. Maalesef analizlerimiz sırasında kriterlerimize uygun bir seçenek bulamadık. Detectify, sınırı köken bazında (şema, ana bilgisayar adı ve bağlantı noktası numarası kombinasyonu) uygular ve bireysel hedef ve son derece dinamik yapılandırmalara izin vermesi gerekir. Mevcut araçların çoğu bu alanda yetersiz kaldı ve bu da bizi kendi hız sınırlayıcımızı uygulama kararına götürdü.
Edebiyat
Hız sınırlama, yazılım mühendisliğinde iyi bilinen bir kavramdır. Kendimiz inşa etmemize rağmen sıfırdan başlamamıza gerek yoktu. Engelleme, azaltma ve şekillendirme dahil olmak üzere çeşitli hız sınırlama türlerini araştırdık. Ayrıca jeton kovası, sızdıran kova, sabit pencere sayacı ve kayan pencere sayacı gibi en sık kullanılan algoritmaları da araştırdık. Ayrıca kullanabileceğimiz farklı topolojilere de baktık.
Amacımız, gereksinimlerimizi karşılarken uygulamayı olabildiğince basit tutmaktı. Sonunda, engelleyici bir token kova hızı sınırlayıcısı uygulamaya karar verdik. İlginç bir şekilde, güvenlik testi ihtiyaçlarımız ve bazı güvenlik testlerinin ne kadar “yığında” olduğu göz önüne alındığında, daha yaygın olan proxy yaklaşımı yerine hizmet kabul yaklaşımına yöneldik.
Kısacası, engelleme hızı sınırlayıcı, sınır aşıldığında hedefe yönelik istekleri reddeder. Bunun tersine, hız sınırlayıcıların kısıtlanması ve şekillendirilmesi, istekleri yavaşlatarak, geciktirerek veya önceliklerini düşürerek yönetir. Token kovası algoritması, her hedef için madde sınırına ulaşmaya eşit sayıda token ile bir saatte doldurulan bir “kova” bulundurarak çalışır. Her istek, kabul edilebilmek için kovadan bir jeton tüketir. Kovanın jetonları bittiğinde, kova yeniden dolduruluncaya kadar istek reddedilir. Hizmet kabul yaklaşımı, bir hedefe yönelik güvenlik testi yapmak isteyen motorların öncelikle küresel hız sınırlayıcıdan kabul alması gerektiği anlamına gelirken, proxy yaklaşımı, motor ile hedef arasındaki istek için daha şeffaf bir “ara katman yazılımı” olarak çalışacaktır. .
Teknik seçimler
Algoritma ve topoloji tanımlandıktan sonra ihtiyaçlarımızı en iyi şekilde karşılayabilecek teknolojileri keşfetmenin zamanı gelmişti. Küresel hız sınırlayıcının, yüksek düzeydeki istekleri önemli düzeyde eşzamanlılıkla işlemesi, aynı zamanda çok düşük gecikmeyle çalışması ve ölçeklenebilir olması gerekir.
Bu gereksinimleri daha da genişlettik ve çözümün bellekte çalışması ve mümkün olduğunca az dahili işlem içermesi gerektiğine karar verdik. Örneğin, son kullanma tarihi politikalarıyla atomik işlemlerden ve basit kilitlerden yararlanmak. Sıcak eşzamanlılık noktasında, eşzamanlılık kontrollerinin ek yükünden kaçınarak sıralı olarak çalışacak tek iş parçacıklı bir yaklaşımı tercih ettik.
Seçeneklerimizi tartıştıktan sonra en iyi çözümün, Redis parçalı kümesi tarafından desteklenen uzun ömürlü ECS görevlerinde küresel hız sınırlayıcı hizmetini çalıştırmak olduğu sonucuna vardık. AWS kullandığımız için Redis parçalı kümemizi oluşturmak için ElastiCache’i kullanmayı uygun bulduk.
Bana kodu göster!
Küresel hız sınırlayıcı hizmeti oldukça basittir ve bir hedefe kabul talebinde bulunmak için basit bir API sağlar. Daha ilginç olan husus, hizmet ile Redis arasında token kovası algoritmasının uygulanmasında yatmaktadır.
Süre sonu politikalarıyla atomik işlemlerden ve basit kilitlerden yararlanmayı, aynı zamanda yüksek eşzamanlılık alanlarında görevleri sırayla çalıştırmayı hedefledik. Bu sıralı yürütme, tek iş parçacıklı bir şekilde çalıştığı için Redis’te basitti. Odak noktamız eşzamanlılık zorluklarını buna yerleştirmekti. Son kullanma tarihi politikalarına sahip basit kilitler, Redis’in üstün olduğu alanlardan biri olarak kullanışlıydı. Bu noktada, algoritmayı mümkün olduğunca az hizmet-Redis etkileşimi ile ve mümkün olduğunca atomik olarak tasarlamaya odaklanmamız gerekiyordu. Birkaç tekrardan sonra bir çözüme ulaştık: Redis sunucusunda bir Lua betiği çalıştırır. Redis, betiğin ihtiyaca mükemmel şekilde uyan atomik yürütülmesini garanti eder.
Aşağıda ayrıntılı açıklamalarla birlikte koda bir göz atalım:
local bucket = KEYS[1] local tokens = ARGV[1] local tokensKey = bucket..":tokens" local lockKey = bucket..":lock" local tokensKeyExpirationSec = 1 local lockKeyExpirationSec = 1 local admitted = 1 local notAdmitted = 0 local refilled = 1 local notRefilled = 0 if redis.call("decr", tokensKey) >= 0 then return { admitted, notRefilled } end if redis.call("set", lockKey, "", "nx", "ex", lockKeyExpirationSec) then redis.call("set", tokensKey, tokens - 1, "ex", tokensKeyExpirationSec) return { admitted, refilled } end return { notAdmitted, notRefilled }
Betik birkaç parametre alır: paket adı ve tokenlara ilişkin sınır değeri. Zaman penceresine gelince, biz sadece 1 saniyelik zaman pencereleriyle çalışıyoruz. Daha sonra, kabul kontrolüne geçerken yaptığımız ilk şey, kovadan bir jeton azaltmaya çalışmaktır. Mevcut jetonlar varsa talebi kabul ederiz. Aksi halde kovayı yeniden doldurma zamanının gelip gelmediğini kontrol ederiz. Bunu yapmak için Redis’in kilit anahtarı yoksa ayarlama özelliğini kullanıyoruz ve sona erme süresi olarak 1 saniyelik zaman penceresini sağlıyoruz. Kilit anahtarını ayarlamayı başarırsak, yeni bir zaman penceresine girdiğimiz ve kovayı yeniden doldurabileceğimiz anlamına gelir, bunu devam etmek için onayı geri verirken yaparız. Yeterli jetonumuz yoksa ve henüz kovayı yeniden dolduramadıysanız, isteği reddederiz. Belirteç anahtarının da bir son kullanma süresi vardır, böylece bir hedefe yönelik istekler bir süredir gerçekleşmediğinde ekstra temizlik yapmamıza gerek kalmaz.
Performansı nasıl?
Kurulduğu günden bu yana performansından memnunuz. Ortalama olarak saniyede 20.000 isteği işliyor ve zaman zaman saniyede 40.000 isteğe kadar çıkan zirvelere ulaşıyor. p99’un gecikmesi normalde 4 milisaniyeden düşüktür ve hata oranı %0’a yakındır.
Gözlemlenebilirlikle ilgili ilginç bir zorluk, tek bir hedefe yönelik kaç isteğin gerçekleştirildiğinin belirlenmesidir. Zaman serisi veritabanlarına aşina olanlar için hedefleri etiket olarak kullanmak işe yaramaz ve bu da kardinalitede bir patlamaya yol açar. Bir alternatif, günlüklere güvenmek ve günlük tabanlı ölçümler oluşturmak olabilir, ancak uğraştığımız hacme bakıldığında, bunun finansal açıdan son derece maliyetli olacağını hayal edebilirler.
Bu zorluğun üstesinden gelmek için yaratıcı düşünmemiz gerekiyordu. Biraz düşündükten sonra kovalar yeniden doldurulduğu anda oturum açmaya karar verdik. Bu yöntem, bir hedefe yapılan isteklerin tam sayısını vermese de, zamanın çeşitli noktalarında bir hedefe gönderilebilecek maksimum istek sayısını gösterir. Bu bizim için temel bilgidir çünkü izlememize ve belirlenen limitleri aşmadığımızdan emin olmamıza olanak sağlar.
Daha fazla motor ve test, daha güvenli müşteriler
Müşterilerimizi güvende tutmak, sunucularında beklenmedik yük sorunlarına yol açmadan ve potansiyel olarak işlerini etkilemeden, saldırı yüzeylerinde güvenlik testlerini güvenli bir şekilde çalıştırmanın güvenli bir yolunu sağlamak için mükemmel teknik dengeyi yakalamakla ilgilidir. Küresel hız sınırlayıcımızın uygulanması, envanterimizdeki motor sayısını ve güvenlik testlerini güvenli bir şekilde artırmamıza ve sistemlerini bozmadan daha fazla güvenlik testi gerçekleştirmemize olanak sağladı.
Mühendislik açısından bakıldığında küresel hız sınırlayıcının uygulanması ilginç bir teknik zorluk teşkil ediyor ve biz de bizim için işe yarayan bir çözüm bulduk. Herhangi bir değişiklik olması durumunda, tüm süreç boyunca kapsamlı fikir yürütmemiz sayesinde, müşterilerimize güvenli ve güvenilir bir deneyim sunmak için uyum sağlamaya hazırız. Bununla birlikte, git kendini hackle!
Detectify hakkında daha fazla bilgi edinmek ister misiniz? 2 haftalık ücretsiz deneme sürümünü başlatın veya uzmanlarımızla konuşun.
Zaten bir Detectify müşterisiyseniz en son ürün güncellemeleri, iyileştirmeler ve yeni güvenlik testleri için Yenilikler sayfasını kaçırmayın.