Penyimpanan secret yang aman di Laravel bukan sekadar menaruh kredensial di file .env. Masalah utamanya adalah memastikan secret tidak masuk repository, tidak bocor ke log, tidak tersimpan basi saat deployment, dan bisa dirotasi tanpa memutus layanan.

Untuk banyak aplikasi, .env masih cukup jika dikelola dengan disiplin. Namun ketika jumlah environment bertambah, tim makin besar, atau kebutuhan audit dan rotasi makin ketat, secret manager seperti HashiCorp Vault atau layanan cloud sejenis biasanya lebih tepat. Kunci praktik yang baik adalah memisahkan config biasa dari secret, membatasi akses, dan merancang alur deployment yang aman.

Membedakan config biasa dan secret

Tidak semua nilai konfigurasi perlu diperlakukan sebagai secret. Kesalahan umum adalah mencampur semua variabel ke kategori yang sama, padahal tingkat risikonya berbeda.

Config biasa

Config biasa adalah nilai yang boleh diketahui internal tim dan umumnya tidak memberi akses langsung jika bocor. Contohnya:

  • Nama aplikasi
  • Timezone
  • Driver cache yang digunakan
  • Flag fitur non-sensitif
  • URL endpoint internal yang tidak mengandung token

Secret

Secret adalah nilai yang jika bocor dapat dipakai untuk mengakses sistem lain, meniru identitas aplikasi, atau membuka data sensitif. Contohnya:

  • Password database
  • API key pihak ketiga
  • Token webhook yang dipakai untuk verifikasi
  • Kredensial SMTP
  • Private key atau signing key
  • Access key object storage

Prinsip sederhana: jika nilainya tidak aman dibagikan di chat tim, issue tracker, screenshot, atau log, perlakukan itu sebagai secret.

Risiko menyimpan secret di repository

Menyimpan secret langsung di repository Git adalah salah satu sumber kebocoran yang paling sering terjadi. Bahkan jika repository private, risikonya tetap tinggi karena riwayat commit, fork, artifact CI, mirror, dan akses mantan anggota tim bisa menyimpan salinan secret tersebut.

Masalah yang sering muncul

  • Secret pernah ter-commit lalu dihapus, tetapi tetap ada di history Git.
  • File contoh salah kelola, misalnya .env ikut ter-push karena aturan ignore keliru.
  • Secret tersebar ke banyak tempat, seperti skrip deployment, pipeline CI, atau dokumentasi internal.
  • Sulit rotasi, karena aplikasi, worker, dan service lain memakai nilai lama di berbagai lokasi.

Yang sebaiknya dilakukan

  • Simpan hanya .env.example tanpa nilai rahasia nyata.
  • Pastikan .env tidak pernah dilacak Git.
  • Gunakan secret scanning di repository dan CI jika tersedia.
  • Jika secret pernah ter-commit, anggap sudah bocor dan lakukan rotasi, bukan hanya menghapus file.

Memakai .env dengan benar di Laravel

Di Laravel, .env adalah mekanisme praktis untuk injeksi konfigurasi per environment. Namun ada pola penggunaan yang benar agar perilaku aplikasi tetap konsisten, terutama saat memakai cache konfigurasi.

Aturan penting: akses env hanya di file config

Gunakan env() di file konfigurasi dalam direktori config/, lalu akses nilainya di aplikasi melalui config(). Jangan memanggil env() langsung di controller, job, service, atau model.

Benar:

// config/services.php
return [
    'payment_gateway' => [
        'base_url' => env('PAYMENT_BASE_URL'),
        'api_key' => env('PAYMENT_API_KEY'),
    ],
];
// app/Services/PaymentClient.php
class PaymentClient
{
    public function headers(): array
    {
        return [
            'Authorization' => 'Bearer ' . config('services.payment_gateway.api_key'),
            'Accept' => 'application/json',
        ];
    }
}

Hindari:

// Jangan seperti ini di kode aplikasi
$token = env('PAYMENT_API_KEY');

Alasannya: saat config:cache digunakan, Laravel mengandalkan konfigurasi yang sudah dikompilasi. Memanggil env() langsung di runtime bisa menghasilkan perilaku yang membingungkan atau nilai yang tidak tersedia sesuai ekspektasi deployment.

Struktur .env yang rapi

Kelompokkan variabel berdasarkan domain agar mudah diaudit dan dipisahkan per environment.

APP_ENV=production
APP_DEBUG=false
APP_URL=https://app.contoh.com

