NAH Lifecycle — Peta Semua Lifecycle jarsWAF

NAH = Not Another Hack (proyek WAF).
Lifecycle ada di mana-mana: proxy, agent, controller, eBPF, log, config, memory, rule engine, blocklist, rate limiter, circuit breaker, health checker, auto-remediation.
Dokumen ini peta jalan semua lifecycle dan hubungannya.


1. Agent Startup Lifecycle

Source: src/agent/mod.rsrun_agent()

main.rs
  └─ Cli parse (agent vs controller)
      └─ run_agent(config, controller_url, token)
           │
           ├─ 1.1 load_config → Config struct
           ├─ 1.2 rate_limiter_cleanup() → background task
           ├─ 1.3 SQLite init → (jika mode=sqlite/clickhouse)
           ├─ 1.4 MPSC channel (log_tx, log_rx) — buffer 10000
           ├─ 1.5 spawn log_worker(log_rx, cfg)
           ├─ 1.6 spawn config_reloader (poll 2s)
           ├─ 1.7 load blocklist from file → DashMap
           ├─ 1.8 build AppState { config, log_tx, blocklist }
           ├─ 1.9 jika controller_url ada:
           │    ├─ spawn metrics_collector
           │    ├─ spawn config_sync_websocket
           │    └─ status → "distributed agent mode"
           ├─ 1.10 spawn blocklist_sync
           ├─ 1.11 start_memory_cleanup() — 30 min periodic
           ├─ 1.12 spawn metrics_pusher (jika push_url ada)
           └─ 1.13 server::run_server(cfg, state) → Axum

FAILURE MODE:
  - Config gagal load → exit (expect)
  - SQLite gagal init → error log, lanjut tanpa SQLite
  - Blocklist file gak ada → DashMap kosong, lanjut
  - Controller unreachable → retry di background tasks

2. Pingora Request Lifecycle (10 Phase)

Source: src/proxy_engine.rs

Client → pingora::Server
    │
    ├─ 0. new_ctx()         ← JarsWafCtx per request
    ├─ 1. request_filter()  ← 12 sub-fase (lihat doc existing)
    ├─ 2. upstream_peer()   ← round-robin + backpressure
    ├─ 3. upstream_request_filter() ← inject X-Forwarded-For
    ├─ 4. request_body_filter() ← streaming + WAF body check
    ├─ 5. response_body_filter() ← inspect response
    ├─ 6. response_filter() ← modify response
    ├─ 7. logging()         ← decrement counters + kirim log
    ├─ 8. fail_to_proxy()   ← error handler upstream
    └─ 9. error_while_proxy() ← error handler proxy

Lihat dokumentasi lengkap: jarswaf-lifecycle-architecture


3. Controller Startup Lifecycle

Source: src/controller/mod.rsrun_controller()

