Mengeraskan Service Linux dengan Sandboxing systemd

Ia melaporkan skor eksposur berbobot dari 0.0 sampai 10.0 yang mencerminkan seberapa banyak sandboxing yang diterapkan sebuah unit service, lalu memberi label OK, MEDIUM, EXPOSED, atau UNSAFE. Skor tinggi berarti pengaturan longgar dan skor rendah berarti pembatasan ketat. Yang penting, ia hanya mengevaluasi fitur per-service yang disediakan systemd sendiri — ia tak bisa melihat autentikasi, validasi input, atau logika aplikasi Anda.
yes membuat /usr dan /boot read-only, full menambahkan /etc menjadi read-only, dan strict membuat seluruh filesystem read-only kecuali /dev, /proc, dan /sys. strict adalah yang paling aman dan yang saya jadikan default. Ketika sebuah service perlu menulis di suatu tempat, Anda whitelist hanya path itu dengan ReadWritePaths alih-alih melonggarkan seluruh kebijakan.
Bisa, itulah alasan Anda menerapkannya terakhir dan mengujinya di bawah beban nyata. Filter yang terlalu ketat sering membuat service start normal, lalu gagal berjam-jam kemudian pada jalur kode langka. Mulai dengan SystemCallFilter=@system-service, setel SystemCallErrorNumber=EPERM agar penolakan menjadi error biasa alih-alih pembunuhan instan, dan awasi journal untuk pesan seccomp sebelum memercayainya di produksi.
Gunakan drop-in via systemctl edit. Mengedit unit bawaan paket di /usr/lib/systemd/system berarti pembaruan paket diam-diam menimpa hardening Anda. Drop-in menulis ke /etc/systemd/system/<nama>.service.d/override.conf, yang lebih diprioritaskan daripada file bawaan dan bertahan melewati upgrade. Ia juga menjaga perubahan Anda terisolasi dan mudah dihapus jika ada yang rusak.
Tidak. Skor itu heuristik yang hanya mencakup direktif sandbox milik systemd sendiri. Ia tak mengatakan apa pun tentang kerentanan tingkat aplikasi, rahasia yang lemah, atau endpoint jaringan yang terekspos. Perlakukan skor bagus sebagai satu lapisan defense in depth — padukan dengan user non-root khusus, eksposur jaringan hak-minimal, dan praktik keamanan aplikasi biasa.

