Anatomi Masalah: Karakteristik Traffic Telemetri Publik
Gateway telemetri publik—seperti proxy download paket ala Scarf, beacon analitik, atau endpoint open-telemetry—memiliki sifat write-heavy dan fire-and-forget. Ciri ini menjadikannya target empuk bot scraping, perulangan skrip CI/CD yang salah konfigurasi, dan injeksi data sintetis.
Dua dampak utama yang timbul tanpa proteksi ketat:
- Metrik Terdistorsi: Data analytics terkontaminasi oleh hit non-manusia yang menduplikasi metrik konsumsi rilis software atau package.
- Lonjakan Biaya Downstream: Message broker (Kafka, RabbitMQ) dan cold storage (ClickHouse, BigQuery) memproses sampah yang meningkatkan tagihan komputasi serta storage.
Deteksi Traffic Sintetis Tanpa Menambah Latensi Ingest
Gateway ingest tidak boleh melakukan operasi I/O pemblokir seperti query database relasional atau pemanggilan API reputasi pihak ketiga secara sinkron. Latensi gateway harus berada di rentang sub-10 milidetik.
1. Filter Header di Reverse Proxy Tier
Singkirkan bot dasar pada layer Nginx/Envoy atau edge compute sebelum request menyentuh runtime aplikasi. Validasi keberadaan User-Agent, reject format URI anomali, dan eliminasi metode HTTP selain POST (atau GET untuk tracking pixel).
2. Heuristik Payload Lokal
Verifikasi header tanpa deserialisasi menyeluruh jika request berukuran besar. Lakukan verifikasi payload size guard terlebih dahulu: batasi ukuran body request maksimal (misalnya 16 KB - 64 KB). Payload telemetri yang melebihi batas wajar langsung di-drop dengan status HTTP 413 Payload Too Large.
Rate Limiting: Token Bucket Partisi IP & User-Agent
Rate limiting berbasis alamat IP tunggal sering memicu false positive ketika klien berada di belakang NAT korporat, GitHub Actions runner, atau proxy cloud. Gunakan partisi komposit:
Key = SHA256(IP_Address + ":" + User_Agent)Pendekatan ini mengisolasi scraper otomatis berskrip unik tanpa memblokir developer lain yang berbagi IP publik yang sama. Token bucket menyediakan toleransi burst sesaat sambil mempertahankan rata-rata rate limit throughput yang ketat.
Validasi Skema & Scrubbing PII Downstream
Data telemetri sering kali secara tidak sengaja membawa kredensial pengembang (token GitHub, API key, JWT) dari environment variabel klien, atau data pribadi (PII) seperti username lokal dan path direktori host. Gateway bertindak sebagai batas sanitasi (sanitization boundary).
- Validasi Tipe Ketat: Validasi tipe primitif setiap field (string, number, boolean). Tolak key tak dikenal (unknown fields) untuk mencegah injeksi arbitrary JSON.
- Regex Token Masking: Deteksi format token umum (misalnya pattern
ghp_[A-Za-z0-9]{36}atau Bearer token) dan gantikan dengan string[REDACTED]sebelum data dipublikasikan ke message queue downstream.
Implementasi: Middleware Validasi, Rate Limit, & Scrubbing
Berikut adalah implementasi proteksi telemetri mandiri menggunakan standar pustaka Node.js tanpa dependensi pihak ketiga.
const crypto = require("node:crypto");
const assert = require("node:assert");
class TelemetryGuard {
constructor({ maxTokens = 10, refillRatePerSec = 2, secret = "default-secret" } = {}) {
this.maxTokens = maxTokens;
this.refillRatePerSec = refillRatePerSec;
this.secret = secret;
// ponytail: in-memory map cocok untuk single-instance. Upgrade ke Redis jika multi-replica.
this.buckets = new Map();
}
getBucketKey(ip, userAgent) {
return crypto.createHash("sha256").update(`${ip}:${userAgent}`).digest("hex");
}
isRateLimited(ip, userAgent) {
const key = this.getBucketKey(ip, userAgent);
const now = Date.now();
let bucket = this.buckets.get(key);
if (!bucket) {
bucket = { tokens: this.maxTokens, lastRefill: now };
this.buckets.set(key, bucket);
} else {
const elapsedSec = (now - bucket.lastRefill) / 1000;
bucket.tokens = Math.min(this.maxTokens, bucket.tokens + elapsedSec * this.refillRatePerSec);
bucket.lastRefill = now;
}
if (bucket.tokens >= 1) {
bucket.tokens -= 1;
return false;
}
return true;
}
verifyHmacSignature(rawBody, signatureHeader) {
if (!signatureHeader) return false;
const computed = crypto.createHmac("sha256", this.secret).update(rawBody).digest("hex");
const source = Buffer.from(signatureHeader, "utf-8");
const target = Buffer.from(computed, "utf-8");
return source.length === target.length && crypto.timingSafeEqual(source, target);
}
validateAndSanitize(payload) {
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
throw new Error("INVALID_PAYLOAD_STRUCTURE");
}
const { event, version, metadata } = payload;
if (typeof event !== "string" || typeof version !== "string") {
throw new Error("TYPE_MISMATCH_CORE_FIELDS");
}
const sanitizedMetadata = {};
if (metadata && typeof metadata === "object") {
for (const [k, v] of Object.entries(metadata)) {
if (typeof v === "string") {
// Mask token GitHub atau credential string umum
sanitizedMetadata[k] = v.replace(/ghp_[a-zA-Z0-9]{36}/g, "[REDACTED_SECRET]");
} else if (typeof v === "number" || typeof v === "boolean") {
sanitizedMetadata[k] = v;
}
}
}
return {
event,
version,
metadata: sanitizedMetadata,
ingestedAt: Date.now()
};
}
}
// Runnable assertion test
function runSelfTest() {
const guard = new TelemetryGuard({ maxTokens: 2, refillRatePerSec: 0, secret: "secret123" });
const ip = "192.168.1.100";
const ua = "Scarf-Collector/1.0";
// 1. Uji Rate Limiter
assert.strictEqual(guard.isRateLimited(ip, ua), false, "Hit 1 harus diizinkan");
assert.strictEqual(guard.isRateLimited(ip, ua), false, "Hit 2 harus diizinkan");
assert.strictEqual(guard.isRateLimited(ip, ua), true, "Hit 3 harus di-rate-limit");
// 2. Uji Verifikasi Signature HMAC
const rawBody = JSON.stringify({ ping: true });
const validSig = crypto.createHmac("sha256", "secret123").update(rawBody).digest("hex");
assert.strictEqual(guard.verifyHmacSignature(rawBody, validSig), true, "Signature valid harus lolos");
assert.strictEqual(guard.verifyHmacSignature(rawBody, "invalidsig"), false, "Signature salah harus ditolak");
// 3. Uji Validasi Skema dan Scrubbing PII
const dirtyPayload = {
event: "package_download",
version: "2.1.0",
metadata: {
ciToken: "ghp_1234567890abcdef1234567890abcdef1234",
nodeVersion: 18
}
};
const sanitized = guard.validateAndSanitize(dirtyPayload);
assert.strictEqual(sanitized.metadata.ciToken, "[REDACTED_SECRET]", "Token harus disanitasi");
assert.strictEqual(sanitized.metadata.nodeVersion, 18, "Tipe data angka harus dipertahankan");
}
runSelfTest();
Skipped: Redis-based token storage dan distributed LRU cache.
Tambahkan saat: Ingest gateway di-deploy multi-node di belakang L4/L7 load balancer tanpa session affinity.
Strategi Respons: Drop Silent vs HTTP 429
Memilih kode HTTP status respon menentukan efektivitas penanganan abuse:
- HTTP 429 (Too Many Requests): Tepat untuk integrasi SDK internal resmi agar SDK melakukan exponential backoff.
- Drop Silent (HTTP 202 Accepted): Tepat untuk scraping agresif atau malicious floods. Mengembalikan kode 202 tanpa memasukkan payload ke downstream queue memutus feedback loop penyerang, membingungkan proses profiling bot tanpa mengorbankan resource sistem backend.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!