controller --port 8080
  │
  ├─ 3.1 Generate admin_token (UUID) jika belum ada → simpan ke config
  ├─ 3.2 load_config → Config
  ├─ 3.3 SQLite init (request_log table + index)
  ├─ 3.4 Init broadcast channels:
  │    ├─ tx/rx (logs — 10000 buffer)
  │    ├─ config_tx/rx (config changes — 100 buffer)
  │    └─ block_tx/rx (block commands — 1000 buffer)
  ├─ 3.5 Load stats from SQLite (24h window)
  ├─ 3.6 Build ControllerState
  ├─ 3.7 Build Axum router:
  │    ├─ /api/v1/agents/* (register, metrics, list)
  │    ├─ /api/v1/logs/* (stream, query, export, clear)
  │    ├─ /api/v1/config/* (get, update, history, rollback)
  │    ├─ /api/v1/vhosts, /custom-rules, /allowlists, /blacklists
  │    ├─ /api/v1/stats, /reputation/blocklist, /threat-intel
  │    ├─ /api/v1/ssl/* (certificates, renew)
  │    ├─ /ws/dashboard, /ws/agent
  │    └─ Svelte dashboard (ServeDir fallback)
  ├─ 3.8 CORS → any origin (dev mode)
  ├─ 3.9 TcpListener → 0.0.0.0:8080
  └─ 3.10 axum::serve → forever

FAILURE MODE:
  - Port 8080 terpakai → crash (expect)
  - SQLite korup → crash di init_sqlite_db
  - Dashboard dist/ gak ada → 404 fallback

4. Log Pipeline Lifecycle

Source: src/logging.rs

WafLogEntry (dari proxy_engine)
  → log_tx.send(entry)  ─async MPSC channel (10k buffer)─→  log_worker(rx)

3 mode:
                          ┌─ file: write JSONL + rotate per 1MB/1000 line
  log_worker(rx, cfg) ───├─ remote: file + batch push ke controller (interval + size)
                          └─ sqlite/clickhouse: batch insert via persistent conn

PERSISTENCE FLOW:
  File:     entry → writeln → rotate_if_needed (1000 line check)
  SQLite:   batch → Vec<WafLogEntry> → tiap 1 detik → spawn_blocking → INSERT tx
  Remote:   batch → Vec<WafLogEntry> → tiap push_interval → HTTP POST ke controller
  ClickHouse: same as SQLite (via HTTP POST, bukan native protocol)

ROTATION:
  max_log_size_mb reach → jarswaf.log → jarswaf.log.1 → ... → jarswaf.log.N (delete oldest)

FAILURE MODE:
  - Channel overflow (10k buffer penuh) → oldest drop (tokio MPSC behavior)
  - SQLite write gagal → error log, batch skip
  - Controller unreachable → retry next interval, logs safe di local

5. Config Reloader Lifecycle

Source: src/agent/mod.rs baris 51-85

Background task (spawn loop 2s):
  ├─ 5.1 fs::metadata(config_path).modified()
  ├─ 5.2 Bandingkan last_modified
  ├─ 5.3 Jika berubah:
  │    ├─ config::load_config(path)
  │    ├─ GLOBAL_CONFIG.store(Arc::new(new_cfg))  ← ArcSwap atomic
  │    └─ info log
  └─ 5.4 Jika error → error log, config lama tetap dipakai

Atomic swap → zero race window:
  - proxy_engine membaca GLOBAL_CONFIG.load() tiap request
  - Config baru langsung berlaku untuk request berikutnya
  - Tidak perlu restart process

FAILURE MODE:
  - Config baru corrupt → config lama tetap aktif
  - File dihapus → error terus tiap 2s, config lama dipakai

6. Memory Cleanup Lifecycle

Source: src/proxy_engine.rsstart_memory_cleanup()

Background task (tiap 30 menit):
  ├─ ACTIVE_CONNECTIONS.retain(|_, _| false)     → reset
  ├─ SESSION_FINGERPRINTS.retain(|_, _| false)    → reset
  └─ BACKEND_ACTIVE_REQUESTS.retain(|_, _| false) → reset

Background tasks (tiap 60 detik):
  ├─ BLOCKED_IPS → retain jika < 5 menit idle
  ├─ RATE_LIMITER → retain jika < 5 menit idle
  └─ IP_REPUTATION → LRU auto-evict (quick_cache)

BOUNDED STRUCTURES:
  - BLOCKLIST_MAX_ENTRIES = 100.000 → retain order trim
  - IP_REPUTATION = 10.000 → quick_cache LRU
  - body_buffer → clear + shrink_to_fit per request

FAILURE MODE:
  - Cleanup lambat → DashMap tumbuh unbounded (memory leak gradual)
  - HR timer drift → periodic task bisa melebar

7. eBPF XDP Lifecycle

Source: src/xdp.rs

XdpManager::new()
  ├─ 7.1 Load Ebpf::load_file("/app/jarswaf-ebpf")
  │    └─ fallback: "target/bpfel-unknown-none/release/jarswaf-ebpf"
  ├─ 7.2 Jika gagal → warn + None, eBPF disabled
  └─ 7.3 Self { bpf: Option<Ebpf> }

.attach(interface)
  ├─ 7.4 bpf.as_mut()? → program_mut("jarswaf_ebpf")
  ├─ 7.5 program.load() → XDP program
  └─ 7.6 program.attach(interface, XdpMode::default())

.block_ip(ip) — dipanggil oleh auto-remediation tier 0→1
  ├─ 7.7 bpf.map_mut("BLOCKLIST") → HashMap<u32, u8>
  ├─ 7.8 INSERT ip_u32 → 1
  └─ 7.9 Kernel drop paket dari IP ini SEBELUM TCP/IP stack

NOT IMPLEMENTED:
  - Tidak ada detach() — XDP permanen attach
  - Tidak ada remove_ip() — IP tetap di blocklist kernel sampai process mati
  - Tidak ada graceful shutdown

FAILURE MODE:
  - Kernel < 5.8 / CONFIG_BPF=n → disabled silently
  - Interface gak ada → error di attach
  - eBPF bytecode mismatch → crash di program_mut

8. Rule Engine Lifecycle

Source: src/rules.rs, src/rules/{uri,headers,body}.rs

INIT (compile time):
  static RULES: &[Rule] = [...]  // array literal
  ├─ id, name, phase, action, severity, check: fn()
  └─ 3 fase: Uri, Headers, Body

CUSTOM RULES (runtime):
  config.toml → [[custom_rules]]
  ├─ condition_type: path/header/cookie/body/query/method
  ├─ operator: contains/prefix/suffix/regex/equals
  └─ action: block/log

PER-REQUEST EXECUTION:
  request_filter() → rule_engine.check_request(path, query, headers)
    ├─ Normalize: URL decode → HTML entity → NFKC → lowercase
    ├─ AST tokenizer (SQLi/XSS/CMD injection — bukan regex mentah)
    └─ Match pattern → semantic tree

  request_body_filter() → rule_engine.check_request(...+ body)
    └─ Sama, tapi body (streaming, per chunk)

RELOAD METHOD:
  - Config change → GLOBAL_CONFIG.store() → next request baca config baru
  - Custom rules dari config → hot reload, tidak perlu restart

FAILURE MODE:
  - Regex bomb → WAF_SEMAPHORE (4 concurrent) → lewati inspeksi jika penuh
  - AST parser panic → catch di phase → allow (bukan block)

9. Agent-Controller WebSocket Lifecycle

Source: src/agent/websocket.rs, src/controller/websocket.rs

AGENT STARTUP:
  ├─ 9.1 HTTP POST /api/v1/agents/register → token + metadata
  ├─ 9.2 Receive agent_id dari controller
  └─ 9.3 WebSocket connect /ws/agent → persistent

CONTROLLER SIDE:
  ├─ 9.4 Agent terdaftar di agent_registry HashMap
  ├─ 9.5 Metrics masuk via HTTP POST /api/v1/agents/metrics
  └─ 9.6 Block command via broadcast channel → agent WS

AGENT SIDE:
  ├─ 9.7 Terima config update dari controller
  ├─ 9.8 Terima block/unblock command
  ├─ 9.9 Kirim metrics periodik ke controller
  └─ 9.10 Reconnect logic (backoff)

FAILURE MODE:
  - WS disconnect → backoff reconnect + log
  - Token invalid → 401 → stop reconnect
  - Controller down → agent lanjut standalone

10. Blocklist Lifecycle

Source: src/agent/blocklist.rs

FILE → MEMORY — SYNC — XDP
  ├─ 10.1 Load dari file JSON (blocklist.json) startup
  ├─ 10.2 Store di DashMap<IpAddr, ()> — shared Arc
  ├─ 10.3 Update dari 3 source:
  │    ├─ WAF rule trigger → auto-block
  │    ├─ Controller push via WS → block/unblock
  │    └─ Sync dengan controller periodik
  ├─ 10.4 Jika IP mencapai threshold → trigger XDP block
  └─ 10.5 Auto-expire: block temporary (Tier 1-4 lifetime)

FILE SYNC:
  - save_blocklist_to_file: atomic write (tmp → rename)
  - load_blocklist_from_file: startup + periodic reload

FAILURE MODE:
  - File corrupt → HashSet kosong
  - Disk full → save gagal → error log
  - XDP map full? → cap terbatas di eBPF (biasanya 16k)

11. Rate Limiter Lifecycle

Source: src/rules.rs (internal)

INIT:
  Static DashMap<IpAddr, TokenBucket>
  TokenBucket { tokens: f64, last_refill: Instant }

PER-REQUEST:
  ├─ 11.1 Cari rate limit policy (path glob match)
  ├─ 11.2 Refill token berdasarkan waktu
  ├─ 11.3 Jika token ≥ cost → kurangi, allow
  └─ 11.4 Jika token < cost → 429 + reputation +5

CLEANUP (60 detik):
  Retain entries idle > 5 menit

DISTRIBUTED:
  Opsional via Redis (set [redis] di config)
  └─ Token bucket sync antar node

REPUTATION INTEGRATION:
  Rate limit hit → reputation_score +5
  Reputation ≥ 50 → auto-blocklist + bot challenge trigger

12. Circuit Breaker Lifecycle

Source: src/proxy_engine.rsBACKEND_FAILURE_COUNTS

STRUCTURE:
  DashMap<String {backend_url}, AtomicUsize>

FLOW:
  ├─ 12.1 Backend response error → counter++
  ├─ 12.2 Jika counter ≥ CIRCUIT_BREAKER_THRESHOLD (5):
  │    ├─ Backend marked "tripped"
  │    └─ Excluded dari round-robin selection
  └─ 12.3 Health checker sukses → counter reset 0 → backend kembali

  BACKEND_ACTIVE_REQUESTS:
    - Increment di upstream_peer()
    - Decrement di logging() post-request
    - Jika > max_concurrent → 503 "Backend Overloaded"

13. Health Checker Lifecycle

Source: src/agent/server.rs (implied)

BACKGROUND (15 detik):
  ├─ 13.1 Iterasi semua backend registered
  ├─ 13.2 TCP connect (timeout 5s)
  ├─ 13.3 Jika sukses → backend healthy
  └─ 13.4 Jika gagal → backend unhealthy

LOAD BALANCER:
  - Filter hanya healthy backend
  - Jika semua unhealthy → fallback ke first backend
  - Round-robin counter (atomic increment)

14. Auto-Remediation Escalation Lifecycle

Source: src/agent/blocklist.rs (implied from jarswaf-lifecycle-architecture doc)

record_block(ip) — dipanggil setiap block decision
  └─ Sliding window 5 menit
      ├─ Tier 0 (baseline): aplikasi block saja
      ├─ Tier 0→1 (5 block/5m): 60 detik + eBPF XDP block
      ├─ Tier 1→2: 300 detik
      ├─ Tier 2→3: 1800 detik
      └─ Tier 3→4: 86400 detik (24 jam)

  Semua tier temporary — auto-expire via cleanup 60 detik.

  XDP trigger path:
  auto-remediation → blocklist.insert(ip) → XDP::block_ip(ip)
  → kernel drop at XDP hook → zero CPU untuk TCP handshake

Matriks Koneksi Antar Lifecycle

LifecycleDipicu olehMemicuData shared
Agent Startupmain.rsSemua lifecycle di proses yang samaConfig Arc
Controller Startupmain.rsWS, logs, agent registryControllerState, broadcast
Request PhasePingora event loopLog pipeline, Rule engine, Auto-remediationJarsWafCtx, GLOBAL_CONFIG
Log Pipelinerequest_filter → logging()WafLogEntry via MPSC
Config ReloaderTimer (2s)GLOBAL_CONFIG.store()ArcSwap
Memory CleanupTimer (30m/60s)DashMap retain
eBPF XDPAuto-remediation tier 0→1XDP BLOCKLIST map
Rule EngineRequest phaseGLOBAL_CONFIG
Agent-Controller WSAgent startupWebSocket, ControllerState
BlocklistFile load + rule trigger + WS pusheBPF XDPDashMap + file + XDP
Rate LimiterTimer (60s)Reputation scoreDashMap + Redis
Circuit BreakerBackend errorBackend exclusionDashMap
Health CheckerTimer (15s)Load balancer state
Auto-RemediationRule matcheBPF block + escalationSliding window

Debugging Checklist: Kalau “Ada di Mana-mana”

GejalaCek Lifecycle
Config berubah tapi gak ngefekConfig Reloader (5) — cek mtime, path
Memory naek terusMemory Cleanup (6) — DashMap leak
Log gak sampai controllerLog Pipeline (4) — controller_url, batch size
XDP gak jalaneBPF (7) — kernel version, .o path
Backend sering 503Circuit Breaker (12) + Health Checker (13)
Agent gak terdaftarAgent-Controller WS (9) — token, backoff
Block IP gak hilang2eBPF (7) — remove_ip belum implement
Rate limit gak konsistenRate Limiter (11) — distributed sync, Redis

Koneksi Vault