Ringkasan Utama
systemd bisa menyandboxing sebuah service tanpa menyentuh kodenya. Setel ProtectSystem=strict agar filesystem read-only, PrivateTmp=yes untuk mengisolasi file sementara, NoNewPrivileges=yes untuk memblokir eskalasi hak akses, dan SystemCallFilter=@system-service untuk membatasi panggilan kernel. Lalu jalankan systemd-analyze security untuk menilai seberapa terekspos unit tersebut.
Sebagian besar service yang berjalan di sebuah mesin Linux tidak butuh root, tidak butuh akses tulis ke seluruh filesystem, dan tidak butuh tiga ratus syscall. Mereka mendapatkannya semua tetap saja, karena itulah default-nya. Satu daemon yang berhasil dieksploitasi lalu menguasai segala hal yang secara teknis bisa dijangkau proses tersebut. Di sebuah VPS yang saya kelola, saya memperlakukan setiap service berumur panjang sebagai titik pijak potensial, dan pertahanan termurah yang saya temukan adalah sandbox bawaan systemd.
Bagian menyenangkannya, tidak ada satu pun dari ini yang berada di dalam aplikasi. Semuanya ada di unit file, atau lebih baik lagi, di sebuah drop-in override. Anda mengeraskan daemon dari luar, mengukur hasilnya dengan satu perintah, dan mengembalikannya begitu ada yang rusak. Tulisan ini membahas direktif yang penting, urutan penerapan yang saya pakai, dan cara membaca skor tanpa mengejar angka sempurna.
Sebelum mengubah apa pun, ambil baseline. systemd-analyze security melaporkan skor eksposur berbobot dari 0.0 sampai 10.0 untuk sebuah unit, di mana tinggi berarti longgar dan rendah berarti terkunci ketat. Ia lalu memberi label OK, MEDIUM, EXPOSED, atau UNSAFE pada unit tersebut. Sebuah service standar tanpa hardening biasanya akan mendarat di wilayah EXPOSED atau UNSAFE, dan itu tidak masalah — itu garis start Anda, bukan nilai rapor.
# Score one unit and see every check, sorted by exposure
systemd-analyze security myapp.service
# The tail of the output looks like this:
# → Overall exposure level for myapp.service: 9.6 UNSAFE 😨
# List every loaded service, worst first
systemd-analyze securityBaca output per-baris. Setiap baris menampilkan sebuah pengaturan, apakah ia disetel, dan seberapa besar kontribusinya terhadap eksposur. Tabel itu adalah daftar tugas Anda — baris merah dengan bobot tertinggi adalah tempat Anda mendapatkan keamanan paling banyak per baris config.
Jangan pernah mengedit unit file bawaan paket di /usr/lib/systemd/system secara langsung — pembaruan paket akan menimpanya. Jalankan perintah di bawah untuk membuat drop-in, dan perubahan Anda hidup di /etc/systemd/system serta bertahan melewati upgrade.
# Opens (or creates) a drop-in override, no risk to the shipped unit
sudo systemctl edit myapp.service
# It writes to:
# /etc/systemd/system/myapp.service.d/override.confProtectSystem mengontrol seberapa banyak bagian filesystem yang bisa ditulisi service. Nilai yes membuat /usr dan /boot read-only; full menambahkan /etc; strict membuat seluruh filesystem read-only kecuali /dev, /proc, dan /sys. Saya default ke strict lalu membuka lubang spesifik dengan ReadWritePaths untuk segelintir direktori yang benar-benar perlu ditulisi service. ProtectHome=yes menyembunyikan /home, /root, dan /run/user sepenuhnya, yang hampir tak ada daemon perlu menyentuhnya.
[Service]
ProtectSystem=strict
ProtectHome=yes
# Whitelist exactly what needs to be writable
ReadWritePaths=/var/lib/myapp /var/log/myapp
# A private, per-service /tmp that no other process can see
PrivateTmp=yesMode kegagalan di sini bisa ditebak: service start, mencoba menulis ke path yang tidak Anda whitelist, dan mati dengan error izin atau read-only-filesystem. Itu justru fitur. journalctl -u myapp akan menyebut path persisnya, Anda tambahkan ke ReadWritePaths, lalu lanjut. Inilah alasan Anda mengeraskan secara bertahap alih-alih menempel satu blok raksasa dan berharap.
Segelintir direktif nyaris tanpa biaya dan jarang merusak apa pun. Saya menerapkannya ke hampir setiap service bahkan sebelum memikirkan penyaringan syscall:
SystemCallFilter membatasi syscall kernel mana yang boleh dilakukan service, didukung oleh seccomp. Pendekatan pragmatisnya adalah mengizinkan grup @system-service yang telah dikurasi, yang mencakup kebutuhan daemon umum, lalu secara eksplisit menolak grup berbahaya seperti @privileged dan @mount. Ini adalah peningkatan skor tunggal terbesar untuk sebagian besar unit, dan juga yang paling mungkin merusak sesuatu yang halus, jadi ia diletakkan terakhir.
[Service]
# Allow the baseline set most services need
SystemCallFilter=@system-service
# Then subtract the dangerous ones (the ~ means deny)
SystemCallFilter=~@privileged @mount @reboot @swap
# Return EPERM instead of killing with SIGSYS — easier to debug
SystemCallErrorNumber=EPERM
# Restrict to the address families you actually use
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6Filter syscall yang terlalu ketat gagal dengan cara membingungkan — service start baik-baik saja, lalu hang atau crash hanya ketika menyentuh jalur kode langka berjam-jam kemudian. Uji di bawah beban nyata, awasi journal untuk penolakan seccomp, dan pertahankan SystemCallErrorNumber=EPERM selama rollout agar penolakan muncul sebagai error biasa alih-alih pembunuhan instan.
| Direktif | Apa fungsinya | Risiko kerusakan |
|---|---|---|
| ProtectSystem=strict | Seluruh filesystem read-only kecuali /dev, /proc, /sys | Sedang — perlu penyetelan ReadWritePaths |
| PrivateTmp=yes | Direktori temp terisolasi per service | Rendah |
| NoNewPrivileges=yes | Memblokir semua eskalasi hak akses | Rendah |
| PrivateDevices=yes | /dev minimal, tanpa akses perangkat keras mentah | Rendah hingga sedang |
| SystemCallFilter=@system-service | daftar-izin seccomp untuk syscall umum | Tinggi — uji di bawah beban nyata |
Setelah tiap perubahan, jalankan systemctl daemon-reload, restart service, pastikan ia benar-benar berfungsi, lalu skor ulang. Melihat unit bergerak dari 9.6 UNSAFE ke sekitar 2.0 OK itu memuaskan, tapi angka itu heuristik, bukan bukti. Ia hanya mengukur sandbox yang systemd sendiri terapkan — ia tak tahu apa pun tentang autentikasi aplikasi Anda, validasi input, atau rahasia yang dipegangnya. Sebuah daemon yang berjalan sebagai user non-root khusus dengan filesystem read-only dan tanpa syscall berbahaya jauh lebih sulit dipersenjatai bahkan jika skornya tak pernah menyentuh nol.
Padukan sandboxing dengan user sistem khusus: setel DynamicUser=yes (atau User= dan Group= tetap) agar service tak pernah berjalan sebagai root sejak awal. Digabung dengan pembatasan filesystem dan syscall di atas, itulah beda antara sebuah bug dan sebuah pembobolan.
# Full workflow, start to finish
sudo systemctl edit myapp.service # add the [Service] hardening block
sudo systemctl daemon-reload
sudo systemctl restart myapp.service
systemctl status myapp.service # confirm it is running
journalctl -u myapp.service -e # check for denials
systemd-analyze security myapp.service # re-score