Implementasi Jejak Audit Best Effort pada CMS KotaPortal
Tabel audit_logs append-only ditulis setelah mutasi sukses, tahan penghapusan akun, dan tidak pernah menggagalkan request bisnis.
Ringkasan
Audit trail di CMS itu penting karena cuma andelin kolom updated_at, jadi nggak jelas siapa yang ubah data. Solusinya bikin tabel audit_logs append-only yang dicatet tiap kali mutasi sukses, tanpa foreign key ke tabel user biar jejaknya tetap kebaca walau akun dihapus. Best effort banget, gagal nulis log cuma jadi warning, nggak bikin aplikasi tumbang.
Tinjauan keamanan internal bertanya siapa yang mengubah catatan tertentu di sistem manajemen konten. Satu-satunya petunjuk yang tersedia di basis data hanyalah kolom updated_at. Kondisi ini memicu tebakan buta mengenai identitas pelaku perubahan data, yang tentu saja tidak dapat diterima dalam standar kepatuhan keamanan modern.
Asumsi awal menyatakan bahwa log aplikasi atau binlog basis data sudah cukup untuk melacak perubahan tersebut secara retrospektif. Namun, evaluasi lebih lanjut menunjukkan kedua sumber tersebut tidak menyimpan identitas aktor dan konteks bisnis yang spesifik. Binlog hanya mencatat perubahan tingkat rendah, sedangkan log aplikasi sering kali tercampur dengan informasi debug. Solusi yang diterapkan adalah membuat tabel audit_logs khusus yang bersifat append-only. Tabel ini ditulis oleh setiap layanan admin tepat setelah mutasi data berhasil dilakukan.
Jejak audit merupakan kasus penggunaan pencatatan keamanan yang berbeda dari log operasional biasa. Memisahkannya dari log umum adalah langkah yang paling masuk akal untuk menjaga integritas data dan mencegah manipulasi [3]. Pendekatan jejak audit best effort memastikan kegagalan pencatatan tidak menghentikan operasi utama aplikasi, sambil tetap memberikan visibilitas yang memadai.
Struktur Tabel dan Implementasi Pencatatan
Migrasi basis data membuat tabel audit_logs dengan spesifikasi yang ketat. Kolom id menggunakan tipe BIGINT UNSIGNED AUTO_INCREMENT sebagai primary key. Kolom user_id dibiarkan bernilai NULL untuk mengakomodasi aksi sistem, disertai kolom email dan bidang untuk identifikasi manual jika diperlukan. Kolom action menggunakan VARCHAR(64) yang hanya menerima nilai create, update, delete, restore, atau status_change. Kolom lain meliputi resource_type, resource_id, detail bertipe TEXT, ip bertipe VARCHAR(45), dan created_at bertipe TIMESTAMP. Indeks ditambahkan pada created_at, user_id, dan gabungan resource_type dengan resource_id untuk mempercepat pencarian forensik.
Desain ini sengaja tidak menyertakan kolom updated_at dan tidak memiliki foreign key ke tabel pengguna. Hal ini memastikan baris data audit tetap bertahan meskipun akun pengguna dihapus dari sistem. Sifat imutabel ini menjadi fondasi kepercayaan terhadap data historis yang dikumpulkan.
Paket Logger bersama menggunakan metode Log yang nil-safe untuk menangani pencatatan di CMS KotaPortal. Kegagalan penyisipan dicatat sebagai peringatan di log sistem dan tidak pernah menggagalkan permintaan bisnis utama pengguna. Metode ini dipanggil hanya setelah mutasi berhasil dikomit ke basis data. Dengan demikian, sistem jejak audit best effort tetap andal tanpa menjadi titik kegagalan tunggal yang mengganggu pengalaman pengguna.
Izin audit:read diberikan secara eksplisit hanya kepada peran admin dan superadmin. Endpoint admin GET menampilkan data lintas kategori dengan dukungan filter pada action, resource_type, user_id, dan paginasi untuk menangani volume data yang besar. Akses data di bahasa pemrograman Go menggunakan ExecContext untuk penyisipan aman dan QueryContext untuk pengambilan daftar dengan parameter yang terikat [5].
Dokumentasi resmi menekankan pentingnya tidak mencatat terlalu banyak atau terlalu sedikit informasi sensitif [3]. Mencatat setiap kueri baca akan membebani basis data, sementara hanya mencatat perubahan status kehilangan konteks kritis. Oleh karena itu, filter diterapkan hanya pada mutasi data yang mengubah keadaan sistem. Resiko residual dari celah audit yang sunyi perlu dipantau secara terpisah, namun ini merupakan pertukaran yang diterima untuk menjaga kinerja sistem produksi.
Implementasi di sisi server memastikan bahwa konteks permintaan diteruskan dengan benar. Penggunaan ExecContext memungkinkan pembatalan operasi jika koneksi terputus, sementara QueryContext menangani pengambilan data dengan batas waktu yang ketat. Parameter kueri selalu divalidasi sebelum dieksekusi untuk mencegah injeksi SQL. Pendekatan ini menjamin bahwa antarmuka administratif tetap responsif meskipun beban pencatatan audit meningkat seiring pertumbuhan data.
Kebijakan akses mengikuti pola peran yang sudah ada: izin audit:read diberikan ke peran admin dan superadmin lewat migrasi, bukan lewat kode aplikasi. Endpoint daftar menampilkan jejak lintas kategori dengan filter action, resource_type, dan user_id, plus paginasi standar. Kombinasi indeks (resource_type, resource_id) membuat pertanyaan "semua perubahan pada entitas ini" tetap murah meski tabel tumbuh.
Keputusan desain yang paling sering diperdebatkan adalah menghapus foreign key ke tabel pengguna. Normalisasi menghendaki relasi; jejak audit menghendaki kebalikan. Baris yang menunjuk akun yang sudah dihapus tetap harus terbaca, karena justru pada masa itulah pertanyaan "siapa yang melakukan" paling sering muncul. Kolom email yang disalin saat penulisan menjadi cadangan keterbacaan ketika user_id menunjuk ke yang sudah tidak ada. Upgrade path-nya sederhana bila kebijakan retensi berubah: tambahkan constraint dengan ON DELETE SET NULL di migrasi berikutnya.
Yang belum tuntas justru perlu dinyatakan eksplisit: best-effort berarti ada jendela ketika mutasi sukses tetapi jejaknya gagal ditulis. Kegagalan itu saat ini hanya muncul sebagai peringatan di log aplikasi. Langkah lanjutan yang masuk akal adalah metrik pada penghitung kegagalan insert dan pemberitahuan saat angkanya naik, sehingga celah senyap berubah menjadi sinyal yang terlihat.