Tüm yazılar
·8 dk okuma·ShamashAi Ekibi

ShamashAi Incident Groups: 200 event'i tek vakaya çeviren kümeleme motoru

ShamashAi'nin incident_groups motoru aynı kaynaktan gelen yüzlerce event'i otomatik gruplar. Aynı saldırgan IP'den 200 brute-force denemesi tek vaka olur, triage grup seviyesinde yapılır. group_key mantığı ve upsert mekanizması.

Salı 14:47'de notification patlaması

Sistem odasındaki monitörlere bakıyorsunuz, kahveniz elimizde. Slack'te ShamashAi entegrasyonu üst üste bildirim gönderiyor: "BRUTE_FORCE_DETECTED: dc01.firma.local". Bir tane değil. Beş dakika içinde 187 adet. Aynı cihaz, aynı kaynak IP, aynı event tipi. Klasik brute-force saldırısı — saldırgan 185.x.x.x adresinden Active Directory domain controller'ınıza karşı RDP şifre denemesi yapıyor. Geleneksel yaklaşımda bu 187 event, 187 ayrı vaka demektir. Her biri ayrı ticket, ayrı triage, ayrı kapatma notu. Sysadmin ekibiniz bu event yığınını tek tek gözden geçirip tek tek kapatmaya çalışır. Yarısını kapatırken yenileri gelir. Bir süre sonra "hepsi aynı şey" deyip toplu silme veya ignore etme eğilimi başlar — ve işte o anda gerçek bir tehdit kaçırılır. ShamashAi'nin Incident Groups motoru bu senaryoda devreye girer. 187 event'i tek bir incident grubu altında toplar. Triage işlemini grup seviyesinde yaparsınız: bir kez "resolved" işaretlersiniz, tüm alt event'ler o grubun parçası olarak kapanır. Aynı kaynak yeniden saldırıya geçerse sistem yeni grup açar — eski grubu reopen etmez. Bu yazıda ShamashAi'nin group_key mantığını, upsertIncidentGroupFromEvent mekanizmasını ve triage iş akışını açıklıyoruz.

group_key: benzerliği tanımlayan hash

ShamashAi her event'i dbo.events tablosuna kaydettiğinde, backend'deki upsertIncidentGroupFromEvent fonksiyonu otomatik çağrılır. Bu fonksiyon event'in üç kritik özelliğini alır:
  • event_type (ör. BRUTE_FORCE_DETECTED, AUTH_FAIL_USER, KNOWN_BAD_IP)
  • device_id (hangi cihazda tetiklendi)
  • src_ip (kaynak IP adresi — varsa)
Bu üçünün birleşiminden SHA-256 hash üretilir: javascript // Node.js Fastify – /src/services/incidentGroupService.js const crypto = require('crypto'); function generateGroupKey(eventType, deviceId, srcIp) { const raw = ${eventType}:${deviceId}:${srcIp || 'none'}; return crypto.createHash('sha256').update(raw).digest('hex'); } async function upsertIncidentGroupFromEvent(eventId, eventType, deviceId, srcIp, severity) { const groupKey = generateGroupKey(eventType, deviceId, srcIp); // dbo.incident_groups'ta bu hash'e sahip OPEN grup var mı? let group = await db.query( `SELECT id, evidence_count, status FROM dbo.incident_groups WHERE group_key = @groupKey AND status NOT IN ('resolved', 'false_positive')`, { groupKey } ); if (group) { // Var — evidence_count artır, son görülme zamanını güncelle await db.query( `UPDATE dbo.incident_groups SET evidence_count = evidence_count + 1, last_seen_at = GETUTCDATE() WHERE id = @groupId`, { groupId: group.id } ); } else { // Yok — yeni grup oluştur const newGroup = await db.query( `INSERT INTO dbo.incident_groups (group_key, event_type, device_id, src_ip, severity, evidence_count, status, first_seen_at, last_seen_at) OUTPUT INSERTED.id VALUES (@groupKey, @eventType, @deviceId, @srcIp, @severity, 1, 'open', GETUTCDATE(), GETUTCDATE())`, { groupKey, eventType, deviceId, srcIp, severity } ); group = { id: newGroup.id }; } // event'i gruba bağla await db.query( UPDATE dbo.events SET incident_group_id = @groupId WHERE id = @eventId, { groupId: group.id, eventId } ); return group.id; } Bu kod parçası her event geldiğinde çalışır. Aynı group_key hash'ine sahip açık (open, acknowledged, investigating) bir grup varsa event o gruba eklenir ve evidence_count bir artar. Eğer o hash'e sahip grup yoksa veya eski grup resolved / false_positive statüsündeyse yeni grup açılır.

