Mengamankan service unit dengan sandboxing seccomp bawaan systemd: SystemCallFilter, SystemCallArchitectures, dan RestrictAddressFamilies. Dipadukan dengan NoNewPrivileges dan ProtectSystem, penguatan dilakukan dari unit file tanpa mengubah kode aplikasi.

Di episode 8 kalian mengunci container lewat profil JSON. Tapi tidak semua aplikasi hidup di container — banyak daemon penting (database, service kecil, tool internal) berjalan langsung di host sebagai service systemd. Untuk mereka, menulis profil JSON tidak praktis; yang dibutuhkan adalah cara mengunci syscall langsung dari unit file.
Episode 9 ini membahas sandboxing seccomp bawaan systemd: SystemCallFilter, SystemCallArchitectures, dan RestrictAddressFamilies, dipadukan dengan NoNewPrivileges dan ProtectSystem. Keunggulannya tegas: kalian memperketat service tanpa mengubah kode aplikasi, cukup dari file unit.
systemd tidak mengirim syscall sendiri — ia menerjemahkan direktif unit menjadi filter BPF yang dimuat ke kernel saat service dimulai. SystemCallFilter=@system-service bukan sekadar teks; di belakang layar systemd membangun program BPF yang sama persis seperti yang kalian tulis dengan libseccomp, lalu menerapkannya pada proses service.
Ada satu kunci fundamental yang membuat seluruh sandboxing ini bisa dicabut: no_new_privs. systemd menetapkannya lewat NoNewPrivileges=true — flag kernel yang memastikan proses tidak bisa menaikkan privilege (misalnya lewat setuid binary) dan sekaligus memungkinkan filter seccomp tetap berlaku tanpa hak istimewa. Tanpa itu, filter yang menolak setuid bisa di-bypass.
Note
Perhatikan: systemd menerapkan sejumlah syscall yang dianggap merusak untuk semua service secara default, bahkan sebelum kalian menyentuh SystemCallFilter. Direktif ini bukan fitur opsional yang "kadang-kadang aktif" — ia bekerja terus-menerus, dan kalian memperluasnya sesuai kebutuhan.
Direktif SystemCallFilter menerima daftar syscall dan/atau grup yang diawali @. Tanpa prefiks ~, direktif berarti allowlist (hanya syscall itu yang boleh); dengan prefiks ~, berarti denylist:
[Service]
# Allowlist: hanya syscall dalam grup ini yang boleh dipanggil
SystemCallFilter=@system-service
# Denylist: semua boleh, kecuali grup yang diberi tanda ~
SystemCallFilter=~@reboot @swap @obsoleteGrup dengan awalan @ mempermudah hidup kalian — satu nama mewakili puluhan syscall. Yang paling sering dipakai:
@system-service — set syscall yang wajar untuk service umum; titik awal yang aman untuk kebanyakan daemon.@file-system — operasi filesystem seperti openat, unlink, rename.@network-io — syscall jaringan seperti socket, connect, accept.@privileged — syscall yang butuh privilege, sebagian besar tidak dibutuhkan service biasa.@obsolete, @reboot, @swap — kandidat utama untuk diblokir.Saat syscall diblokir, service menerima errno EPERM secara default. Kalian bisa mengubahnya dengan SystemCallErrorNumber=. Nilai defaultnya sudah masuk akal untuk sebagian besar kasus.
Tip
Mulailah dengan SystemCallFilter=@system-service sebagai allowlist, lalu jalankan service di staging. Bila service memanggil syscall yang tidak ada di grup, systemd mencatat kegagalannya di log service — baca dengan journalctl -u nama.service dan tambahkan syscall atau grup yang hilang. Pola log-dulu-tingkatkan ini sama persis dengan alur episode 7.
Direktif SystemCallArchitectures mengontrol arsitektur syscall yang boleh dipanggil service. Nilai defaultnya native — hanya arsitektur asli kernel yang diizinkan. Ini penting karena kernel 64-bit bisa menerima syscall dari binary 32-bit (mode compat), dan celah di jalur compat itu pernah menjadi sumber bug keamanan:
[Service]
SystemCallArchitectures=nativeSelama service kalian tidak perlu menjalankan binary 32-bit, biarkan nilai ini. Setiap syscall dari arsitektur lain langsung ditolak tanpa peduli aturan SystemCallFilter — pagar luar sebelum pagar dalam.
Kembali ke episode 5: seccomp bisa menyaring argumen syscall, dan socket() punya argumen pertama — address family. systemd mengabstraksikannya dengan RestrictAddressFamilies:
[Service]
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6Service hanya bisa membuka socket dengan keluarga yang terdaftar. AF_PACKET (raw packet), AF_NETLINK (komunikasi dengan kernel), dan AF_UNIX yang tidak diizinkan semuanya diblokir. Untuk daemon yang hanya melayani TCP, daftar singkat ini sudah lebih dari cukup — dan memotong jalur eksfiltrasi data lewat protokol lain.
Sekarang gabungkan semua direktif seccomp dengan sandboxing filesystem systemd dalam satu unit yang realistis:
[Unit]
Description=Layanan webapp yang diperketat
[Service]
ExecStart=/usr/local/bin/webapp
User=webapp
Group=webapp
Restart=on-failure
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
RestrictSUIDSGID=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@reboot @swap @obsolete
SystemCallErrorNumber=EPERM
[Install]
WantedBy=multi-user.targetMembaca unit ini lapis demi lapis:
NoNewPrivileges=true — kunci utama; tanpa ini, sebagian direktif seccomp tidak bisa dijamin.ProtectSystem=strict — seluruh / menjadi read-only untuk service; hanya path yang diizinkan eksplisit yang bisa ditulis.ProtectHome=true — direktori home tidak terlihat.RestrictAddressFamilies, SystemCallArchitectures, SystemCallFilter — tiga lapis seccomp: keluarga socket, arsitektur, dan daftar syscall.Perhatikan yang tidak berubah: ExecStart sama seperti biasa, aplikasi tidak tahu apa-apa. Seluruh penguatan datang dari unit file — ini kekuatan utama pendekatan systemd.
Mengklaim unit lebih aman itu mudah; membuktikannya butuh alat. systemd-analyze security menilai paparan unit dan menunjukkan direktif mana yang sudah diterapkan:
systemd-analyze security webapp.serviceContoh keluaran (dipotong):
NAME DESCRIPTION EXPOSURE
PrivateNetwork=no Service has access to network 0.5
RestrictAddressFamilies=yes Address family restricted 0.1
SystemCallFilter=yes System call allowlisted 0.1
NoNewPrivileges=yes Privilege escalation prevented 0.1
→ Overall exposure level for webapp.service: 1.2Semakin rendah skor eksposure, semakin tertutup service-nya. Verifikasi juga status aktual service dengan systemctl show webapp.service untuk memastikan direktif benar-benar aktif, bukan hanya tertulis.
Warning
Sandboxing systemd bukan alasan untuk mengabaikan seccomp lain. ProtectSystem dan SystemCallFilter memeriksa dimensi yang berbeda: satu membatasi akses filesystem, satu membatasi aksi terhadap kernel. Kombinasikan keduanya untuk defense in depth — seperti memasang kunci pintu dan alarm secara bersamaan, bukan memilih salah satu.
Beberapa hal yang menyelamatkan kalian di produksi:
SystemCallFilter satu level per rilis. Service yang berhenti setelah perketatan adalah sinyal syscall yang hilang, bukan alasan untuk menyerah.strace -f -c sebelum menyusun SystemCallFilter — hasilnya jadi peta syscall yang sebenarnya.@system-service + tambahan) lebih aman. Denylist (~@reboot dll.) hanya untuk menambal yang jelas-jelas berbahaya.Di episode 9 ini kalian telah memahami bahwa systemd menerjemahkan direktif unit menjadi filter seccomp: SystemCallFilter untuk daftar syscall dan grup @, SystemCallArchitectures=native untuk menutup jalur arsitektur compat, dan RestrictAddressFamilies untuk mengunci keluarga socket. Dipadukan dengan NoNewPrivileges dan ProtectSystem, satu file unit bisa mengubah daemon biasa menjadi service yang jauh lebih sulit ditembus — tanpa menyentuh kode aplikasi.
Kunci yang harus dibawa pulang:
SystemCallArchitectures=native adalah pagar luar yang hampir selalu gratis.systemd-analyze security, bukan sekadar klaim di unit file.Semua lapisan sejauh ini diterapkan per-mesin: per container, per service. Bagaimana mengelolanya ketika ada ratusan mesin? Di episode 10, kita masuk ke Seccomp di Kubernetes: field seccompProfile di securityContext, evolusi dari annotation alpha, Pod Security Standards, dan distribusi profil kustom antar node.