DB_CONNECTION=mysql
DB_HOST=db.internal
DB_PORT=3306
DB_DATABASE=app_prod
DB_USERNAME=app_user
DB_PASSWORD=secret-yang-diisi-saat-deploy

REDIS_HOST=redis.internal
REDIS_PASSWORD=secret-redis

PAYMENT_BASE_URL=https://api.payment.example
PAYMENT_API_KEY=secret-payment

MAIL_HOST=smtp.example
MAIL_PORT=587
MAIL_USERNAME=mailer
MAIL_PASSWORD=secret-mail

Pemisahan secret per environment

Jangan memakai secret yang sama untuk local, staging, dan production. Jika satu environment bocor, secret environment lain tidak ikut terdampak.

  • Local: gunakan kredensial khusus pengembangan, ruang lingkup minimal.
  • Staging: gunakan akun terpisah dari production.
  • Production: akses paling ketat, rotasi terjadwal, audit lebih lengkap.

Selain itu, hindari penggunaan akun bersama antar-aplikasi. Setiap aplikasi dan setiap environment idealnya memiliki identitas sendiri.

Bagaimana .env didistribusikan saat deployment

Jangan menyalin .env dari laptop developer ke server. File ini sebaiknya dibuat oleh sistem deployment, panel secret di platform, atau proses bootstrap instance yang mengambil secret dari lokasi aman.

Pola yang umum dipakai:

  1. CI membangun artifact aplikasi tanpa secret.
  2. Saat deploy, server atau runtime mengambil secret dari environment variable atau secret manager.
  3. Laravel memuat konfigurasi tersebut.
  4. Jika memakai config:cache, lakukan setelah secret final tersedia.

Kapan .env cukup, kapan perlu Vault atau secret manager

.env cocok untuk aplikasi yang sederhana sampai menengah, selama akses ke server dan proses deployment dikelola dengan baik. Tetapi kebutuhan berubah ketika jumlah secret, jumlah service, atau tuntutan kepatuhan meningkat.

.env biasanya cukup jika

  • Aplikasi hanya beberapa service
  • Jumlah environment sedikit
  • Tim kecil dan akses server ketat
  • Rotasi secret jarang dan masih bisa dikendalikan manual
  • Audit formal belum menjadi kebutuhan utama

Gunakan secret manager jika

  • Secret tersebar ke banyak aplikasi, worker, cron, dan service
  • Butuh audit trail siapa mengubah atau mengakses secret
  • Butuh rotasi terjadwal dan lebih sering
  • Butuh secret dinamis atau berumur pendek
  • Ingin mengurangi distribusi file .env statis ke banyak server
  • Infrastruktur sudah berbasis cloud, container, atau orchestration

Trade-off .env vs secret manager

  • .env: sederhana, mudah dipahami, tetapi distribusi dan rotasi cenderung manual.
  • Vault / layanan cloud sejenis: lebih aman dan terpusat, tetapi menambah kompleksitas operasional, dependensi jaringan, dan kebutuhan bootstrap identity.

Jika memilih Vault atau layanan cloud sejenis, pertanyaan utama bukan hanya “di mana secret disimpan”, tetapi juga “bagaimana aplikasi membuktikan identitasnya untuk mengambil secret”. Ini sering menjadi bagian tersulit dari desain.

Pola integrasi Laravel dengan secret manager

Ada dua pendekatan umum untuk memakai Vault atau layanan cloud sejenis bersama Laravel.

1. Inject ke environment saat startup

Secret manager diakses oleh entrypoint, init container, agen, atau proses bootstrap. Secret lalu diset sebagai environment variable sebelum PHP-FPM, worker queue, atau scheduler dijalankan.

Kelebihan:

  • Paling kompatibel dengan pola Laravel berbasis config()
  • Tidak perlu mengubah banyak kode aplikasi
  • Mudah dipadukan dengan config:cache

Kekurangan:

  • Perubahan secret biasanya butuh reload atau restart proses
  • Nilai secret dapat tetap hidup selama umur proses

2. Ambil secret secara runtime melalui adapter

Aplikasi memiliki lapisan sendiri untuk mengambil secret saat dibutuhkan, biasanya dengan cache lokal singkat untuk mengurangi latensi.

Kelebihan:

  • Lebih fleksibel untuk rotasi cepat
  • Bisa mendukung secret dinamis atau TTL pendek

Kekurangan:

  • Kompleksitas lebih tinggi
  • Harus menangani kegagalan jaringan, cache, fallback, dan observabilitas
  • Jika tidak hati-hati, secret bisa tersebar di banyak lapisan kode

