ProtectSystem=strict membuat seluruh sistem berkas dipasang read-only untuk unit tersebut, kecuali direktori yang secara eksplisit dibuka lewat ReadWritePaths=. Direktif itu bagian dari keluarga sandbox di systemd.exec, bersama NoNewPrivileges=, PrivateTmp=, ProtectHome=, SystemCallFilter=, dan DynamicUser=. Efeknya terukur: systemd-analyze security menghitung exposure level tiap unit pada skala 0.0 sampai 10.0, sehingga selisih antara unit tanpa sandbox dan unit yang dikeraskan bisa dibandingkan dengan angka, bukan opini.
Artikel ini membahas urutan direktif mana yang memberi hasil terbesar, cara mengukurnya, dan risiko yang muncul ketika hardening dipasang terlalu cepat pada service yang menulis ke direktori tak terduga.
TL;DR
- ProtectSystem=strict menjadikan hampir seluruh sistem berkas read-only; hanya ReadWritePaths= yang boleh ditulis.
- NoNewPrivileges= mencegah proses menaikkan hak lewat setuid, dan murah dipasang di hampir semua service.
- systemd-analyze security memberi skor exposure level 0.0 sampai 10.0 per unit, jadi progres bisa diukur.
- PrivateTmp= dan ProtectHome= paling sering memecahkan aplikasi karena mengubah lokasi berkas sementara dan direktori rumah.
- Pasang bertahap: satu direktif, satu restart, satu verifikasi fungsi, sebelum menambah direktif berikutnya.
Apa itu ProtectSystem=strict dan apa bedanya dengan yes?
ProtectSystem=strict memasang seluruh sistem berkas read-only kecuali direktori yang dibuka lewat ReadWritePaths=, sehingga service hanya bisa menulis ke jalur yang benar-benar dinyatakan. Nilai yes memasang /usr dan /boot read-only tetapi masih memberi ruang tulis di tempat lain, sedangkan full menambahkan /etc ke daftar read-only. Semuanya didokumentasikan di halaman systemd.exec, bersama direktif sandbox lain dan nilai yang diterima masing-masing.
Praktisnya, strict adalah pilihan default yang masuk akal untuk service modern: aplikasi web yang menulis ke satu direktori data, satu direktori log, dan satu direktori unggahan tidak membutuhkan akses tulis ke seluruh sistem berkas. Aplikasi yang menulis ke banyak lokasi tersebar, atau yang memanggil skrip sistem yang menulis ke /var/lib, justru akan gagal dan menampilkan pesan error yang menyesatkan sampai ReadWritePaths= ditambahkan.
ProtectHome= melengkapi ini dengan tiga nilai: yes membuat direktori rumah tidak dapat diakses, read-only membuatnya hanya bisa dibaca, dan tmpfs menyembunyikannya di balik sistem berkas sementara kosong. Untuk service yang berjalan sebagai pengguna sistem tanpa direktori rumah, yes adalah pilihan paling sederhana.
Direktif sandbox mana yang paling besar efeknya?
Urutan dampak biasanya mengikuti seberapa luas akses yang dipangkas: pembatasan sistem berkas memberi lompatan terbesar, disusul pembatasan hak, lalu pembatasan syscall. Tabel berikut merangkum direktif yang paling sering dipakai, berdasarkan keterangan di dokumentasi systemd.exec.
| Direktif | Efek utama | Risiko kompatibilitas |
|---|---|---|
| ProtectSystem=strict | Hampir seluruh sistem berkas read-only | Sedang: butuh ReadWritePaths= untuk setiap jalur tulis |
| ProtectHome=yes | Direktori rumah tidak dapat diakses | Rendah bila service tidak menyimpan data di home |
| PrivateTmp=yes | /tmp dan /var/tmp terisolasi per unit | Sedang: berkas sementara tidak lagi dibagi antar service |
| NoNewPrivileges=yes | Proses tidak bisa menaikkan hak lewat setuid | Rendah, kecuali service memang memanggil biner setuid |
| DynamicUser=yes | UID dialokasikan saat unit berjalan, hilang saat berhenti | Tinggi: kepemilikan berkas persisten harus diatur lewat StateDirectory= |
| CapabilityBoundingSet= | Membatasi capability yang boleh dipegang proses | Sedang: daftar kosong mematikan banyak operasi privileged |
| RestrictAddressFamilies= | Membatasi keluarga alamat soket yang dapat dibuat | Rendah untuk service yang hanya butuh IPv4 dan IPv6 |
| SystemCallFilter= | Membatasi syscall yang boleh dipanggil | Tinggi: debugging menjadi sulit bila terlalu agresif |
Untuk sebagian besar service, kombinasi NoNewPrivileges=yes, ProtectSystem=strict, ProtectHome=yes, dan PrivateTmp=yes sudah memberi pengurangan permukaan serangan yang berarti tanpa menyentuh syscall. SystemCallFilter= sebaiknya dipasang belakangan, setelah tiga langkah pertama stabil, karena kegagalannya muncul sebagai crash tanpa pesan yang jelas.
Bagaimana mengukur exposure level sebuah unit?
Jalankan systemd-analyze security dengan nama unit sebagai argumen, lalu baca exposure level yang dilaporkan pada skala 0.0 sampai 10.0. Semakin tinggi skornya, semakin banyak direktif pembatas yang belum dipasang; angka itu adalah ringkasan dari daftar direktif yang diperiksa satu per satu pada keluaran yang sama.
Alat itu juga melaporkan rekomendasi per direktif, sehingga urutan perbaikan tidak perlu ditebak. Perintahnya didokumentasikan di halaman systemd-analyze. Karena keluarannya panjang, kebiasaan yang berguna adalah menyimpan keluaran sebelum dan sesudah perubahan ke berkas terpisah, lalu membandingkannya ketika ada service yang tiba-tiba gagal.
Satu peringatan penting: skor bagus bukan bukti service aman. Exposure level mengukur seberapa ketat sandbox dipasang, bukan seberapa aman kode aplikasi. Service dengan skor rendah tetap bisa rentan bila aplikasinya sendiri menyimpan kredensial di berkas yang bisa dibaca, atau bila ia berjalan sebagai pengguna yang sama dengan service lain.
Apakah hardening bisa memecahkan aplikasi?
Bisa, dan gejalanya sering menyesatkan karena aplikasi melaporkan kegagalan menulis berkas, bukan menolak sandbox. Tiga penyebab yang paling sering ditemui adalah PrivateTmp= memisahkan /tmp sehingga berkas yang diharapkan dibagi antar proses tidak lagi terlihat, ProtectHome= menutup akses ke direktori rumah tempat aplikasi menyimpan konfigurasi, dan ProtectSystem=strict mencegah penulisan ke /var/lib atau /etc tanpa ReadWritePaths=.
Cara mendiagnosisnya sederhana: jalankan unit, lalu periksa statusnya dan cari pesan penolakan akses. Bila aplikasi mengeluh tidak bisa menulis ke jalur tertentu, tambahkan jalur itu ke ReadWritePaths=, jangan melonggarkan ProtectSystem= ke nilai yang lebih permisif. Menambah satu jalur tulis adalah perubahan sempit; menurunkan tingkat proteksi mengembalikan seluruh permukaan serangan yang baru saja dipangkas.
DynamicUser=yes layak diperlakukan terpisah karena efeknya besar pada kepemilikan berkas. Karena UID dialokasikan saat unit berjalan, direktori yang harus bertahan antar restart perlu dinyatakan lewat StateDirectory=, CacheDirectory=, atau LogsDirectory= agar systemd mengelola kepemilikan dan izinnya. Aplikasi yang membuat berkas di jalur sembarang akan menemukan berkas itu tidak dapat diakses pada siklus berikutnya.
Bagaimana menerapkan hardening bertahap tanpa downtime?
Terapkan satu direktif per putaran, lalu restart hanya unit tersebut dan verifikasi fungsi utamanya. Urutan yang biasanya paling murah: NoNewPrivileges= dulu, lalu PrivateTmp=, lalu ProtectHome=, lalu ProtectSystem=strict dengan ReadWritePaths=, dan terakhir pembatasan capability atau syscall.
Pasang direktif lewat berkas drop-in agar tidak hilang saat paket diperbarui. Struktur direktori drop-in mengikuti nama unit, misalnya /etc/systemd/system/nama.service.d/hardening.conf, diisi bagian [Service] berisi direktif yang diinginkan. Setelah menulis berkas, jalankan systemctl daemon-reload, lalu restart unit dan periksa status serta log-nya.
Untuk service yang menulis ke beberapa jalur, mulailah dari daftar jalur yang benar-benar dipakai. Cek dengan memantau berkas yang dibuka selama satu siklus kerja normal, bukan dengan menebak dari dokumentasi aplikasi. Daftar jalur hasil pengamatan biasanya lebih pendek dari dugaan, dan setiap jalur yang tidak benar-benar perlu ditulis sebaiknya tidak dibuka.
Direktif pembatas memori dan CPU tinggal di halaman terpisah, yaitu systemd.resource-control. Memisahkan dua kelompok pengaturan itu membantu saat audit: sandbox menjawab pertanyaan "apa yang boleh disentuh", sedangkan resource control menjawab "seberapa banyak boleh dipakai".
Direktif apa yang sebaiknya dipasang belakangan?
CapabilityBoundingSet=, RestrictAddressFamilies=, dan SystemCallFilter= memberi pengurangan permukaan paling dalam, tetapi juga paling sering memecahkan aplikasi dengan cara yang sulit dibaca. Ketiganya membatasi apa yang boleh dilakukan proses setelah berjalan, sehingga kegagalannya muncul sebagai penolakan operasi individual, bukan sebagai pesan bahwa unit gagal start.
CapabilityBoundingSet= membatasi daftar capability yang boleh dipegang proses. Service yang hanya mendengarkan port di atas 1024 dan membaca berkas miliknya sendiri biasanya tidak membutuhkan capability apa pun, sehingga daftar kosong adalah titik awal yang wajar. Bila aplikasi membutuhkan pengikatan port di bawah 1024, kemampuan itu bisa diberikan terpisah lewat AmbientCapabilities= tanpa membuka seluruh daftar.
RestrictAddressFamilies= membatasi keluarga alamat soket yang dapat dibuat proses. Service HTTP yang hanya melayani IPv4 dan IPv6 tidak membutuhkan keluarga soket lain, sehingga daftar yang pendek mengurangi jalur yang bisa dipakai kode berbahaya untuk berkomunikasi keluar. Service yang memanggil alat lain lewat soket Unix justru membutuhkan keluarga AF_UNIX tetap terbuka.
SystemCallFilter= bekerja di lapisan paling bawah dan paling cepat berubah perilaku antar versi kernel. Sebelum memakainya, pastikan ada jalur rollback cepat: simpan berkas drop-in lama, dan siapkan perintah untuk menghapusnya. Pada service yang sering memanggil biner pihak ketiga, filter syscall cenderung menimbulkan kegagalan musiman yang sulit direproduksi.
Kapan hardening tidak layak dipasang?
Ada tiga situasi di mana menahan diri lebih rasional daripada memaksakan sandbox. Pertama, service yang sedang dalam proses migrasi besar: setiap kegagalan akan bercampur dengan perubahan lain sehingga penyebabnya sulit dipisahkan. Kedua, service yang memang menjalankan alat administratif dengan akses luas, misalnya alat yang mengelola container atau berkas sistem, karena batas yang ketat bertabrakan dengan pekerjaannya sendiri.
Ketiga, host tanpa mekanisme pemantauan status unit. Sandbox yang memecahkan aplikasi secara diam-diam lebih berbahaya daripada tanpa sandbox, karena service tampak berjalan sementara fungsinya sudah gagal. Pastikan ada pemeriksaan status dan log setelah setiap perubahan, dan pastikan hasilnya tercatat agar perubahan berikutnya bisa dibandingkan dengan kondisi yang diketahui baik.
FAQ
Apakah ProtectSystem=strict memblokir penulisan log?
Tidak selalu, tetapi bisa. Bila aplikasi menulis langsung ke /var/log, jalur itu perlu dibuka lewat ReadWritePaths= atau diganti ke LogsDirectory= agar systemd mengelola direktorinya. Banyak aplikasi modern menulis ke stdout dan membiarkan journald menyimpannya.
Apakah NoNewPrivileges= aman untuk semua service?
Untuk hampir semua service aplikasi, ya. Direktif ini mencegah proses menaikkan hak lewat biner setuid, dan service yang memang bergantung pada biner semacam itu akan gagal. Kegagalannya jelas dan mudah dikenali, sehingga aman dipasang lebih dulu.
Mengapa skor exposure level berbeda antar service yang mirip?
Karena skor dihitung dari direktif yang aktif pada tiap unit, bukan dari jenis aplikasinya. Dua service dengan biner sama bisa punya skor berbeda bila salah satunya dijalankan dengan DynamicUser= atau CapabilityBoundingSet= yang lebih ketat.
Apakah PrivateTmp= memutus komunikasi antar service?
Bisa. Setiap unit mendapat /tmp sendiri, sehingga berkas sementara tidak lagi dibagi. Service yang bertukar berkas lewat /tmp harus dipindahkan ke direktori bersama yang eksplisit, atau isolasi diterima sebagai konsekuensi yang diinginkan.
Apakah hardening menggantikan patch aplikasi?
Tidak. Sandbox membatasi dampak bila aplikasi sudah dieksploitasi, tetapi tidak menambal kerentanan di dalam aplikasi. Keduanya bekerja di lapisan berbeda dan sebaiknya dijalankan bersama, bukan saling menggantikan.
💬 Komentar (0)
Belum ada komentar. Jadilah yang pertama! 💬