Rahasia Tidak Pernah Masuk Repositori
# .gitignore — perlu, tapi jauh dari cukup .env *.pem *.key
Riwayat Git menyimpan segalanya. Kunci API yang pernah ter-commit tetap dapat diambil dari commit lama, dari fork, dari klon lokal siapa pun, dan dari layanan yang mengindeks repositori publik dalam hitungan menit. Satu-satunya tindakan yang benar adalah mencabut dan mengganti rahasia itu. Membersihkan riwayat berguna sebagai kebersihan, tetapi tidak pernah menjadi pengganti rotasi.
gitleaks detect --source . --verbose trufflehog git file://. --only-verified
Urutan Pilihan Penyimpanan
1. Penyimpanan rahasia terkelola Vault, AWS Secrets Manager, GCP Secret Manager 2. Rahasia platform Kubernetes Secret + KMS, Docker secret 3. Variabel lingkungan dari sistem CI, bukan dari berkas ter-commit 4. Berkas terenkripsi di repo SOPS + age/KMS — masih dapat diterima 5. Teks apa adanya di repo tidak pernah
Base64 sekadar pengodean — siapa pun yang dapat membaca objek Secret dapat membacanya kembali seketika. Tanpa mengaktifkan enkripsi di lapisan penyimpanan, isinya juga tersimpan apa adanya di etcd. Aktifkan EncryptionConfiguration dengan penyedia KMS, dan batasi hak get secrets lewat RBAC.
kubectl get secret db -o jsonpath='{.data.sandi}' | base64 -d
Enkripsi Data Diam
import { randomBytes, createCipheriv, createDecipheriv } from "crypto";
function enkripsi(teks, kunci) {
const iv = randomBytes(12); // BARU setiap kali
const c = createCipheriv("aes-256-gcm", kunci, iv);
const data = Buffer.concat([c.update(teks, "utf8"), c.final()]);
const tag = c.getAuthTag();
return Buffer.concat([iv, tag, data]).toString("base64");
}
Ini bukan pelemahan bertahap melainkan kegagalan total. Dua pesan yang dienkripsi dengan pasangan kunci dan IV yang sama memungkinkan penyerang memulihkan hasil XOR kedua teks asli, dan pada mode GCM juga membuka jalan memalsukan tanda autentikasi. IV harus dihasilkan acak untuk setiap operasi enkripsi — tidak pernah dari nilai tetap, penghitung yang di-reset, atau konstanta di kode.
Pilih Mode Terautentikasi
AES-256-GCM enkripsi + autentikasi ← pilih ini ChaCha20-Poly1305 alternatif setara, cepat tanpa akselerasi perangkat keras AES-256-CBC tanpa autentikasi ← perlu HMAC terpisah ECB jangan pernah ← pola data tetap terlihat
Tanpa autentikasi, penyerang dapat mengubah teks tersandi dan dekripsi tetap berjalan menghasilkan data yang berubah. Mode GCM menolak data yang telah diubah sejak awal.
Rotasi Kunci
{
"kunci_aktif": "v3",
"kunci": {
"v1": "...", // masih dipakai untuk dekripsi data lama
"v2": "...",
"v3": "..." // dipakai untuk enkripsi baru
}
}
Simpan pengenal versi kunci bersama setiap data terenkripsi. Tanpa itu, rotasi menuntut enkripsi ulang seluruh data secara serentak — yang di lapangan berarti rotasi tidak pernah dilakukan sama sekali.
Rahasia dalam Log dan Pesan Galat
const REDAKSI = ["sandi", "token", "otorisasi", "kartu", "rahasia", "kunci"];
function bersihkan(obj) {
const salinan = { ...obj };
for (const k of Object.keys(salinan)) {
if (REDAKSI.some(r => k.toLowerCase().includes(r))) salinan[k] = "[disunting]";
}
return salinan;
}
log.info({ permintaan: bersihkan(req.body) }, "permintaan masuk");
Sandi yang tercatat di log berpindah ke sistem agregasi log, cadangan, dan sering kali ke layanan pihak ketiga — dengan daftar orang yang dapat mengaksesnya jauh lebih luas daripada basis data produksi. Menyunting field sensitif di titik pencatatan lebih andal daripada berharap tidak ada yang mencatatnya.
Kredensial pada Image Kontainer
# Rentan — rahasia tetap ada di lapisan image
COPY .env /app/.env
RUN echo "$TOKEN_NPM" > ~/.npmrc && npm ci && rm ~/.npmrc
# Aman — mount rahasia hanya selama build
RUN --mount=type=secret,id=npm \
NPM_TOKEN=$(cat /run/secrets/npm) npm ci
Menghapus berkas pada RUN berikutnya tidak menghilangkannya — lapisan sebelumnya tetap tersimpan di image dan dapat dibaca dengan docker history atau dengan mengekstrak lapisan.