Untuk sebagian besar aplikasi Laravel, pendekatan pertama lebih sederhana dan aman secara operasional. Pendekatan kedua lebih cocok jika kebutuhan rotasi dan dinamika secret memang kuat.

Strategi rotasi secret tanpa downtime

Rotasi secret yang aman bukan hanya mengganti nilainya. Tantangannya adalah masa transisi ketika sebagian proses masih memakai secret lama dan sebagian sudah memakai yang baru.

Prinsip dasar rotasi

  • Hindari mengganti secret dengan model cutover mendadak jika service tidak mendukung beberapa kredensial sekaligus.
  • Utamakan masa overlap, di mana secret lama dan baru sama-sama valid untuk sementara.
  • Pastikan worker, scheduler, dan proses long-running ikut diperbarui, bukan hanya web process.

Pola rotasi yang aman

  1. Buat kredensial baru di sistem target, tanpa langsung mencabut yang lama.
  2. Distribusikan secret baru ke secret store atau environment deployment.
  3. Deploy atau reload aplikasi agar membaca secret baru.
  4. Verifikasi trafik baru sudah memakai kredensial baru.
  5. Restart worker long-running jika perlu.
  6. Setelah masa aman, cabut kredensial lama.

Contoh kasus: API key pihak ketiga

Jika penyedia mendukung dua API key aktif, gunakan key baru lebih dulu sambil key lama tetap valid. Aplikasi Laravel dipindahkan ke key baru melalui deployment. Setelah monitoring menunjukkan semua instance sehat, nonaktifkan key lama.

Contoh kasus: password database

Rotasi password database lebih sensitif karena koneksi aktif bisa tetap memakai kredensial lama sampai koneksi diputus atau pool diperbarui. Rencanakan reload proses dan validasi koneksi baru. Jika arsitektur memiliki worker queue yang berjalan lama, restart worker menjadi bagian wajib dari rotasi.

Rotasi gagal paling sering terjadi karena web process sudah memakai secret baru, tetapi queue worker atau scheduler masih hidup dengan konfigurasi lama.

Fallback saat secret berubah

Fallback bukan berarti menyimpan secret lama selamanya. Fallback berarti ada mekanisme transisi yang terkontrol.

  • Untuk token verifikasi atau signing key tertentu, dukung daftar key aktif: satu utama, satu cadangan sementara.
  • Untuk kredensial eksternal, gunakan overlap validity jika provider mendukung.
  • Untuk runtime fetch, cache secret dengan TTL pendek dan mekanisme refresh saat auth gagal.

Namun fallback harus dibatasi waktunya. Semakin lama secret lama tetap valid, semakin besar permukaan serangannya.

Least privilege, audit, dan pemisahan akses

Least privilege

Setiap aplikasi hanya boleh mengakses secret yang memang dibutuhkan. Jangan berikan akses ke seluruh namespace secret jika aplikasi hanya memerlukan satu database password dan satu API key.

Praktik yang baik:

  • Pisahkan secret per aplikasi dan per environment.
  • Pisahkan identitas untuk web, worker, dan proses admin jika hak aksesnya berbeda.
  • Gunakan akun database terpisah untuk aplikasi yang berbeda.
  • Batasi izin ke layanan cloud hanya pada resource yang digunakan.

Audit perubahan

Anda perlu tahu siapa mengubah secret, kapan, dan di environment mana. Jika memakai secret manager, manfaatkan audit trail bawaan. Jika masih memakai .env via deployment pipeline, simpan jejak perubahan di sistem CI/CD atau konfigurasi infrastruktur.

Yang sebaiknya tercatat:

  • Waktu perubahan
  • Operator atau service account yang melakukan perubahan
  • Environment target
  • Nama secret yang berubah
  • Status deployment dan verifikasi pasca-rotasi

Hindari menyimpan nilai secret di log audit. Cukup metadata perubahan.

Alur deployment yang aman untuk Laravel

Berikut contoh alur deployment yang realistis dan minim kebocoran:

  1. Developer push kode tanpa secret ke repository.
  2. CI menjalankan test dan membangun artifact.
  3. Artifact dipublikasikan tanpa file .env produksi.
  4. Di environment target, platform atau agen mengambil secret dari store yang aman.
  5. Secret diinjeksikan sebagai environment variable atau file lokal yang hanya bisa dibaca proses aplikasi.
  6. Laravel menjalankan migrasi jika diperlukan, lalu cache konfigurasi setelah secret tersedia.
  7. Web process dan queue worker direload agar memakai nilai terbaru.
  8. Monitoring memverifikasi koneksi database, cache, dan layanan eksternal.

