Skip to content

RBAC Dua Sumbu: Role untuk Aksi, Bidang untuk Data

Adityo Guni Waluyo

Role menentukan aksi, bidang menentukan data. Catatan implementasi scope resolver fail-closed di Go: ModulesFor, ResolveBidang, dan jebakan NULL.

Ringkasan

Portal lintas dinas bikin pusing karena role admin doang nggak cukup buat batasin data per bidang. Akhirnya misahin konsepnya, role buat aksi dan kolom bidang buat filter data biar nggak meledak aturan izinnya. Sistemnya dibikin fail-closed jadi kalau bidang nggak dikenal ya nggak dapet data apa pun, aman deh.

Dua akun admin terbuka di layar saya. Sama-sama pegang role admin, tapi satu dari dinas pariwisata, satu lagi dari dinas olahraga. Permission matrix yang sudah jalan cuma menjawab setengah soal: keduanya boleh edit, boleh publish. Yang tak terjawab: baris data mana yang boleh mereka lihat? Soal ini muncul pas portal kami mulai dipakai lintas bidang di satu sistem.

Tebakan pertama saya: tambah permission per modul di tabel yang sama. Jalur itu cepat meledak. Sepuluh bidang kali lima modul berarti lima puluh baris aturan cuma untuk satu role, dan setiap bidang baru memaksa audit ulang semua kombinasi. Solusi yang akhirnya saya ambil justru memisahkan dua hal yang selama ini menempel: role untuk aksi, bidang untuk data.

Kolom users.bidang dan paket scope

Model Role-Based Access Control bukan ide baru; ia diformalkan Ferraiolo dan Kuhn pada 1992 dan jadi model dominan untuk kontrol akses modern [1]. Yang saya kerjakan di sini cuma menambah sumbu keduanya. Migrasi menambah kolom users.bidang; di MySQL 5.7, ADD COLUMN kolom biasa berjalan ALGORITHM=INPLACE dengan LOCK=NONE di InnoDB, jadi operasi normal tabel tidak diblokir saat migrasi jalan [3].

Isi kolomnya cuma lima nilai: pariwisata, olahraga, budaya, kepemudaan, atau ALL. Paket internal/shared/scope menerjemahkan nilai itu jadi daftar modul kategori yang boleh dikelola. PARIWISATA otomatis mencakup EKONOMI_KREATIF mengikuti domain SPBE dinas kami. Satu detail kecil yang sering dilewatkan: ModulesFor(ALL) mengembalikan salinan, bukan slice aslinya, karena hasil append di Go bisa mengalokasikan array baru dan harus disimpan kembali ke variabel [5]. Tanpa salinan, satu pemanggil yang append bisa meracuni scope pemanggil berikutnya.

func ModulesFor(bidang string) []string {
    if bidang == All {
        out := make([]string, len(allModules))
        copy(out, allModules)
        return out
    }
    return bidangModules[bidang] // nil untuk bidang tak dikenal
}

Fail-closed di tiga titik

OWASP merumuskan prinsipnya tegas: ketika tak ada aturan akses yang cocok, aplikasi tidak boleh netral; ia harus memutuskan menolak atau mengizinkan [2]. Implementasi saya ambil sisi menolak. ModulesFor mengembalikan nilai nil untuk bidang tak dikenal, dan nil berarti tanpa data. Middleware ResolveBidang yang memuat bidang setelah autentikasi JWT juga fail-closed: error database berujung 500, bukan view tanpa filter.

Ada satu pengecualian sadar. String kosong dipetakan ke ALL. Bukan lupa, tapi jembatan kompatibilitas: semua token JWT yang terbit sebelum migrasi tidak membawa informasi bidang, dan semua user memang di-default ALL saat migrasi. Tanpa pemetaan ini, deploy kecil malah mengunci akun lama.

Jebakan NULL di pengumuman global

Tabel announcements dapat kolom bidang dengan kontrak NULL berarti global. Di sini semantik SQL main peran. Nilai NULL tidak pernah true dalam perbandingan apa pun, bahkan terhadap dirinya sendiri, jadi pencariannya wajib lewat IS NULL [4]. Filter daftar pengumuman jadi eksplisit:

WHERE status = 'published'
  AND (bidang IS NULL OR bidang = ?)

Sisa pekerjaannya membosankan dengan cara yang baik. NarrowModules menerima permintaan ?module= eksplisit, mencocokkan dengan scope, dan menjawab daftar kosong tanpa menyentuh database kalau modulnya di luar cakupan. Gerbang kecil yang menutup secara default ternyata lebih berharga daripada matriks izin raksasa: tiga titik keputusan tadi, resolver yang balik nil, middleware yang memetakan kosong ke ALL, dan klausa OR yang dipaksa semantik NULL, menyimpan hampir semua logika keamanannya.

Sumber:

Artikel terkait