Secret untuk Aplikasi PHP Kecil - Jauhkan Credential dari Code dan Mudah Diganti
Aplikasi PHP kecil pada akhirnya membutuhkan password database, token API, atau key untuk menandatangani pesan. Di mana nilai itu sebaiknya disimpan? Menaruhnya langsung di file PHP memang sederhana, tetapi cara ini mengikat credential dengan code yang menggunakannya. Memindahkannya ke environment variable terasa lebih rapi, tetapi langkah itu saja belum menjawab siapa yang dapat membacanya, bagaimana nilainya sampai ke server, atau apa yang harus dilakukan ketika bocor.
Tujuan yang berguna bukan menemukan tempat persembunyian yang tidak terlihat. Jika perlu menggunakannya, aplikasi harus menerima secret dalam bentuk plaintext pada suatu titik. Tujuan yang lebih realistis adalah memisahkan code dari credential, membatasi jangkauan setiap credential, mengurangi salinan yang tidak perlu, dan menjadikan penggantian sebagai prosedur biasa alih-alih sesuatu yang menakutkan.
Artikel ini menyusun model tersebut untuk aplikasi PHP kecil di VPS atau home server. Pembahasannya tidak mengasumsikan enterprise vault dan tidak mengklaim satu metode penyimpanan selalu paling aman. Mekanisme yang tepat bergantung pada service manager, sistem operasi, jalur deployment, dan threat model.
Tentukan lebih dulu apa yang benar-benar merupakan secret
Secret adalah nilai yang kepemilikannya memberikan kemampuan atau kepercayaan tertentu. Password database, bearer API token, webhook signing secret, dan private key adalah beberapa contoh umum. Public hostname, locale, log level, atau feature flag dapat berbeda antar-deployment tanpa harus dirahasiakan.
Perbedaan ini penting karena configuration dan secret saling beririsan, tetapi tidak identik. The Twelve-Factor App memperlakukan credential sebagai configuration yang berubah menurut deployment dan berpendapat bahwa configuration semacam itu harus dipisahkan dari code. Itu merupakan batas yang berguna. Namun, bukan berarti setiap nilai configuration memerlukan encrypted storage atau setiap environment variable dilindungi seperti secret.
Password pengguna termasuk kategori lain. Aplikasi biasanya memverifikasinya dengan password hashing yang dirancang khusus, bukan mengambil kembali password aslinya. Sebaliknya, password database operasional umumnya harus dapat diperoleh aplikasi agar bisa melakukan autentikasi ke database. Mencampur kedua masalah ini menghasilkan saran yang keliru: hashing tepat ketika nilai asli tidak lagi diperlukan, tetapi tidak dapat menggantikan penyimpanan credential yang harus disampaikan aplikasi.
Mengapa hard-coding sudah bermasalah sebelum penyerang datang
CWE-798 dari MITRE mendefinisikan hard-coded credential sebagai software weakness. Bahaya langsungnya adalah pengungkapan kepada siapa pun yang dapat membaca source atau artifact, tetapi masalah operasionalnya sama penting. Credential yang tertanam dalam code cenderung dipakai bersama di beberapa deployment, tersalin ke contoh, dan harus diubah melalui code release. Lifecycle-nya menjadi terikat dengan lifecycle aplikasi.
Version control membuat pengungkapan yang tidak disengaja bertahan lama. Menambahkan .env ke .gitignore merupakan langkah pencegahan yang berguna untuk file lokal yang belum dilacak, tetapi dokumentasi Git menegaskan bahwa ignore rule tidak memengaruhi file yang sudah dilacak. Mengganti nama file atau menghapusnya di commit terbaru juga tidak membatalkan credential yang tersimpan dalam history lama.
Template juga membutuhkan disiplin. File .env.example yang masuk repository dapat mendokumentasikan nama variable wajib dan format contoh yang tidak berbahaya, tetapi isinya harus berupa placeholder, bukan credential development atau production yang berfungsi:
APP_ENV=production
DB_HOST=127.0.0.1
DB_NAME=app
DB_USER=replace-me
DB_PASS=replace-me
WEBHOOK_SIGNING_SECRET=replace-me
Contoh tersebut menjelaskan interface antara deployment dan aplikasi. Ia bukan secret store.
Pisahkan lima urusan, bukan hanya dua file
"Jauhkan secret dari Git" memang perlu, tetapi belum lengkap. Desain yang dapat dipelihara memisahkan lima urusan:
- Code: mengetahui nama dan bentuk nilai wajib yang diharapkan, tetapi tidak mengetahui nilai production.
- Storage: menyimpan nilai otoritatif dengan access control yang sesuai untuk deployment.
- Delivery: menyediakan nilai untuk process yang dituju saat startup atau runtime.
- Use: menjaga nilai agar tidak masuk URL, log rutin, error page, dan child process yang tidak terkait.
- Lifecycle: mencatat pemilik, scope, pembuatan, rotation, expiration jika didukung, dan revocation.
OWASP Secrets Management Cheat Sheet memperlakukan access control, auditing, rotation, revocation, expiration, backup, dan recovery sebagai bagian dari sistem yang sama. Situs kecil dapat menerapkannya dengan inventaris singkat dan mekanisme lokal terbatas, bukan platform besar. Pertanyaannya tetap sama meskipun tool yang digunakan sederhana.
Jadikan configuration yang hilang sebagai kegagalan startup
PHP dapat membaca process environment variable dengan getenv(). Menurut manual PHP, pencarian berdasarkan nama mengembalikan false ketika variable tidak ada. Karena itu, secret wajib sebaiknya divalidasi secara eksplisit, bukan diam-diam memakai empty string, password testing, atau nilai yang tertanam di code.
<?php
declare(strict_types=1);
function requiredEnvironmentValue(string $name): string
{
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Missing required configuration: {$name}");
}
return $value;
}
$pdo = new PDO(
'mysql:host=' . requiredEnvironmentValue('DB_HOST')
. ';dbname=' . requiredEnvironmentValue('DB_NAME')
. ';charset=utf8mb4',
requiredEnvironmentValue('DB_USER'),
requiredEnvironmentValue('DB_PASS'),
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
Contoh ini hanya menetapkan satu sifat: aplikasi menolak berjalan tanpa nilai wajib. Contoh tersebut tidak memvalidasi hostname, memilih hak akses database, menyembunyikan process environment, atau mengonfigurasi PHP-FPM. Semua itu tetap menjadi tanggung jawab deployment. Production error handler juga harus mencegah exception dan stack trace berubah menjadi respons publik.
Lebih aman mencatat nama variable yang hilang daripada nilainya. Aturan yang sama berlaku untuk diagnostic page, support bundle, output CI, shell tracing, dan serialized configuration. Redaction setelah pencatatan mudah gagal; tidak memasukkan nilainya ke output rutin merupakan default yang lebih baik.
Environment variable menyelesaikan pemisahan, bukan seluruh threat model
Environment variable praktis karena service manager, container, shell, dan sistem CI dapat melakukan injection tanpa mengubah code PHP. Mekanisme ini juga memungkinkan satu build memakai nilai berbeda pada development dan production. Bagi banyak deployment kecil, hal tersebut merupakan peningkatan berarti dibanding credential yang masuk repository.
Namun, environment variable adalah delivery channel dan process state, bukan layanan secret management lengkap. Paparannya bergantung pada sistem operasi, service manager, debugging tool, hubungan antar-process, penanganan crash, dan siapa yang dapat memeriksa deployment configuration. Nilainya juga dapat diwariskan ke child process kecuali runtime mencegahnya.
File .env lokal menambahkan satu lapisan: library membaca file plaintext lalu menaruh nilainya ke environment atau configuration aplikasi. Karena itu, file tersebut harus berada di luar public document root, tidak masuk version control, hanya dapat dibaca oleh identitas deployment atau service yang memerlukannya, dan dipertimbangkan secara sengaja dalam backup policy. Menamai file itu "environment" tidak mengubah risiko filesystem-nya.
Environment delivery dapat menjadi baseline yang wajar ketika host hanya memiliki sedikit administrator tepercaya, permission file dan service terkendali, serta credential memiliki hak akses sempit. Alasannya menjadi kurang meyakinkan ketika banyak workload berbagi account, deployment menyalin nilai melalui beberapa sistem, atau rotation yang sering diperlukan.
Restricted file bisa lebih sederhana dan lebih jujur
Configuration file yang disediakan root di luar repository dan web root dapat menjadi pilihan yang mudah dipahami pada satu server. Service account aplikasi memperoleh read access; web server dan pengguna lain tidak otomatis memperoleh akses hanya demi kemudahan. Path file dapat tetap stabil sementara deployment tooling mengganti isinya.
Cara ini tidak otomatis lebih aman daripada environment variable. Plaintext tetap ada di disk, salinan backup dapat memperbanyaknya, dan process aplikasi yang sudah disusupi dapat membaca credential yang memang sah digunakannya. Keuntungannya adalah filesystem ownership, permission, mount boundary, dan file access dapat dianalisis secara langsung. Kekurangannya, fitur lifecycle seperti dynamic issuance, audit trail, dan automatic rotation harus dibangun di tempat lain.
Jangan menaruh decryption key di sebelah encrypted secret lalu menganggap masalah selesai. Encryption at rest dapat melindungi disk atau backup yang disalin jika key model-nya tepat, tetapi manfaatnya kecil terhadap kompromi account yang sama bila account tersebut dapat membaca ciphertext sekaligus key.
Di systemd, pertimbangkan service credentials
Untuk deployment Debian yang kompatibel, systemd menyediakan pilihan lain. Dokumentasi proyek tentang System and Service Credentials menjelaskan credential yang diperoleh saat service dimulai dan disajikan sebagai file di bawah directory yang namanya disimpan dalam $CREDENTIALS_DIRECTORY. Akses dibatasi untuk service user, dan data credential itu sendiri tidak diteruskan ke bawah process tree seperti environment value.
systemd mendukung pemuatan credential dari file dan juga dapat menangani encrypted credential. Directive dan kemampuan encryption yang tersedia bergantung pada versi systemd yang terpasang, sehingga manual lokal harus diperiksa sebelum deployment. Code aplikasi juga perlu membaca credential file tersebut, atau bootstrap layer harus menerjemahkannya ke interface yang diharapkan aplikasi.
Ada satu peringatan yang mudah terlewat: systemd menyatakan data literal SetCredential= di unit file tidak boleh digunakan untuk sensitive secret karena unit file dapat dibaca. Directive untuk file loading dan encrypted credential memiliki sifat berbeda. Menyalin password ke jenis public configuration yang baru tetap saja berarti menyalin password.
Kurangi kemampuan setiap credential
Storage tidak dapat menutupi credential yang terlalu berkuasa. Aplikasi publik biasanya tidak memerlukan database administrator account. Thumbnail worker mungkin membutuhkan write access ke satu media area, tetapi tidak perlu izin mengelola pengguna. Webhook verifier membutuhkan signing secret untuk satu endpoint, bukan semua external API token.
OWASP dan CWE-798 sama-sama menekankan restricted access dan limited privileges. Dalam praktiknya, ini berarti memisahkan credential menurut service dan environment, hanya memberikan operasi database atau API yang dibutuhkan, serta menghindari satu nilai "master" bersama. Credential dengan masa hidup lebih singkat dapat memperpendek waktu guna sebuah salinan ketika issuer dan deployment mendukungnya, tetapi expiration tanpa renewal yang andal juga dapat menjadi availability failure.
Simpan inventaris yang menjawab empat pertanyaan dasar: siapa pemilik secret ini, service mana yang menggunakannya, kewenangan apa yang diberikannya, dan bagaimana cara menggantinya. Jangan menaruh nilai secret di dalam inventaris. Daftar nama, pemilik, consumer, dan prosedur rotation sudah cukup untuk membuat credential yang terlupakan terlihat.
Rancang rotation sebelum keadaan darurat pertama
Rotation bukan sekadar membuat random string baru. Issuer harus menerima credential baru, aplikasi harus memperolehnya, deployment harus diverifikasi, lalu credential lama harus di-revoke. Sebagian sistem mengizinkan overlap dengan dua nilai valid; sistem lain memerlukan cutover terkoordinasi. Kontrak provider menentukan urutan yang aman.
- Buat pengganti dengan scope minimum yang diperlukan.
- Sediakan melalui mekanisme storage dan delivery yang dipilih.
- Restart atau reload hanya consumer yang memerlukannya.
- Verifikasi operasi yang dituju tanpa mencetak nilainya.
- Revoke credential lama dan, jika aman diuji, pastikan nilainya tidak lagi berfungsi.
- Perbarui inventaris dan hapus salinan usang dari sistem deployment serta backup sesuai policy.
Rotation yang gagal harus memiliki jalur rollback atau recovery yang jelas, tetapi itu tidak selalu berarti mengaktifkan kembali nilai lama. Jika perubahan dipicu oleh kompromi, memulihkan credential yang sudah terekspos hanya menciptakan ulang insiden.
Jika secret masuk Git, perlakukan sebagai terekspos
Respons pertama adalah membatasi kemampuan, bukan membuat repository tampak bersih. Panduan secret scanning GitHub menyatakan credential yang terdampak harus segera di-rotate. Revocation penting karena fork, clone, cache, log, teks pull request, dan salinan lain dapat bertahan setelah history diedit.
Respons yang terukur adalah mengidentifikasi credential dan scope-nya, melakukan revoke atau rotation melalui issuer, melakukan deployment pengganti, meninjau usage log yang tersedia, lalu mencari salinan tambahan tanpa mencetaknya. Pembersihan repository mungkin tetap tepat, terutama untuk sensitive data yang memang harus dihapus, tetapi bukan pengganti invalidation credential lama. Menulis ulang shared history juga menimbulkan biaya koordinasi dan sebaiknya mengikuti prosedur terbaru dari hosting provider.
Review singkat untuk service PHP kecil
- Tidak ada production credential yang tertanam dalam PHP, contoh yang di-commit, container image, atau file di public document root.
- Nilai wajib fail closed saat startup; default development tidak dapat diam-diam mencapai production.
- Identitas service PHP hanya dapat membaca credential yang dibutuhkannya.
- Database user dan API token memiliki permission praktis yang paling sempit.
- Log, error page, diagnostic, output CI, dan support bundle tidak mencetak nilai secret.
- Backup dan deployment file yang disalin termasuk dalam exposure model.
- Setiap secret memiliki pemilik, consumer, tujuan, dan prosedur penggantian yang sudah diuji.
- Nilai yang terekspos dapat di-revoke tanpa bergantung pada pembersihan Git history.
Kesimpulan
Secret management untuk aplikasi PHP kecil tidak dimulai dengan membeli vault. Langkah awalnya adalah menolak mengikat credential dengan code, lalu menanyakan di mana setiap nilai disimpan, bagaimana nilainya sampai ke satu process, apa yang dapat dilakukan process tersebut dengannya, dan bagaimana nilai itu akan diganti.
Environment variable mungkin cukup sebagai delivery mechanism pertama; restricted file mungkin lebih mudah diaudit pada satu host; systemd credentials dapat memberikan service boundary yang lebih ketat jika didukung; dedicated secret manager dapat menjadi layak ketika jumlah identity, deployment, dan tuntutan rotation bertambah. Tidak satu pun menghilangkan kebutuhan akan least privilege atau leak response.
Ujian yang jujur bukan apakah sebuah secret mustahil ditemukan. Pertanyaannya adalah apakah pembaca dan salinan yang tidak perlu sudah disingkirkan, serta apakah satu credential yang terekspos dapat diganti sebelum menjadi bagian permanen dari aplikasi.
References
- OWASP Cheat Sheet Series, “Secrets Management Cheat Sheet”.
- MITRE CWE, “CWE-798: Use of Hard-coded Credentials”, halaman terakhir diperbarui 30 April 2026.
- PHP Documentation Group, “getenv”.
- Adam Wiggins, “The Twelve-Factor App: Config”, terakhir diperbarui 2017.
- systemd project, “System and Service Credentials”.
- GitHub Docs, “Secret scanning”.
- Git project, “gitignore Documentation”.
