Anatomi Insiden: Lonjakan Trafik Scanner Go dan Blokir Edge

Peningkatan kapasitas konkurensi pada utility scanner berbasis Go (seperti arsitektur SenPaiScanner) sering kali memicu pemblokiran otomatis di lapisan edge network (misalnya Cloudflare WAF/Rate Limiting). Ketika ribuan goroutine memicu request tanpa kontrol aliran transport yang tepat, edge mendeteksi anomali ini sebagai serangan layer-7 atau scraping agresif, berujung pada status HTTP 429 (Too Many Requests), HTTP 403 Forbidden, atau pemutusan paksa melalui TCP Reset (ECONNRESET).

Dampaknya fatal: IP egress scanner masuk daftar blacklist, operasional monitoring lumpuh, dan metrik sistem terdistorsi.

Diagnosa Cepat Melalui Metrik dan Log

Identifikasi masalah harus dilakukan dalam hitungan menit sebelum IP reputasi scanner rusak permanen. Fokuskan observasi pada dua metrik utama:

  • Error Rate Spikes: Lonjakan tajam respon non-2xx. Filter metrik edge berdasarkan kode status 429 dan 403.
  • Transport Error Logs: Munculnya galat sistem operasi pada level socket Go, seperti read: connection reset by peer atau dial tcp: i/o timeout yang mengindikasikan edge memutus handshake TLS/TCP.
// Contoh query log aggregator (Loki / ELK)
{app="edge-scanner"} |= "connection reset by peer" or status = 429
| stats count() by status, target_host

Mitigasi Cepat: Safe Rollback Concurrency Worker

Jangan langsung menghentikan proses secara paksa (SIGKILL) karena akan merusak state scanner dan meninggalkan socket TCP dalam status TIME_WAIT. Terapkan safe rollback konfigurasi:

  1. Turunkan nilai pool worker secara dinamis atau deploy ulang versi stabil sebelumnya dengan konfigurasi worker minimal.
  2. Kirim sinyal SIGTERM agar proses menjalankan graceful shutdown, menyelesaikan job in-flight, dan menutup koneksi HTTP idle.
# Rollback cepat via environment variable jika runtime mendukung reload
export SCANNER_CONCURRENCY=50
kill -HUP $(pgrep edge-scanner)

# Atau rollback deployment via Kubernetes
kubectl rollout undo deployment/edge-scanner -n monitoring

Postmortem Root Cause: Misfigurasi Connection Pooling

Masalah utama scanner Go sering kali bukan pada jumlah goroutine, melainkan pada http.Transport default. Secara default, http.DefaultTransport memiliki konfigurasi MaxIdleConnsPerHost = 2.

Ketika 5.000 goroutine berjalan simultan ke host yang sama, hanya 2 koneksi idle yang disimpan. Sisanya (4.998 koneksi) dibuat baru dan langsung dibuang setelah request selesai. Hal ini menyebabkan lonjakan drastis dalam pembuatan soket TCP (SYN flood tak disengaja) yang langsung memicu mekanisme mitigasi DDoS Cloudflare.

// Solusi konfigurasi transport yang benar
var safeTransport = &http.Transport{
    MaxIdleConns:        1000,
    MaxIdleConnsPerHost: 100,
    IdleConnTimeout:     90 * time.Second,
    TLSHandshakeTimeout: 10 * time.Second,
}

var client = &http.Client{
    Transport: safeTransport,
    Timeout:   15 * time.Second,
}

Pencegahan Jangka Panjang: Dynamic Rate-Limiter dan Circuit Breaker

Untuk mencegah kejadian berulang saat memindai target di balik proteksi edge, integrasikan golang.org/x/time/rate untuk token bucket rate limiting serta circuit breaker berbasis error rate.

package main

import (
    "context"
    "net/http"
    "sync/atomic"
    "time"
    "golang.org/x/time/rate"
)

type SafeScanner struct {
    client  *http.Client
    limiter *rate.Limiter
    blocked atomic.Bool
}

func (s *SafeScanner) Fetch(ctx context.Context, url string) (*http.Response, error) {
    if s.blocked.Load() {
        return nil, http.ErrHandlerTimeout
    }

    if err := s.limiter.Wait(ctx); err != nil {
        return nil, err
    }

    req, _ := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    resp, err := s.client.Do(req)

    if err != nil {
        return nil, err
    }

    // Circuit breaker darurat saat mendeteksi blokir edge
    if resp.StatusCode == http.StatusTooManyRequests || resp.StatusCode == http.StatusForbidden {
        s.blocked.Store(true)
        go s.cooldownCircuit(30 * time.Second)
    }

    return resp, nil
}

func (s *SafeScanner) cooldownCircuit(d time.Duration) {
    time.Sleep(d)
    s.blocked.Store(false)
}
Catatan: Implementasi di atas mencegah scanner membombardir target saat IP mulai dibatasi, memberikan jeda waktu pendinginan bagi edge router target.