Perintah yang perlu diperhatikan

Jika Anda memakai cache konfigurasi, urutan sangat penting. Setelah secret diperbarui, pastikan konfigurasi yang dikompilasi tidak lagi memuat nilai lama.

php artisan config:clear
php artisan config:cache

Untuk worker queue yang berjalan lama, reload atau restart setelah perubahan secret:

php artisan queue:restart

Perintah pasti saat deployment bergantung pada arsitektur Anda, tetapi prinsipnya sama: refresh config cache dan restart proses long-running.

Kesalahan umum yang sering menyebabkan kebocoran atau gangguan

1. Memakai secret langsung di kode

Contoh buruk:

$client = new ApiClient('hardcoded-secret');

Ini membuat rotasi sulit, menambah risiko commit tidak sengaja, dan memperbesar kemungkinan secret tersebar ke log, test, atau potongan kode lain.

2. Menggunakan env() di luar file config

Ini sering memicu perilaku tidak konsisten setelah config:cache. Gunakan config() di kode aplikasi.

3. Config cache basi

Setelah mengganti secret, aplikasi masih gagal autentikasi karena proses masih membaca cache konfigurasi lama. Ini salah satu masalah paling umum pada deployment Laravel.

Gejala yang sering terlihat:

  • Nilai di server sudah benar, tetapi aplikasi tetap gagal konek
  • Hanya sebagian instance yang error
  • Web sudah normal, tetapi queue masih gagal

Langkah cek:

  • Pastikan secret baru benar-benar tersedia di environment target
  • Refresh config cache
  • Reload PHP-FPM atau proses aplikasi sesuai stack Anda
  • Restart worker queue

4. Secret bocor ke log

Jangan pernah melog header Authorization, payload kredensial, DSN lengkap, atau exception yang menyertakan secret mentah. Masking wajib diterapkan pada logging aplikasi, reverse proxy, APM, dan pipeline CI.

Contoh buruk:

logger()->info('Request ke payment', [
    'headers' => $headers,
]);

Jika $headers berisi bearer token, Anda baru saja menyebarkan secret ke sistem log.

Pendekatan lebih aman:

logger()->info('Request ke payment', [
    'has_auth_header' => isset($headers['Authorization']),
    'base_url' => config('services.payment_gateway.base_url'),
]);

5. Satu secret dipakai untuk semua environment

Jika staging bocor, production ikut berisiko. Pisahkan semuanya.

6. Hak akses terlalu luas

Service account aplikasi seharusnya tidak bisa membaca semua secret organisasi. Batasi sesuai kebutuhan minimum.

7. Tidak memperhatikan proses long-running

Queue worker, scheduler, Octane-like runtime, atau proses daemon lain dapat mempertahankan secret lama lebih lama daripada web request biasa.

Checklist hardening penyimpanan secret yang aman di Laravel

  • .env tidak masuk repository dan tidak dibagikan manual lewat chat/email.
  • .env.example tidak berisi nilai rahasia nyata.
  • Secret hanya diakses melalui config(), bukan env() di kode aplikasi.
  • Setiap environment memakai secret berbeda.
  • Akses ke secret store mengikuti prinsip least privilege.
  • Deployment menyuntikkan secret pada saat runtime atau bootstrap, bukan dari mesin developer.
  • Setelah perubahan secret, jalankan refresh config cache dan restart proses long-running.
  • Log, error tracker, dan APM mem-mask nilai sensitif.
  • Ada audit trail untuk perubahan secret.
  • Ada runbook rotasi yang menjelaskan urutan perubahan dan rollback.
  • Secret yang pernah bocor langsung dirotasi, bukan sekadar dihapus dari kode.

Memilih pendekatan yang tepat

Jika aplikasi Laravel Anda masih sederhana, .env bisa tetap aman selama disiplin operasionalnya baik: tidak masuk repo, akses dibatasi, distribusi saat deploy terkontrol, dan konfigurasi tidak dipakai sembarangan di kode. Untuk tim yang lebih besar atau sistem yang lebih kompleks, secret manager seperti Vault atau layanan cloud sejenis memberi keuntungan nyata dalam audit, sentralisasi, dan rotasi.

Yang paling penting bukan sekadar alatnya, melainkan rancangan operasionalnya: pemisahan per environment, least privilege, proses rotasi tanpa downtime, dan kontrol terhadap cache konfigurasi serta worker yang berjalan lama. Dengan pola ini, penyimpanan secret yang aman di Laravel menjadi bagian dari arsitektur deployment, bukan hanya file konfigurasi.