Authentication dan RBAC sudah memberi setiap koneksi sebuah identitas — tetapi identitas itu diaktifkan oleh kredensial, dan kredensial adalah data rahasia yang paling sering bocor lewat kesalahan yang paling sepele: ditulis di source code, ter-commit ke git, atau tercetak di log. Password yang kuat menjadi tidak berarti bila ia tertempel di file konfigurasi yang dibaca semua orang. Chapter ini membahas siklus hidup kredensial yang benar: disimpan di luar kode, dibaca lewat environment variable, dirotasi berkala, dan — untuk tim yang lebih besar — dipindahkan ke secret manager.

Peta materi Chapter ini:

  • Mengapa kredensial di source code berbahaya, dan cara memindainya.
  • Environment variable MONGODB_URI, file .env, dan .gitignore.
  • Membaca kredensial dengan Node.js tanpa mencetak isinya.
  • Secret manager: kapan .env tidak lagi cukup.
  • Rotasi password dan tabel kebiasaan lakukan-vs-hindari.

Kredensial yang Terbaca Polos #

Connection string dengan format mongodb://user:password@host menyimpan password sebagai bagian teks. Menyalin string itu ke source code berarti menaruh rahasia di tempat yang paling banyak dilihat, disalin, dan diarsipkan — termasuk di riwayat git yang menyimpan setiap versi file. Sebagai ilustrasi, kita simulasikan isi sebuah proyek ke dalam collection dan pindai pola connection string yang menyertakan kredensial:

js
db = db.getSiblingDB("latihan_kredensial")
db.dropDatabase()
db.berkas.insertMany([
  { _id: 1, file: "koneksi-lama.js",
    isi: 'const uri = "mongodb://dewi_aplikasi:[email protected]:27017/toko?authSource=admin"' },
  { _id: 2, file: "koneksi.js", isi: "const uri = process.env.MONGODB_URI" },
  { _id: 3, file: "README.md", isi: "Panduan koneksi aplikasi toko." }
])
db.berkas.find({ isi: { $regex: "mongodb://[^/@:]+:[^/@:]+@" } }, { file: 1, _id: 0 })

Output yang Dihasilkan:

text
latihan_kredensial
{ ok: 1, dropped: 'latihan_kredensial' }
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3 } }
[ { file: 'koneksi-lama.js' } ]

Baris latihan_kredensial adalah gema REPL dari pernyataan db = db.getSiblingDB(...) — nama database aktif. Mari kita bedah kuncinya: pola mongodb://[^/@:]+:[^/@:]+@ mencocokkan skema mongodb:// yang diikuti user:password@ — tanda khas kredensial tersemat. Satu file terdeteksi: koneksi-lama.js. Pada proyek nyata, pemindaian yang sama bisa dilakukan langsung di direktori kode dengan grep -rn "mongodb://" . — dan temuan apa pun harus segera dirotasi, bukan sekadar dihapus.

Fakta berikut memperparah kebocoran di source code: server pun tidak menyimpan password Anda sebagai teks. Kita buktikan dengan memeriksa kredensial yang tersimpan untuk sebuah user:

js
db = db.getSiblingDB("latihan_kredensial")
try { db.dropUser("dewi_aplikasi") } catch (e) { }
db.createUser({ user: "dewi_aplikasi", pwd: "sandiDewi123", roles: [] })
const u = db.runCommand({ usersInfo: "dewi_aplikasi", showCredentials: true }).users[0]
printjson({
  user: u.user,
  mekanisme: Object.keys(u.credentials),
  fieldKunci: Object.keys(u.credentials["SCRAM-SHA-256"])
})

Output yang Dihasilkan (bagian $clusterTime dan operationTime pada gema createUser diringkas):

text
latihan_kredensial

{ ok: 1 }

{
  user: 'dewi_aplikasi',
  mekanisme: [
    'SCRAM-SHA-1',
    'SCRAM-SHA-256'
  ],
  fieldKunci: [
    'iterationCount',
    'salt',
    'storedKey',
    'serverKey'
  ]
}

SCRAM hanya menyimpan bahan turunan: salt, storedKey, dan serverKey — password aslinya tidak pernah tertulis di disk server. Bandingkan dengan source code Anda: satu-satunya salinan password yang terbaca polos justru ada di aplikasi yang menuliskannya. Kita bereskan database latihan ini di akhir chapter.

Environment Variable dan File .env #

Praktik dasar yang benar: connection string hidup di environment variable MONGODB_URI, bukan di kode. Selama pengembangan lokal, nilainya ditaruh di file .env yang tidak pernah di-commit:

text
MONGODB_URI=mongodb://dewi_aplikasi:[email protected]:27017/toko?authSource=admin
PORT=3000

File .env wajib masuk .gitignore sejak commit pertama, dan proyek menyertakan .env.example berisi daftar variabel tanpa nilai sebagai templat bagi anggota tim baru:

text
.env

Kode aplikasi membaca variabel itu lewat process.env. File berikut menunjukkan caranya sekaligus kebiasaan mengamarkan kredensial sebelum mencetak:

js
// Membaca connection string dari environment variable.
// Jalankan tanpa MONGODB_URI, lalu dengan MONGODB_URI yang di-set.

const uri = process.env.MONGODB_URI

if (!uri) {
  console.log("MONGODB_URI belum di-set")
  console.log("Jalankan: export MONGODB_URI='mongodb://user:password@host:27017/db'")
  process.exit(1)
}

// Sembunyikan bagian user:password sebelum mencetak,
// agar kredensial tidak tertulis di log maupun terminal.
const tersamar = uri.replace(/\/\/([^:]+):([^@]+)@/, "//$1:***@")
console.log("MONGODB_URI terbaca:", tersamar)

Kita jalankan dua kali. Tanpa environment variable, program berhenti dengan pesan yang tidak membocorkan apa pun:

bash
node baca-env.js

Output yang Dihasilkan:

text
MONGODB_URI belum di-set
Jalankan: export MONGODB_URI='mongodb://user:password@host:27017/db'

Dengan MONGODB_URI yang di-set (di sini memakai nilai fiktif), connection string terbaca dan bagian kredensialnya tampil teramarkan:

bash
MONGODB_URI='mongodb://dewi_aplikasi:[email protected]:27017/toko?authSource=admin' node baca-env.js

Output yang Dihasilkan:

text
MONGODB_URI terbaca: mongodb://dewi_aplikasi:***@mongodb0.example.net:27017/toko?authSource=admin

Pola replace() pada file di atas adalah bentuk paling sederhana dari masking — kebiasaan yang juga wajib diterapkan pada logger aplikasi agar connection string tidak pernah masuk log secara utuh.

Secret Manager untuk Tim #

File .env memadai untuk satu mesin pengembang. Kebutuhan berubah ketika tim membesar dan jumlah rahasia bertambah: siapa yang boleh membaca nilai apa, kapan terakhir rotasi, dan siapa yang mengaksesnya kemarin. Pertanyaan-pertanyaan itu dijawab oleh secret manager:

SolusiKekuatan utama
HashiCorp VaultPenyimpanan terpusat, dynamic secrets, kebijakan akses granular
AWS Secrets ManagerRotasi terjadwal, integrasi IAM, audit akses
Google Secret Manager / Azure Key VaultTerintegrasi dengan ekosistem cloud masing-masing

Pola pemakaiannya konsisten: aplikasi meminta rahasia ke secret manager saat startup (mengautentikasi dengan identitas mesin, bukan password baru), lalu memakai nilainya sebagai MONGODB_URI seperti biasa — kode aplikasi nyaris tidak berubah, hanya sumber variabelnya.

Rotasi Password #

Password yang sama sejak server pertama dibangun adalah kredensial dengan masa eksposur terpanjang. Rotasi menggantinya lewat db.changeUserPassword() — perintah yang sudah kita coba di Chapter Database Users — dengan urutan operasi yang menjaga aplikasi tetap hidup: siapkan kredensial baru, deploy aplikasi yang membacanya, lalu nonaktifkan kredensial lama. Saat ada indikasi bocor, rotasi dilakukan segera tanpa menunggu jadwal.

LakukanHindari
Simpan connection string di environment variable atau secret managerMenuliskan password di source code atau file yang di-commit
Amarkan kredensial di log (*** pada bagian user:password)Mencetak connection string utuh ke log atau pesan galat
Rotasi berkala dan segera saat indikasi bocorMemakai satu password sejak server dibangun
Satu kredensial per aplikasi agar bisa dirotasi terpisahSatu user dipakai bersama banyak aplikasi
.env di .gitignore + .env.example sebagai templatMeng-commit .env karena “cuma sebentar”

Terakhir, kita bersihkan seluruh artefak latihan chapter ini — user percobaan dan database-nya:

js
db = db.getSiblingDB("latihan_kredensial")
db.dropUser("dewi_aplikasi")
db.dropDatabase()

Output yang Dihasilkan (diringkas serupa):

text
latihan_kredensial
{ ok: 1 }
{ ok: 1, dropped: 'latihan_kredensial' }

Rangkuman #

  • Connection string menyimpan password sebagai teks; kredensial di source code bisa ditemukan dengan pemindaian pola mongodb://user:password@.
  • Server hanya menyimpan bahan turunan SCRAM (salt, storedKey, serverKey) — salinan polos satu-satunya ada di sisi yang menuliskannya.
  • Simpan MONGODB_URI di environment variable; file .env masuk .gitignore sejak commit pertama, dengan .env.example sebagai templat.
  • Amarkan kredensial di log dan pesan galat — baca-env.js menunjukkan pola masking-nya.
  • Tim yang membesar memindahkan rahasia ke secret manager (Vault, AWS Secrets Manager) untuk kontrol akses dan rotasi terpusat.
  • Rotasi berkala lewat changeUserPassword dengan urutan: kredensial baru, deploy, nonaktifkan yang lama.