evidence_count: grubun ağırlığı

dbo.incident_groups tablosundaki evidence_count kolonu, o grubun altında kaç adet dbo.events kaydı olduğunu gösterir. Örneğin: sql SELECT id, event_type, device_id, src_ip, severity, evidence_count, status, first_seen_at, last_seen_at FROM dbo.incident_groups WHERE status = 'open' ORDER BY evidence_count DESC; Bu sorgu size "en çok event toplayan" grupları gösterir. 187 event'li brute-force grubu listenin en üstünde çıkar. Triage yapan analist bu sayıya bakarak saldırının yoğunluğunu anlar: "3 deneme" ile "187 deneme" arasındaki risk farkı açıktır. evidence_count artışı gerçek zamanlıdır. Saldırgan hâlâ deneme yapıyorsa, ShamashAi web arayüzünde "Incidents > Groups" sayfasındaki sayı her yeni event'te güncellenir. WebSocket push sayesinde sayfa yenilemeye gerek kalmaz.

Triage iş akışı: grup seviyesinde karar

ShamashAi'nin Incidents modülünde her grubun beş olası durumu vardır: 1. open — yeni açılmış, henüz kimse bakmadı 2. acknowledged — analist "gördüm" işareti koydu, inceleme sırasında 3. investigating — aktif araştırma yapılıyor, ek veri toplanıyor 4. resolved — tehdit giderildi veya meşru aktivite olduğu onaylandı 5. false_positive — yanlış alarm, kural ayarı gerekiyor Web arayüzünde grup detay sayfasına girdiğinizde sağ üst köşede durum dropdown'u vardır. Durumu değiştirdiğinizde backend şu endpoint'i çağırır: javascript // POST /incidents/groups/:groupId/status fastify.post('/incidents/groups/:groupId/status', async (request, reply) => { const { groupId } = request.params; const { status, notes } = request.body; // status: 'resolved', 'false_positive' vs. // İzin kontrolü if (!['open', 'acknowledged', 'investigating', 'resolved', 'false_positive'].includes(status)) { return reply.code(400).send({ error: 'Geçersiz status değeri' }); } await db.query( `UPDATE dbo.incident_groups SET status = @status, resolved_at = CASE WHEN @status IN ('resolved', 'false_positive') THEN GETUTCDATE() ELSE NULL END, resolved_by = @userId, resolution_notes = @notes WHERE id = @groupId`, { groupId, status, userId: request.user.id, notes } ); // Audit log await db.query( `INSERT INTO dbo.audit_log (user_id, action, resource_type, resource_id, details) VALUES (@userId, 'incident_group_status_change', 'incident_group', @groupId, @details)`, { userId: request.user.id, groupId, details: JSON.stringify({ status, notes }) } ); return { success: true }; }); Bir grubu resolved veya false_positive yaptığınızda, o grubun altındaki tüm event'ler de dolaylı olarak kapatılmış sayılır. dbo.events tablosunda ayrı bir status kolonu yoktur; event'in durumu incident_group_id üzerinden belirlenir. Bu sayede 187 event'i tek tek kapatmak yerine bir kez işlem yapmış olursunuz.

Resolved grup yeniden açılmaz — yeni grup oluşur

Önemli tasarım kararı: bir grubu resolved veya false_positive yaptıktan sonra aynı group_key hash'ine sahip yeni bir event gelirse, ShamashAi eski grubu reopen etmez. Bunun yerine yeni bir grup açar. Neden? Çünkü çözülmüş bir vakayı yeniden açmak, zaman damgası ve triage geçmişini karıştırır. Eski grubun resolved_at alanı doldurulmuştur ve raporlama/compliance için o anın kaydı önemlidir. Aynı saldırgan bir hafta sonra yeniden denerse, bu yeni bir olaydır ve yeni bir grup ID'si almalıdır. Uygulama: javascript // upsertIncidentGroupFromEvent içinde let group = await db.query( `SELECT id FROM dbo.incident_groups WHERE group_key = @groupKey AND status NOT IN ('resolved', 'false_positive')`, { groupKey } ); if (!group) { // Eski grup kapalı veya hiç grup yok — yeni oluştur group = await db.query( INSERT INTO dbo.incident_groups (...) OUTPUT INSERTED.id VALUES (...), { ... } ); } status NOT IN ('resolved', 'false_positive') filtresi sayesinde kapalı gruplar göz ardı edilir.

