Lewati ke konten utama

WhatsApp Service — Keamanan & Hardening

Dokumen ini merangkum seluruh kontrol keamanan yang telah diimplementasikan pada whatsapp_service (port 8091), mencakup hasil audit keamanan 2026-05-25–2026-06-05.


Keamanan & Hardening

Semua item di bawah telah diimplementasikan dan diverifikasi per audit keamanan 2026-05-29–2026-06-05.

Webhook Security

  • Signature Meta wajib diverifikasi secara eksplisit: validateMetaSignature menolak setiap request apabila WHATSAPP_APP_SECRET kosong — tidak ada fallback diam-diam ke mode bypass. Pengembang yang membutuhkan bypass untuk lingkungan lokal wajib menyetel WEBHOOK_SIGNATURE_BYPASS=true secara eksplisit di file env development. Dengan demikian, misconfiguration APP_ENV di host production-like tidak bisa secara tidak sengaja menonaktifkan verifikasi HMAC SHA-256.

  • Hub.Challenge tolak token kosong: verifyWebhookChallenge menolak permintaan handshake dari Meta apabila WHATSAPP_VERIFY_TOKEN (expected token) belum dikonfigurasi. Tidak ada path di mana token kosong diterima sebagai valid — verifikasi tidak bisa di-bypass hanya dengan membiarkan konfigurasi token kosong.

Konfigurasi & Environment

  • Env systemd tidak bisa ditimpa oleh file .env: whatsapp_service menggunakan godotenv.Load (bukan godotenv.Overload). Ini memastikan variabel environment yang sudah di-set oleh directive Environment= di unit systemd tidak dapat ditimpa oleh isi file .env.production. Apabila seorang attacker berhasil memodifikasi file .env di disk tetapi tidak memiliki akses ke systemd unit file, nilai-nilai kritikal (API key, webhook secret, APP_ENV) yang dikelola oleh systemd tetap berlaku dan tidak dapat dioverride.

  • Fail-fast pada konfigurasi kritis kosong: validateConfig() menolak startup jika INTERNAL_NOTIFICATION_API_KEY, WHATSAPP_API_SECRET, atau WHATSAPP_VERIFY_TOKEN kosong saat APP_ENV=production. Misconfiguration terdeteksi di waktu startup, bukan saat runtime.

Auth & Authorization

  • Internal API key fail-closed: Middleware withInternalKey menggunakan subtle.ConstantTimeCompare untuk membandingkan API key. Jika INTERNAL_NOTIFICATION_API_KEY kosong, semua request ke endpoint internal ditolak — tidak ada fail-open. Constant-time compare mencegah timing side-channel attack pada shared-secret auth.

  • Template whitelist via env: Pengiriman template WhatsApp dibatasi oleh WHATSAPP_ALLOWED_TEMPLATES — service tidak bisa dipakai untuk mengirim template Meta arbitrary di luar daftar yang diizinkan oleh operator.

Keamanan Jaringan & Transport

  • Binding loopback only: Service hanya listen di 127.0.0.1:8091, tidak ter-expose langsung ke internet. Traffic publik masuk melalui nginx yang kemudian proxy ke service.

  • HTTP server timeout lengkap: http.Server dikonfigurasi dengan ReadHeaderTimeout: 5s (mitigasi Slowloris), ReadTimeout: 30s, WriteTimeout: 30s, IdleTimeout: 120s. Tidak ada konfigurasi server tanpa timeout yang berisiko terhadap slow-client atau connection exhaustion.

Observability & PII Protection

  • Phone number di-mask di semua log: Fungsi maskPhone menyamarkan nomor telepon sebelum ditulis ke log stream (contoh: +628***68). Data PII merchant tidak pernah muncul dalam bentuk penuh di log event whatsapp_send maupun whatsapp_webhook.

  • Event log terstruktur dengan taxonomy error: classifyWAError mengklasifikasikan 10 kelas error secara konsisten (ok, meta_auth, meta_rate_limit, meta_template_invalid, meta_4xx, meta_5xx, network, network_timeout, unknown). Taxonomy ini memungkinkan monitoring dan alerting yang tepat tanpa perlu expose detail internal ke caller.

Operational Hardening

  • Graceful shutdown 15 detik: Service menangani SIGTERM/SIGINT dengan memberikan window 15 detik bagi in-flight HTTP request untuk selesai sebelum proses berhenti. Hal ini krusial untuk mencegah half-write pada tabel whatsapp_send_attempts — tanpa graceful shutdown, deploy bisa mengakibatkan baris audit log tersimpan dengan status tidak konsisten karena proses dipaksa berhenti di tengah call ke Meta Cloud API.

  • Dockerfile distroless nonroot: Image produksi menggunakan gcr.io/distroless/static-debian12:nonroot sebagai base image stage 2. Binary berjalan sebagai user nonroot tanpa shell, mengurangi attack surface secara signifikan jika terjadi container escape.

Test Coverage Keamanan

  • 343 baris unit test keamanan live: Coverage mencakup security_test.go (validateConfig production guards + middleware fail-closed + verifyWebhookChallenge), telemetry_test.go (maskPhone + classifyWAError), dan webhook_security_test.go (8 kasus HMAC SHA-256 verify termasuk signature valid, invalid, kosong, dan bypass flag). Seluruh test pass di setiap build.

Dokumen ini merupakan bagian dari seri security documentation Kesles Merchant. Untuk detail arsitektur service lihat architecture.md. Untuk prosedur deploy dan rotasi env lihat deploy-runbook.md.


Penguatan Tambahan (2026-06-08)

  • Idempotency mutual exclusion sebelum eksekusi: Store.TryLock() menggunakan Redis SetNX (TTL 30s) untuk acquire distributed lock sebelum mengirim pesan — bukan setelah. Request kedua dengan idempotency key yang sama saat lock aktif mendapat 409 Conflict. Duplicate send tidak dapat terjadi meski dua goroutine concurrent membaca cache miss bersamaan.

  • UUID format divalidasi sebelum DB query: Handler GET /messages/{uuid} memvalidasi format UUID path parameter dengan isValidUUIDFormat() sebelum query ke database. Input non-UUID tidak pernah mencapai layer SQL — input malformed ditolak 404 di handler level.

  • dist/.env dikecualikan dari git tracking: /dist/.env dan /dist/.env.* ditambahkan secara eksplisit ke .gitignore sebagai belt-and-suspenders — credential di folder build artifact tidak dapat ter-commit secara tidak sengaja.