Credential systemd untuk Secret Service - Mengirim Data Tanpa Menaruhnya di Environment
Sebuah background service membutuhkan password database sebelum dapat melakukan pekerjaan yang berguna. Menaruh password itu di Environment= terlihat praktis: aplikasi sudah tahu cara membaca environment variable dan unit tetap ringkas. Namun, kemudahan ini memunculkan pertanyaan yang lebih penting: apakah process environment merupakan saluran yang tepat untuk data rahasia?
Manual eksekusi systemd menjawab pertanyaan itu secara langsung. Manual tersebut menyatakan bahwa environment variable tidak cocok untuk secret karena nilai yang dikonfigurasi bagi sebuah unit dapat terekspos melalui IPC manager dan diwariskan ke sepanjang process tree, mungkin melewati batas yang seharusnya tidak menerima nilai tersebut. EnvironmentFile= dapat menyembunyikan nilai dari teks unit, tetapi service tetap menerima nilai itu sebagai environment variable.
Credential systemd menawarkan saluran pengiriman yang berbeda. Mekanisme ini tidak menghilangkan kebutuhan untuk melindungi, merotasi, atau mencabut secret. Namun, service dapat membaca secret dari file khusus per unit, alih-alih membawa nilainya di environment. Klaim yang lebih terbatas ini berguna di server Debian kecil, selama batasannya tetap terlihat.
Apa yang berubah ketika secret menjadi credential?
Credential systemd adalah objek teks atau biner berukuran terbatas yang dikirimkan ke sebuah unit. Dengan LoadCredential=, service manager membaca file atau socket sumber dan menyiapkan salinan terpisah untuk unit tersebut. Service melihat salinan ini di credential directory yang read-only. Menurut systemd.exec(5), directory itu dapat diakses oleh user yang dikonfigurasi untuk unit tersebut dan oleh root, serta ditempatkan di memori non-swappable jika platform dan hak akses mengizinkannya.
Path menuju directory tersebut tersedia bagi process sebagai $CREDENTIALS_DIRECTORY. Di dalam pengaturan unit, specifier %d mengembang menjadi directory yang sama. Perbedaannya penting: menaruh sebuah path seperti /run/credentials/example.service/db-password di environment variable tidak berarti menaruh password di sana.
Ini mengubah pola paparan, bukan membuat datanya menjadi misterius. Process yang memang membutuhkan password dapat membacanya. Root dapat membacanya. Kerentanan yang memungkinkan penyerang bertindak sebagai service mungkin juga memungkinkan penyerang membaca password tersebut. Credential mengurangi paparan yang tidak perlu; mekanisme ini bukan batas keamanan terhadap consumer yang memang dituju.
Setup minimal dengan sumber plaintext
Anggap sebuah service bernama example-worker.service berjalan memakai akun khusus. Unit-nya dapat memuat file sumber yang dikendalikan root di bawah /etc, lalu memberi tahu aplikasi lokasi credential yang telah disiapkan:
[Unit]
Description=Example background worker
[Service]
Type=simple
User=example-worker
LoadCredential=db-password:/etc/example-worker/db-password
Environment=DB_PASSWORD_FILE=%d/db-password
ExecStart=/usr/local/bin/example-worker
File sumber tetap harus dilindungi dengan ownership dan permission yang ketat. Akun service yang tidak memiliki privilege tidak perlu memperoleh akses langsung ke /etc/example-worker/db-password; system service manager membacanya dan menyediakan salinan khusus per unit ketika memulai service. Hal ini sangat berguna bersama service user khusus atau dinamis karena operator tidak perlu membuat sebuah file plaintext persisten dapat dibaca secara luas hanya agar service dapat dimulai.
Ada batas penting pada kata salinan: LoadCredential= tidak mengenkripsi file sumber. Jika password disimpan sebagai plaintext di bawah /etc, password itu tetap berupa plaintext di sana, di dalam backup, dan di setiap snapshot yang memuat path tersebut. File permission tetap menjadi bagian dari rancangan keamanan.
Biarkan aplikasi menerima file path
Interface aplikasi yang paling bersih biasanya berupa pengaturan yang menerima path file secret. Nilai aslinya tetap berada di luar command-line argument dan environment variable, sementara deployment layer dapat menentukan lokasi file tersebut.
Sebuah worker PHP kecil dapat membaca path secara defensif seperti berikut:
<?php
$passwordFile = getenv('DB_PASSWORD_FILE');
if ($passwordFile === false || $passwordFile === '') {
throw new RuntimeException('DB_PASSWORD_FILE is not configured');
}
$password = file_get_contents($passwordFile);
if ($password === false) {
throw new RuntimeException('Database password cannot be read');
}
$password = rtrim($password, "\r\n");
Hanya menghapus line ending di bagian akhir merupakan pilihan yang disengaja. trim() yang lebih luas juga akan menghapus spasi dan karakter lain yang mungkin menjadi bagian dari secret. Apakah line ending perlu dihapus sama sekali bergantung pada cara credential dibuat; binary key harus diperlakukan sebagai data biner, bukan diedit sebagai teks.
Aplikasi sebaiknya tidak mencatat nilai tersebut ke log, menyertakannya dalam exception, atau menyalinnya ke environment milik child process. Serah terima berbasis file mempersempit satu jalur paparan, tetapi perilaku aplikasi yang ceroboh dapat membuka jalur lain.
Ketika plaintext saat disimpan masih menjadi masalah
Untuk file sumber yang tidak semestinya tetap berupa plaintext di disk, systemd juga mendukung encrypted credential. Manual systemd-creds menjelaskan cara mengenkripsi dan mengautentikasi credential menggunakan key yang diturunkan dari perangkat TPM2, host key yang hanya dapat dibaca root dan disimpan di bawah /var/lib/systemd/, atau gabungan keduanya. Kemampuan tepatnya berbeda menurut versi systemd dan host, sehingga systemd-creds --version dan manual yang terpasang secara lokal merupakan pemeriksaan kompatibilitas yang lebih baik daripada contoh yang disalin dari distribusi lebih baru.
Command berikut meminta password tanpa menampilkannya dan menulis encrypted credential bernama db-password:
sudo mkdir -p /etc/credstore.encrypted
systemd-ask-password -n | sudo systemd-creds encrypt \
--name=db-password \
- /etc/credstore.encrypted/db-password
Unit kemudian memakai varian directive yang terenkripsi:
[Service]
User=example-worker
LoadCredentialEncrypted=db-password:/etc/credstore.encrypted/db-password
Environment=DB_PASSWORD_FILE=%d/db-password
ExecStart=/usr/local/bin/example-worker
Saat service dimulai, systemd mengautentikasi dan mendekripsi objek yang tersimpan, lalu menyediakan plaintext kepada process melalui interface file credential yang sama. Dengan demikian, enkripsi melindungi representasi yang tersimpan; enkripsi tidak mencegah service yang sedang berjalan memperoleh nilai aslinya.
Pilih klaim enkripsi dengan hati-hati
Enkripsi berbasis host key dan enkripsi berbasis TPM2 menjawab threat model yang berbeda. Host key yang hanya dapat dibaca root dapat melindungi file credential yang disalin ketika key itu tidak ikut tersedia, tetapi penyerang dengan akses memadai ke file terenkripsi dan host key memiliki material yang dibutuhkan untuk dekripsi. TPM2 binding dapat mengikat dekripsi ke hardware lokal. Mekanisme ini juga dapat menghadirkan pertanyaan tentang pemulihan dan migrasi: apa yang terjadi ketika mesin, motherboard, instalasi sistem operasi, atau boot policy berubah?
Tidak ada mode terbaik yang berlaku universal tanpa mempertimbangkan kebutuhan pemulihan. Sebelum mengikat secret yang tidak tergantikan ke satu mesin, simpan jalur pemulihan yang dilindungi secara terpisah dan uji jalur tersebut. Enkripsi yang bekerja sempurna hingga satu-satunya server rusak bukanlah rencana availability yang lengkap.
Hal yang tidak dikelola oleh credential
Namanya dapat membuat fitur ini terdengar lebih besar daripada kenyataannya. Credential systemd adalah mekanisme pengiriman dan, bila dipilih, perlindungan saat data disimpan. Mekanisme ini tidak otomatis menjadi service secrets-management yang lengkap.
OWASP Secrets Management Cheat Sheet menjelaskan lifecycle yang lebih luas: access control, pembuatan, rotation, revocation, expiration, auditing, backup, dan recovery. Password database statis yang dimuat systemd tetap membutuhkan keputusan di setiap area tersebut. Mengenkripsi blob lokal tidak memperpendek masa berlaku password, mengurangi privilege-nya di database, atau mengungkap bahwa seseorang menyalinnya setelah service membacanya.
Rotation juga melintasi dua sistem. Database atau API yang mendasarinya harus menerima secret baru, dan sumber credential harus diganti dengan aman. Service pada umumnya membutuhkan restart atau jalur reaktivasi lain yang khusus untuk aplikasi sebelum menerima credential yang baru disiapkan. Transisi itu perlu direncanakan agar nilai lama dan baru tidak membuat service kehilangan akses.
Checklist migrasi yang berhati-hati
- Periksa versi lokal. Baca
systemd.exec(5)dansystemd-creds(1)yang terpasang, bukan hanya halaman online terbaru. - Berikan identity tersendiri kepada service. Credential yang dibatasi pada akun bersama tetap mewarisi luasnya akses akun tersebut.
- Tambahkan interface file path. Buat aplikasi menerima sesuatu seperti
DB_PASSWORD_FILEtanpa mencatat isinya ke log. - Mulai dari threat model. Tentukan apakah penyimpanan plaintext dengan permission ketat sudah cukup atau apakah salinan offline dan backup memerlukan encrypted credential.
- Jaga agar unit bebas dari secret literal.
SetCredential=didokumentasikan untuk data non-sensitif; gunakan directive pemuatan atau encrypted directive untuk secret. - Validasi sebelum restart. Setelah mengganti nama dan path contoh dengan nilai nyata, jalankan
sudo systemd-analyze verify /etc/systemd/system/example-worker.service, kemudian reload manager dan lakukan restart dalam waktu yang terkontrol. - Verifikasi tanpa mencetak nilainya. Periksa bahwa unit berhasil dimulai, aplikasi dapat melakukan autentikasi, dan log maupun diagnostic tidak memuat credential.
- Dokumentasikan rotation dan recovery. Catat cara mengganti secret, service mana yang harus di-restart, dan cara memulihkan data terenkripsi setelah kehilangan hardware.
Saluran yang lebih sempit, bukan perisai mutlak
Memindahkan secret dari environment variable ke credential systemd bermanfaat karena membuat serah terima lebih eksplisit dan memberi nilai tersebut interface filesystem yang lebih sempit. Menggunakan encrypted credential juga dapat melindungi salinan yang tersimpan terhadap sebagian ancaman offline. Kedua langkah itu tidak mengubah kenyataan dasar bahwa service harus melihat secret agar dapat menggunakannya.
Karena itu, pertanyaan yang berguna bukan “Apakah ini membuat password aman?” melainkan “Reader dan lokasi penyimpanan mana yang tidak perlu dan berhasil dihilangkan, serta risiko mana yang masih tersisa?” Di server kecil, jawaban yang jujur sering mengarah pada rancangan sederhana: satu service identity, satu credential khusus untuk satu tujuan, privilege minimal, tidak ada secret di log atau unit environment, dan prosedur pemulihan yang diuji sebelum diperlukan.
