Pada Chapter Pengelolaan Secrets di section Persiapan Lingkungan, kita sudah menutup celah paling awal: kredensial tidak lagi tertulis di kode, berkas .env ditahan dari Git lewat .gitignore, dan production disebut perlunya secrets manager. Praktik tersebut adalah langkah pertama, bukan garis akhir. Ketika TokoKita berjalan dengan tiga environment dan belasan pipeline, muncul pertanyaan lanjutan: siapa yang boleh membaca berkas secrets di disk, bagaimana mengganti API key tanpa menghentikan pipeline yang sedang berjalan, dan mengapa environment pengembangan tidak boleh memegang kunci production. Chapter ini menjawab ketiganya lewat simulasi nyata — pemeriksaan izin berkas, rotasi dua versi, dan pembatasan scope — lalu menutup dengan pola kerja Vault.
Rekap Singkat: .env dan .gitignore #
Aturan dasarnya tetap berlaku: kode membaca kredensial dari environment variable seperti DB_PASSWORD, berkas .env berisi nilai nyata hanya untuk pengembangan lokal, dan .env.example tanpa nilai rahasia menjadi dokumentasi variabel yang dibutuhkan. Nilai penampung seperti YOUR_DATABASE_PASSWORD dipakai agar kelalaian terlihat sejak baris pertama. Yang belum kita bahas adalah perlindungan berkas itu sendiri setelah nilai nyata masuk — itulah titik awal simulasi berikut.
Simulasi: Izin Berkas, Rotasi, dan Scope per Environment #
Skrip di bawah menuliskan berkas secrets untuk environment staging, mengunci izinnya ke pemilik saja, mensimulasikan rotasi API key versi lama ke versi baru, lalu mencetak scope service account pipeline di tiap environment:
"""Demo ch5: berkas secrets dengan chmod 600, rotasi API key, scope per environment."""
import base64
import hashlib
import os
import stat
from cryptography.fernet import Fernet
# Kunci demo deterministik; production memakai Fernet.generate_key().
def kunci_dari(label: str) -> bytes:
return base64.urlsafe_b64encode(hashlib.sha256(label.encode()).digest())
# 1) Tulis berkas secrets staging, lalu kunci izin baca/tulis ke pemilik saja
berkas = "secrets-staging.env"
with open(berkas, "w") as f:
f.write("DB_PASSWORD=YOUR_DATABASE_PASSWORD\n")
f.write("API_KEY=YOUR_API_TOKEN\n")
os.chmod(berkas, 0o600)
info = os.stat(berkas)
terbaca_umum = bool(info.st_mode & (stat.S_IRGRP | stat.S_IROTH))
print("== Pemeriksaan berkas secrets ==")
print(f"berkas : {berkas}")
print(f"izin berkas : {stat.filemode(info.st_mode)}")
print(f"dapat dibaca grup/publik: {terbaca_umum} (harus False)")
# 2) Rotasi API key: deteksi versi lama, terbitkan versi baru, transisi
fernet = Fernet(kunci_dari("staging/api-key-store"))
api_key_v1 = b"tokokita-api-v1-YOUR_API_TOKEN"
api_key_v2 = b"tokokita-api-v2-YOUR_API_TOKEN"
token_v1 = fernet.encrypt(api_key_v1)
print()
print("== Simulasi rotasi API key ==")
print("versi pada secret aktif:", "v1 -> jadwalkan rotasi"
if b"api-v1" in fernet.decrypt(token_v1) else "v2")
token_v2 = fernet.encrypt(api_key_v2)
AKTIF = {"v2"} # setelah rotasi selesai, hanya v2 yang diizinkan
def panggil_api(token: bytes) -> str:
versi = fernet.decrypt(token).decode().split("-")[2]
return "diterima" if versi in AKTIF else "DITOLAK (versi lama)"
print("panggilan dengan v1 setelah rotasi:", panggil_api(token_v1))
print("panggilan dengan v2 setelah rotasi:", panggil_api(token_v2))
# 3) Scope service account dibatasi per environment
SCOPE_ENV = {
"dev": {"sandbox.read"},
"staging": {"warehouse.read", "warehouse.write"},
"prod": {"warehouse.read", "warehouse.write", "billing.read"},
}
print()
print("== Scope service account pipeline per environment ==")
for env, scope in SCOPE_ENV.items():
print(f"{env:9}: {', '.join(sorted(scope))}")
print("dev dapat membaca billing:", "billing.read" in SCOPE_ENV["dev"])
os.remove(berkas)Output yang Dihasilkan:
== Pemeriksaan berkas secrets ==
berkas : secrets-staging.env
izin berkas : -rw-------
dapat dibaca grup/publik: False (harus False)
== Simulasi rotasi API key ==
versi pada secret aktif: v1 -> jadwalkan rotasi
panggilan dengan v1 setelah rotasi: DITOLAK (versi lama)
panggilan dengan v2 setelah rotasi: diterima
== Scope service account pipeline per environment ==
dev : sandbox.read
staging : warehouse.read, warehouse.write
prod : billing.read, warehouse.read, warehouse.write
dev dapat membaca billing: FalseMari kita telaah baris-baris kode di atas:
- Baris 20 (
os.chmod(berkas, 0o600)): izinrw-------berarti hanya pemilik berkas yang dapat membaca dan menulis; grup dan pengguna lain ditolak sistem operasi sebelum aplikasi mana pun berjalan. - Baris 22-23 (
os.stat): pemeriksaan memakai bitS_IRGRPdanS_IROTH— audit kecil yang memastikan berkas tidak terbaca grup atau publik, aturan yang sama denganchmod 600di terminal. - Baris 37-38 (deteksi versi): nilai secret menyimpan penanda versi
api-v1, sehingga pipeline dapat melaporkan versi apa yang sedang dipakai dan kapan rotasi harus dijadwalkan. - Baris 41 (
AKTIF): himpunan versi aktif menjadi saklar rotasi — selama masa transisi berisi{"v1", "v2"}, lalu cukup{"v2"}setelah semua pemanggil diperbarui. - Baris 44-46 (
panggil_api): versi dibaca dari token terenkripsi lalu diperiksa terhadapAKTIF; kredensial lama ditolak tanpa perlu mengubah kode pemanggil. - Baris 53-57 (
SCOPE_ENV): scope service account berbeda per environment —devhanya boleh membaca sandbox dan terbukti tidak memegangbilling.read.
Vault dan Dynamic Secrets #
Di production, secrets manager seperti HashiCorp Vault
atau AWS Secrets Manager
menambahkan tiga kemampuan yang tidak dimiliki berkas .env: kredensial dinamis berumur pendek, kebijakan akses per aplikasi, dan catatan setiap pembacaan. Pipeline tidak lagi memegang kata sandi database yang sama bertahun-tahun; pipeline meminta kredensial sementara saat dibutuhkan dan kredensial tersebut dicabut otomatis saat masa sewa habis:
Urutan tersebut mengubah model ancaman secara mendasar: kredensial yang dicuri paling lama hidup satu jam, dan setiap permintaan kredensial tercatat sehingga kebocoran terlihat dari pola aksesnya. Rotasi berubah dari proyek besar menjadi rutinitas otomatis — Vault sendiri dapat menjadwalkan pembuatan kredensial database baru setiap jam tanpa melibatkan manusia.
Pola pengelolaan berlapis ini menutup siklus hidup sebuah secret: nilai lahir di secrets manager, dipakai lewat environment variable atau API, diaudit setiap pembacaan, dan berhenti berlaku pada waktu yang sudah ditentukan sejak awal. Tidak ada titik di mana nilai rahasia berlama-lama tanpa pemilik, tanpa masa berlaku, dan tanpa jejak pemakaian.
Dengan kunci dan kredensial aman di tempatnya, satu celah masih tersisa: orang-orang yang sah membuka data — analyst, tim support, sampai engineer yang men-debug — tetap melihat NIK dan nomor telepon dalam bentuk utuh. Teknik menyembunyikan bagian data dari mata manusia tanpa menghapus nilai analitiknya adalah bidang Chapter Masking dan Tokenization.