Role-Based Access Control menyediakan bahan bakunya; least privilege (hak minimum) adalah disiplin memakainya. Prinsipnya satu kalimat: berikan setiap user hak yang paling sedikit — dan paling sempit cakupannya — yang masih membuat pekerjaannya selesai. Chapter ini menerjemahkan prinsip itu menjadi kebiasaan konkret: satu user per layanan, matriks hak yang bisa diaudit, pembatasan sumber koneksi, serta rotasi dan tinjauan berkala.

Peta materi Chapter ini:

  • Mengapa hak berlebih adalah utang keamanan yang pasti ditagih.
  • Matriks aplikasi → user → role sebagai dokumen rancangan hak akses.
  • authenticationRestrictions untuk membatasi dari mana user boleh masuk.
  • Rutinitas rotasi kredensial dan audit daftar user.

Hak Berlebih Adalah Utang Keamanan #

Setiap hak yang tidak dipakai tetap menjadi pintu yang bisa dimasuki. Jika aplikasi katalog hanya perlu membaca data tetapi user-nya memegang readWrite, maka satu celah injection pada aplikasi itu otomatis menjadi celah penghapusan data. Least privilege memperkecil blast radius: ketika satu kredensial bocor, kerusakan yang bisa dilakukan dibatasi oleh role yang dipegang user itu — bukan oleh niat penyerangnya.

Prinsip ini juga berlaku pada cakupan database. Aplikasi yang hanya bekerja di latihan_toko tidak perlu readWriteAnyDatabase; cukup readWrite yang di-scope ke satu database itu. Dan orang berbeda dari mesin: admin manusia memakai akun pribadi yang bisa diaudit, bukan akun bersama yang bisa dipakai siapa pun.

Matriks Aplikasi ke Role #

Rancangan hak akses yang baik bisa dibaca dari satu tabel. Berikut contoh matriks untuk sebuah toko buku daring dengan empat “pemain” — dua aplikasi, satu tugas terjadwal, satu operator manusia:

PemainUserRoleAlasan
API utamatoko_apireadWrite di latihan_tokoCRUD pesanan, stok, katalog
Dashboard penjualantoko_dashboardread di latihan_tokoGrafik dan laporan tanpa perlu menulis
Tugas pencadangantoko_backupbackup di admin + clusterMonitorMenjalankan mongodump terjadwal
Operator manusiasiti_opsdbOwner di latihan_tokoPerbaikan index dan schema saat insiden

Dua baris pertama bisa kita bangun sungguhan di server latihan:

js
use latihan_uji
// hapus sisa percobaan sebelumnya bila ada
try { db.dropUser("toko_api"); db.dropUser("toko_dashboard") } catch (e) { print("bersih — buat baru") }
db.createUser({
  user: "toko_api",
  pwd: "sandiToko123",
  roles: [{ role: "readWrite", db: "latihan_toko" }]
})
db.createUser({
  user: "toko_dashboard",
  pwd: "sandiDash123",
  roles: [{ role: "read", db: "latihan_toko" }]
})
db.getUsers()

Output yang Dihasilkan (bagian $clusterTime dan operationTime yang berulang diringkas):

text
switched to db latihan_uji

bersih — buat baru

{ ok: 1 }
{ ok: 1 }
{
  users: [
    {
      _id: 'latihan_uji.toko_api',
      userId: UUID('00c1a57a-19d9-41da-9337-d81b2fef572a'),
      user: 'toko_api',
      db: 'latihan_uji',
      roles: [ { role: 'readWrite', db: 'latihan_toko' } ],
      mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
    },
    {
      _id: 'latihan_uji.toko_dashboard',
      userId: UUID('0dd853b9-8f65-4904-9a41-4f00ef7ec4b2'),
      user: 'toko_dashboard',
      db: 'latihan_uji',
      roles: [ { role: 'read', db: 'latihan_toko' } ],
      mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ]
    }
  ],
  ok: 1
}

Perhatikan polanya: dua aplikasi yang bekerja pada database yang sama tetap memegang role berbeda sesuai kebutuhan masing-masing. Ketika dashboard diretas lewat dependency yang bocor, penyerangnya hanya bisa membaca — tidak bisa menghapus stok atau mengubah harga.

