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
.envtidak 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:
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:
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:
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):
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:
MONGODB_URI=mongodb://dewi_aplikasi:[email protected]:27017/toko?authSource=admin
PORT=3000File .env wajib masuk .gitignore sejak commit pertama, dan proyek menyertakan .env.example berisi daftar variabel tanpa nilai sebagai templat bagi anggota tim baru:
.envKode aplikasi membaca variabel itu lewat process.env. File berikut menunjukkan caranya sekaligus kebiasaan mengamarkan kredensial sebelum mencetak:
// 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:
node baca-env.jsOutput yang Dihasilkan:
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:
MONGODB_URI='mongodb://dewi_aplikasi:[email protected]:27017/toko?authSource=admin' node baca-env.jsOutput yang Dihasilkan:
MONGODB_URI terbaca: mongodb://dewi_aplikasi:***@mongodb0.example.net:27017/toko?authSource=adminPola 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:
| Solusi | Kekuatan utama |
|---|---|
| HashiCorp Vault | Penyimpanan terpusat, dynamic secrets, kebijakan akses granular |
| AWS Secrets Manager | Rotasi terjadwal, integrasi IAM, audit akses |
| Google Secret Manager / Azure Key Vault | Terintegrasi 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.
| Lakukan | Hindari |
|---|---|
| Simpan connection string di environment variable atau secret manager | Menuliskan 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 bocor | Memakai satu password sejak server dibangun |
| Satu kredensial per aplikasi agar bisa dirotasi terpisah | Satu user dipakai bersama banyak aplikasi |
.env di .gitignore + .env.example sebagai templat | Meng-commit .env karena “cuma sebentar” |
Terakhir, kita bersihkan seluruh artefak latihan chapter ini — user percobaan dan database-nya:
db = db.getSiblingDB("latihan_kredensial")
db.dropUser("dewi_aplikasi")
db.dropDatabase()Output yang Dihasilkan (diringkas serupa):
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_URIdi environment variable; file.envmasuk.gitignoresejak commit pertama, dengan.env.examplesebagai templat. - Amarkan kredensial di log dan pesan galat —
baca-env.jsmenunjukkan pola masking-nya. - Tim yang membesar memindahkan rahasia ke secret manager (Vault, AWS Secrets Manager) untuk kontrol akses dan rotasi terpusat.
- Rotasi berkala lewat
changeUserPassworddengan urutan: kredensial baru, deploy, nonaktifkan yang lama.