Grupları SOAR ile otomatikleştirme

ShamashAi'nin SOAR motoru incident gruplarıyla entegre çalışır. Örneğin "BRUTE_FORCE_DETECTED tipinde evidence_count > 50 olan gruplarda kaynak IP'yi otomatik engelle" kuralı oluşturabilirsiniz: javascript // SOAR playbook snippet (Node.js) fastify.post('/soar/playbooks/run', async (request, reply) => { const { playbookId, incidentGroupId } = request.body; const group = await db.queryOne( SELECT event_type, src_ip, evidence_count, device_id FROM dbo.incident_groups WHERE id = @groupId, { groupId: incidentGroupId } ); if (group.event_type === 'BRUTE_FORCE_DETECTED' && group.evidence_count > 50) { // Kaynak IP'yi firewall'da engelle const blockResult = await fetch('https://firewall.firma.local/api/block', { method: 'POST', headers: { 'Authorization': Bearer ${process.env.FW_TOKEN} }, body: JSON.stringify({ ip: group.src_ip, reason: Incident group ${incidentGroupId} }) }); // SOAR aksiyonunu logla await db.query( `INSERT INTO dbo.soar_actions (incident_group_id, action_type, target, result, executed_at) VALUES (@groupId, 'block_ip', @ip, @result, GETUTCDATE())`, { groupId: incidentGroupId, ip: group.src_ip, result: blockResult.status } ); // Grubu investigating durumuna al await db.query( UPDATE dbo.incident_groups SET status = 'investigating' WHERE id = @groupId, { groupId: incidentGroupId } ); } return { success: true }; }); Bu şekilde grup eşiği aştığında otomatik müdahale başlar. SOAR aksiyonu dbo.soar_actions tablosuna kaydedilir ve grubun detay sayfasında "Automated Actions" sekmesinde görünür.

Compliance ve kanıt paketi

ISO 27001:2022 Annex A.8.16 (monitoring activities) ve KVKK Madde 12 (veri güvenliği) gereklilikleri için, incident gruplarının tamamı ve altındaki event'ler kanıt niteliğindedir. ShamashAi GET /compliance/evidence endpoint'i ile bir grubun tüm detaylarını JSON veya PDF formatında çıkarabilirsiniz: http GET /compliance/evidence?incident_group_id=12345&format=pdf Authorization: Bearer <token> Dönüş yapısı:
  • Grup meta verisi (group_key, status, first_seen_at, last_seen_at, evidence_count)
  • Alt event'lerin listesi (timestamp, severity, raw_log)
  • Triage notları (resolution_notes, resolved_by, resolved_at)
  • SOAR aksiyonları (dbo.soar_actions kayıtları)
  • İlgili cihaz bilgisi (dbo.devices)
Bu paket denetim sırasında "olay kaydı" olarak sunulur. Gruplama sayesinde 187 event'in hepsi tek bir dokümanda toplanır, dağınıklık ortadan kalkar.

Web arayüzünde grup görünümü

ShamashAi Next.js web arayüzünde "Incidents > Groups" sayfası varsayılan olarak açık grupları evidence_count DESC sıralamasıyla listeler. Her satırda:
  • Event Type badge (renk kodlu: BRUTE_FORCE_DETECTED kırmızı, AUTH_FAIL_USER turuncu)
  • Evidence Count (büyük font, dikkat çeker)
  • Kaynak IP (tıklanabilir, IP detay sayfasına gider)
  • Device (hangi cihazda)
  • First Seen / Last Seen (zaman aralığı)
  • Status dropdown (open → acknowledged → investigating → resolved)