Membatasi Dari Mana User Boleh Masuk #

Least privilege juga punya dimensi jaringan. Field authenticationRestrictions pada db.createUser() membatasi alamat IP atau rentang CIDR asal koneksi yang boleh mengautentikasi sebagai user tersebut — kredensial yang dicuri dari luar jaringan aplikasi menjadi tidak bisa dipakai:

js
db.createUser({
  user: "toko_cron",
  pwd: "sandiCron123",
  roles: [{ role: "read", db: "latihan_toko" }],
  authenticationRestrictions: [
    { clientSource: ["127.0.0.1"] }
  ]
})
db.getUser("toko_cron", { showAuthenticationRestrictions: true })

Output yang Dihasilkan (diringkas serupa; daftar actions pada inheritedPrivileges juga dipadatkan karena isinya persis privilege role read yang kita bedah di Chapter Role-Based Access Control):

text
{ ok: 1 }
{
  _id: 'latihan_uji.toko_cron',
  userId: UUID('509ed15b-485f-477a-b476-99c405b2fa8b'),
  user: 'toko_cron',
  db: 'latihan_uji',
  mechanisms: [ 'SCRAM-SHA-1', 'SCRAM-SHA-256' ],
  authenticationRestrictions: [ { clientSource: [ '127.0.0.1' ] } ],
  roles: [ { role: 'read', db: 'latihan_toko' } ],
  inheritedRoles: [ { role: 'read', db: 'latihan_toko' } ],
  inheritedPrivileges: [
    {
      resource: { db: 'latihan_toko', collection: '' },
      actions: [ 'changeStream', 'collStats', 'dbHash', 'dbStats', 'find',
        'killCursors', 'listCollections', 'listIndexes', 'listSearchIndexes',
        'planCacheRead', 'performRawDataOperations' ]
    },
    {
      resource: { db: 'latihan_toko', collection: 'system.js' },
      actions: [ '... daftar actions yang sama ...' ]
    }
  ],
  inheritedAuthenticationRestrictions: []
}

User toko_cron kini hanya bisa login dari 127.0.0.1 — cocok untuk tugas terjadwal yang berjalan di mesin yang sama dengan server. Di produksi, ganti nilainya dengan IP atau CIDR jaringan internal tempat layanan itu berada, misalnya "10.0.0.0/24". Detail batasan hanya tampil pada query user bentuk tepat seperti db.getUser() di atas — db.getUsers() menolak menampilkannya.

Rotasi dan Audit Berkala #

Least privilege bukan keadaan yang sekali dipasang lalu dilupakan. Aplikasi bertambah, kebutuhan bergeser, dan tanpa disadari user lama menumpuk hak yang tidak lagi dipakai. Jadikan tiga rutinitas ini bagian dari kalender operasional:

  • Rotasi kredensial: ganti password user aplikasi secara berkala (misalnya tiap 90 hari) dan segera saat ada indikasi bocor — pakai db.changeUserPassword() dari Chapter Database Users.
  • Tinjauan hak: telusuri db.getUsers() di tiap database dan db.getRole() pada role kustom; pertanyakan setiap role yang tidak jelas pemakainya.
  • Pembersihan: jatuhkan user milik layanan yang sudah pensiun dengan db.dropUser() — user menganggur adalah pintu yang terlupakan.

Bersihkan kembali percobaan kita:

js
db.dropUser("toko_api")
db.dropUser("toko_dashboard")
db.dropUser("toko_cron")
db.getUsers()

Output yang Dihasilkan (diringkas serupa):

text
{ ok: 1 }
{ ok: 1 }
{ ok: 1 }
{
  users: [],
  ok: 1
}

Rangkuman #

  • Least privilege: hak paling sedikit dan cakupan paling sempit yang masih menyelesaikan pekerjaan — untuk memperkecil dampak kredensial yang bocor.
  • Satu user per layanan, didokumentasikan dalam matriks aplikasi → user → role yang bisa diaudit sekilas.
  • authenticationRestrictions menambah dimensi jaringan: user hanya boleh masuk dari IP/CIDR tertentu.
  • Rotasi password, tinjauan getUsers()/getRole(), dan pembersihan user pensiun adalah rutinitas berkala, bukan tugas sekali jalan.