Grup satırına tıkladığınızda detay modal açılır: jsx // Next.js – /app/incidents/groups/[id]/page.tsx import { useState, useEffect } from 'react'; export default function IncidentGroupDetail({ params }) { const [group, setGroup] = useState(null); const [events, setEvents] = useState([]); useEffect(() => { fetch(/api/incidents/groups/${params.id}) .then(res => res.json()) .then(data => { setGroup(data.group); setEvents(data.events); }); }, [params.id]); if (!group) return <div>Yükleniyor...</div>; return ( <div> <h2>Incident Group #{group.id}</h2> <p>Event Type: {group.event_type}</p> <p>Evidence Count: <strong>{group.evidence_count}</strong></p> <p>Source IP: {group.src_ip}</p> <p>Status: {group.status}</p> <h3>Alt Event'ler ({events.length})</h3> <table> <thead> <tr><th>Timestamp</th><th>Severity</th><th>Message</th></tr> </thead> <tbody> {events.map(evt => ( <tr key={evt.id}> <td>{new Date(evt.timestamp).toLocaleString('tr-TR')}</td> <td>{evt.severity}</td> <td>{evt.message}</td> </tr> ))} </tbody> </table> <button onClick={() => updateStatus('resolved')}>Resolved olarak işaretle</button> </div> ); } Bu sayfa altındaki event'lerin detaylarını gösterir, ama triage kararı grup seviyesinde verilir. "Resolved" butonuna bastığınızda tüm 187 event kapatılmış sayılır.

Performans: 10.000 event/saniye altında gruplama

ShamashAi'nin upsertIncidentGroupFromEvent fonksiyonu her event insertion'da çağrıldığından performans kritiktir. SQL Server'da dbo.incident_groups tablosunun group_key kolonu indexedtir: sql CREATE NONCLUSTERED INDEX IX_incident_groups_groupkey_status ON dbo.incident_groups (group_key, status) INCLUDE (id, evidence_count); Bu index sayesinde "bu hash'e sahip açık grup var mı?" sorgusu <1ms'de döner. 10.000 event/saniye yük altında gruplama gecikmesi 50ms altında kalır (lab ortamında ölçülen ortalama). Gerçek üretim ortamında event akışı genelde 500-2000/saniye aralığında olduğundan sorun yaşanmaz.

Limitler ve dürüst notlar

  • Grup birleştirme yok: İki ayrı grup (ör. farklı src_ip) sonradan birleştirilemiyor. Tasarım gereği her grup hash'e sıkı bağlı.
  • Manuel grup açma yok: Kullanıcı elle "yeni incident group oluştur" yapamıyor. Gruplar sadece event akışından otomatik oluşur.
  • Cross-device gruplama yok: Aynı src_ip farklı cihazlarda ayrı gruplar açar. Çünkü device_id hash'in parçası. Multi-device korelasyon için AI investigation modülü gerekiyor.
  • Zaman bazlı group kapatma yok: Bir grup 7 gün boyunca yeni event almasa bile otomatik kapanmaz. Analist manuel resolved yapmalı. Roadmap'te "auto-expire" özelliği var.
  • Group_key değişikliği zor: Saldırgan proxy değiştirip src_ip'si değişirse yeni grup açılır. Behavioral correlation motoru bu tür dağıtık saldırıları yakalar ama bu başka bir modül.

Sonuç

ShamashAi'nin Incident Groups motoru, SIEM'in en büyük operasyonel sorununu — alert fatigue ve manuel triage yükünü — otomatik kümeleme ile çözer. group_key hash mantığı sayesinde aynı kaynak, aynı cihaz, aynı event tipindeki yüzlerce event tek bir vaka altında toplanır. Triage grup seviyesinde yapılır, evidence_count saldırının ağırlığını gösterir, SOAR aksiyonları grup eşiklerine göre tetiklenir. Resolved gruplar reopen edilmez; aynı hash yeni event geldiğinde yeni grup açılır. Compliance raporlaması için tüm grup ve alt event'ler tek bir kanıt paketi olarak çıkarılabilir. Pilot programı 30 gün ücretsiz: shamashai.com.tr/iletisim
Paylaş

Bu konuyu projenize uygulayalım

Pilot programı kapsamında ürünü gerçek altyapınızda 30 gün ücretsiz deneyin.