Semua versi terbaru dari distribusi Linux paling populer menggunakan systemd
untuk mem-boot mesin dan mengelola layanan sistem. Systemd
menyediakan beberapa fitur untuk mempermudah memulai layanan dan lebih aman. Ini adalah kombinasi yang langka, dan artikel ini menunjukkan mengapa berguna untuk membiarkan systemd
mengelola sumber daya dan sandbox suatu layanan.
Pembenaran
Jadi, mengapa kita harus menggunakan systemd
untuk sandbox keamanan? Pertama, orang mungkin berargumen bahwa setiap bit dari fungsi ini sudah diekspos melalui alat yang ada dan terkenal, yang dapat ditulis dan digabungkan dengan cara yang sewenang-wenang. Kedua, terutama dalam hal program yang ditulis dalam C/C++ dan bahasa tingkat rendah lainnya, panggilan sistem yang sesuai dapat digunakan secara langsung, mencapai implementasi lean yang disesuaikan secara hati-hati dengan kebutuhan layanan tertentu.
Ada empat alasan utama:
1. Keamanan sulit. Implementasi terpusat di manajer layanan berarti bahwa layanan yang memanfaatkannya dapat disederhanakan secara signifikan. Tidak diragukan lagi, implementasi terpusat ini rumit, tetapi karena penggunaannya yang luas, itu diuji dengan baik. Jika kami menganggap bahwa itu digunakan kembali di ribuan layanan, keseluruhan kompleksitas sistem berkurang.
2. Dasar keamanan bervariasi antar sistem. Systemd
menghaluskan perbedaan antara arsitektur perangkat keras, versi kernel, dan konfigurasi sistem.
Fungsionalitas yang menyediakan pengerasan layanan diimplementasikan sejauh mungkin pada sistem tertentu. Misalnya, systemd
unit mungkin berisi konfigurasi AppArmor dan SELinux. Yang pertama digunakan pada sistem Ubuntu/Debian, yang kedua pada Fedora/RHEL/CentoOS, dan tidak juga untuk distribusi yang tidak mengaktifkan sistem MAC apa pun. Sisi lain dari fleksibilitas ini adalah bahwa fitur tersebut tidak dapat diandalkan sebagai hanya mekanisme penahanan (atau bahwa layanan tersebut hanya digunakan pada sistem yang mendukung semua fitur yang diperlukan).
3. Keamanan memerlukan mengutak-atik sistem tingkat rendah. Fitur yang disediakan oleh manajer layanan tidak bergantung pada bahasa implementasi layanan, sehingga mudah untuk menulis layanan dalam bahasa tingkat tinggi, misalnya shell atau Python atau apa pun yang nyaman, dan tetap menguncinya.
4. Keamanan membutuhkan hak istimewa. Ini adalah paradoks, tetapi hak istimewa diperlukan untuk menghilangkan hak istimewa. Misalnya, kita sering kali perlu menjadi root untuk menyiapkan namespace pemasangan khusus untuk membatasi tampilan sistem file. Sebagai contoh lain, daemon HTTP sering dimulai sebagai root hanya untuk dapat membuka port bernomor rendah dan port bernomor rendah dibatasi atas nama keamanan. Manajer layanan harus tetap berjalan dengan hak istimewa tertinggi, tetapi layanan tidak boleh, dan penyiapan pengerasan sering kali menjadi satu-satunya alasan untuk meminta hak istimewa yang lebih tinggi. Setiap bug dalam implementasi layanan pada fase ini bisa berbahaya. Dengan memindahkan penyiapan ke pengelola layanan, layanan dapat dimulai tanpa fase awal peningkatan hak istimewa ini.
Untuk menempatkan ini dalam konteks, Fedora 32 yang baru dirilis berisi hampir 1800 file unit berbeda untuk memulai layanan yang ditulis dalam C, C++, Python, Java, Ocaml, Perl, Ruby, Lua, Tcl, Erlang dan seterusnya - dan hanya satu systemd
.
[ Butuh lebih banyak tentang systemd? Unduh lembar contekan systemd untuk petunjuk lebih bermanfaat. ]
Beberapa cara yang setara untuk memulai layanan
Paling umum, systemd
layanan didefinisikan melalui file unit :file teks dalam format ini yang menyatakan perintah untuk dijalankan dan berbagai setelan. Setelah file unit ini diedit, systemctl daemon-reload
harus dipanggil untuk menyodok manajer untuk memuat pengaturan baru. Output dari daemon mendarat di jurnal dan perintah terpisah digunakan untuk melihatnya. Saat menjalankan perintah secara interaktif, semua itu sangat tidak nyaman. systemd-run
perintah memberitahu manajer untuk memulai perintah atas nama pengguna dan merupakan alternatif yang bagus untuk penggunaan interaktif. Perintah untuk mengeksekusi ditentukan mirip dengan sudo
. Argumen posisi pertama dan semuanya setelahnya adalah perintah yang sebenarnya, dan opsi sebelumnya diinterpretasikan oleh systemd-run
diri. systemd-run
perintah memiliki opsi untuk menentukan pengaturan tertentu seperti --uid
dan --gid
untuk pengguna dan grup. -E
opsi menyetel variabel lingkungan, sedangkan opsi "tangkap semua" -p
menerima pasangan key=value arbitrer yang mirip dengan file unit.
$ systemd-run whoami
Running as unit: run-rbd26afbc67d74371a6d625db78e33acc.service
$ journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
journalctl -u run-rbd26afbc67d74371a6d625db78e33acc.service
-- Logs begin at Thu 2020-04-23 19:31:49 CEST, end at Mon 2020-04-27 13:22:35 CEST. --
Apr 27 13:22:18 fedora systemd[1]: Started run-rbd26afbc67d74371a6d625db78e33acc.service.
Apr 27 13:22:18 fedora whoami[520662]: root
Apr 27 13:22:18 fedora systemd[1]: run-rbd26afbc67d74371a6d625db78e33acc.service: Succeeded.
systemd-run -t
menghubungkan input, output, dan aliran kesalahan standar dari perintah ke terminal pemanggilan. Ini bagus untuk menjalankan perintah secara interaktif (perhatikan bahwa proses layanan masih merupakan anak dari manajer).
$ systemd-run -t whoami
Running as unit: run-u53517.service
Press ^] three times within 1s to disconnect TTY.
root
Lingkungan yang konsisten
Sebuah unit selalu dimulai di lingkungan yang ditentukan dengan cermat. Saat kita memulai unit menggunakan systemctl
atau systemd-run
, perintah selalu dipanggil sebagai anak dari manajer. Lingkungan shell tidak mempengaruhi lingkungan di mana perintah layanan dijalankan. Tidak semua pengaturan yang dapat ditentukan dalam file unit didukung oleh systemd-run
, tetapi sebagian besar, dan selama kita tetap berpegang pada subset itu, pemanggilan melalui file unit dan systemd-run
setara. Faktanya, systemd-run
membuat file unit sementara dengan cepat.
Misalnya:
$ sudo systemd-run -M rawhide -t /usr/bin/grep PRETTY_NAME= /etc/os-release
Di sini, sudo
berbicara dengan PAM untuk mengizinkan eskalasi hak istimewa, dan kemudian mengeksekusi systemd-run
sebagai akar. Selanjutnya, systemd-run
membuat koneksi ke mesin bernama rawhide , di mana ia berbicara dengan manajer sistem (PID 1 dalam wadah) melalui dbus. Manajer memanggil grep
, yang melakukan tugasnya. grep
perintah mencetak ke stdout, yang terhubung ke terminal semu dari mana sudo
dipanggil.
Setelan keamanan
Pengguna dan pengguna dinamis
Tanpa basa-basi lagi, mari kita bicara tentang beberapa pengaturan khusus, dimulai dengan primitif paling sederhana dan paling kuat.
Pertama, mekanisme pemisahan hak istimewa tertua, paling dasar, dan mungkin paling berguna:pengguna. Anda dapat menentukan pengguna dengan User=foobar
di [Layanan] bagian dari file unit, atau systemd-run -p User=foobar
, atau systemd-run --uid=foobar
. Ini mungkin tampak jelas—dan di Android, setiap aplikasi memiliki penggunanya sendiri—tetapi di dunia Linux, kami masih memiliki terlalu banyak layanan yang tidak perlu dijalankan sebagai root.
Systemd
menyediakan mekanisme untuk membuat pengguna sesuai permintaan. Saat dipanggil dengan DynamicUser=yes
, nomor pengguna unik dialokasikan untuk layanan. Nomor ini memutuskan untuk nama pengguna sementara. Tugas ini tidak disimpan di /etc/passwd
, tetapi dihasilkan dengan cepat oleh modul NSS setiap kali nomor atau nama yang sesuai ditanyakan. Setelah layanan dihentikan, nomor tersebut mungkin akan digunakan kembali nanti untuk layanan lain.
Kapan pengguna statis biasa digunakan untuk layanan, dan kapan yang dinamis lebih disukai? Pengguna dinamis sangat bagus ketika identitas pengguna bersifat sementara, dan tidak diperlukan integrasi dengan layanan lain dalam sistem. Tetapi ketika kami memiliki kebijakan dalam database untuk mengizinkan akses pengguna tertentu, direktori yang dibagikan dengan grup tertentu, atau konfigurasi lain di mana kami ingin merujuk ke nama pengguna, pengguna dinamis mungkin bukan pilihan terbaik.
Pasang ruang nama
Secara umum, perlu diperhatikan bahwa systemd
seringkali hanya membungkus fungsionalitas yang disediakan oleh kernel. Misalnya, berbagai setelan yang membatasi akses ke hierarki sistem file, membuat bagian-bagiannya menjadi hanya-baca atau tidak dapat diakses, diselesaikan dengan mengatur sistem file yang sesuai di ruang nama pemasangan yang tidak dibagikan.
Beberapa pengaturan yang berguna diterapkan seperti ini. Dua yang paling berguna dan umum adalah ProtectHome=
dan ProtectSystem=
. Yang pertama menggunakan namespace mount yang tidak dibagikan untuk membuat /home
baik hanya-baca atau sama sekali tidak dapat diakses. Yang kedua adalah tentang melindungi /usr
, /boot
, dan /etc
.
Pengaturan ketiga yang juga berguna tetapi sangat spesifik adalah PrivateTmp=
. Ia menggunakan ruang nama mount untuk membuat direktori pribadi terlihat sebagai /tmp
dan /var/tmp
untuk layanan. File sementara layanan disembunyikan dari pengguna lain untuk menghindari masalah apa pun karena tabrakan nama file atau izin yang salah.
Tampilan sistem file dapat dikelola pada tingkat direktori individual melalui InaccessiblePaths=
, ReadOnlyPaths=
, ReadWritePaths=
, BindPaths=
, dan ReadOnlyBindPaths=
. Dua pengaturan pertama memberikan semua atau hanya akses tulis ke bagian hierarki sistem file. Yang ketiga adalah tentang memulihkan akses, yang berguna ketika kita ingin memberikan akses penuh hanya ke beberapa direktori tertentu jauh di dalam hierarki. Dua yang terakhir memungkinkan direktori bergerak, atau, lebih tepatnya berbicara, secara pribadi mengikat-mount mereka di lokasi yang berbeda.
Kembali ke subjek DynamicUser=yes
, pengguna sementara tersebut hanya mungkin jika layanan tidak diizinkan untuk membuat file permanen pada disk. Jika file tersebut terlihat oleh pengguna lain, mereka akan ditampilkan sebagai tidak memiliki pemilik, atau lebih buruk lagi, mereka dapat diakses oleh pengguna sementara baru dengan nomor yang sama, yang menyebabkan kebocoran informasi atau eskalasi hak istimewa yang tidak diinginkan. Systemd
menggunakan mount namespaces untuk membuat sebagian besar pohon sistem file tidak dapat ditulis ke layanan. Untuk mengizinkan penyimpanan permanen, direktori pribadi dipasang ke pohon sistem file yang terlihat oleh layanan.
Perhatikan bahwa perlindungan tersebut tidak tergantung pada mekanisme kontrol akses file dasar menggunakan kepemilikan file dan topeng izin. Jika sistem file dipasang hanya-baca, bahkan pengguna yang dapat memodifikasi file tertentu berdasarkan izin standar tidak dapat melakukannya sampai sistem file di-remount baca-tulis. Ini memberikan perlindungan terhadap kesalahan dalam manajemen file (bagaimanapun juga, tidak jarang bagi pengguna untuk mengatur topeng izin yang salah sesekali) dan merupakan lapisan strategi pertahanan yang mendalam.
Sumber daya yang diimplementasikan menggunakan ruang nama mount umumnya sangat efisien karena implementasi kernelnya efisien. Overhead saat menyiapkannya biasanya juga dapat diabaikan.
Pembuatan direktori otomatis untuk layanan
Fitur yang relatif baru yang systemd
menyediakan layanan adalah manajemen otomatis direktori. Jalur yang berbeda dari sistem file memiliki karakteristik penyimpanan yang berbeda dan tujuan penggunaan, tetapi mereka termasuk dalam beberapa kategori standar. FHS menetapkan bahwa /etc
adalah untuk file konfigurasi, /var/cache
adalah untuk penyimpanan tidak permanen, /var/lib/
adalah untuk penyimpanan semi permanen, /var/log
untuk log, dan /run
untuk file yang mudah menguap. Sebuah layanan sering membutuhkan subdirektori di setiap lokasi tersebut. Systemd
menyetelnya secara otomatis, seperti yang dikontrol oleh ConfigurationDirectory=
, CacheDirectory=
, StateDirectory=
, LogsDirectory=
, dan RuntimeDirectory=
pengaturan. Pengguna memiliki direktori tersebut. Direktori runtime dihapus oleh manajer saat layanan berhenti. Ide umumnya adalah untuk mengikat keberadaan aset sistem file tersebut dengan masa pakai layanan. Mereka tidak perlu dibuat sebelumnya dan dibersihkan dengan tepat setelah layanan berhenti berjalan.
$ sudo systemd-run -t -p User=user -p CacheDirectory=foo -p StateDirectory=foo -p RuntimeDirectory=foo -p PrivateTmp=yes ls -ld /run/foo /var/cache/foo /var/lib/foo /etc/foo /tmp/
Running as unit: run-u45882.service
Press ^] three times within 1s to disconnect TTY.
drwxr-xr-x 2 user user 40 Apr 26 08:21 /run/foo ← automatically created and removed
drwxr-xr-x 2 user user 4096 Apr 26 08:20 /var/cache/foo ← automatically created
drwxr-xr-x. 2 user user 4096 Nov 13 21:50 /var/lib/foo ← automatically created
drwxr-xr-x. 2 root root 4096 Nov 13 21:50 /etc/foo ← automatically created, but not owned by the user, since the service (usually) shall not modify its own configuration
drwxrwxrwt 2 root root 40 Apr 26 08:21 /tmp/ ← "sticky bit" is set, but this directory is not the one everyone else sees
Tentu saja, ketujuh lokasi tersebut (menghitung PrivateTmp=
sebagai dua) tidak mencakup kebutuhan setiap layanan, tetapi mereka harus cukup untuk sebagian besar situasi. Untuk kasus lain, pengaturan manual atau konfigurasi yang sesuai di tmpfiles.d
selalu menjadi pilihan.
Manajemen direktori otomatis terikat baik dengan DynamicUser=
pengaturan dan pengguna yang dibuat secara otomatis, dengan menyediakan layanan yang berjalan sebagai pengguna terpisah dan tidak diizinkan untuk mengubah sebagian besar hierarki sistem file (meskipun izin akses file mengizinkannya). Layanan masih dapat mengakses direktori tertentu dan menyimpan data di sana, tanpa pengaturan apa pun selain konfigurasi file unit.
Misalnya, layanan web Python dapat dijalankan sebagai:
$ systemd-run -p DynamicUser=yes -p ProtectHome=yes -p StateDirectory=webserver --working-directory=/srv/www/content python3 -m http.server 8000
atau melalui file unit yang setara:
[Service]
DynamicUser=yes
ProtectHome=yes
StateDirectory=webserver
WorkingDirectory=/srv/www/content
ExecStart=python3 -m http.server 8000
Kami memastikan bahwa layanan berjalan sebagai pengguna sementara tanpa kemampuan untuk mengubah sistem file atau memiliki akses apa pun ke data pengguna.
Pengaturan yang dijelaskan di sini dapat dianggap "tingkat tinggi". Meskipun implementasinya mungkin rumit, konsepnya sendiri mudah dipahami, dan efeknya pada layanan jelas. Ada banyak setelan lain untuk menghilangkan berbagai izin dan kemampuan, mengunci protokol jaringan dan pengaturan kernel, dan bahkan menonaktifkan panggilan sistem individual. Ini berada di luar cakupan artikel singkat ini. Lihat dokumentasi referensi ekstensif.
Memanfaatkan semua ini
Ketika kita memiliki pemahaman yang baik tentang apa yang dilakukan dan dibutuhkan oleh layanan, kita dapat mempertimbangkan hak istimewa apa yang diperlukan dan apa yang dapat kita ambil. Kandidat yang jelas berjalan sebagai pengguna yang tidak memiliki hak istimewa dan membatasi akses ke data pengguna di bawah /home
. Semakin kami mengizinkan systemd
untuk mengatur segalanya bagi kita (misalnya, dengan menggunakan StateDirectory=
dan teman-teman), semakin besar kemungkinan layanan dapat berhasil dijalankan sebagai pengguna yang tidak memiliki hak istimewa. Seringkali layanan membutuhkan akses ke subdirektori tertentu, dan kami dapat mencapainya dengan menggunakan ReadWritePaths=
dan pengaturan serupa.
Menambahkan langkah-langkah keamanan dengan cara otomatis apa pun tidak mungkin. Tanpa pemahaman yang baik tentang apa yang dibutuhkan layanan dalam skenario konfigurasi yang berbeda dan untuk operasi yang berbeda, kami tidak dapat mendefinisikan kotak pasir yang berguna. Artinya, sandbox layanan paling baik dilakukan oleh penulis atau pengelolanya.
Evaluasi dan status quo
Jumlah kemungkinan pengaturan besar, dan yang baru ditambahkan dengan setiap rilis systemd
. Menjaga dengan itu sulit. Systemd
menyediakan alat untuk mengevaluasi penggunaan arahan sandboxing dalam file unit. Hasilnya harus dianggap sebagai petunjuk — lagi pula, seperti yang disebutkan di atas, pembuatan kebijakan keamanan secara otomatis itu sulit, dan evaluasi apa pun hanya menghitung apa yang digunakan dan apa yang tidak, tanpa pemahaman mendalam tentang apa yang penting untuk layanan tertentu.
$ systemd-analyze security systemd-resolved.service
NAME DESCRIPTION EXPOSURE
...
✓ User=/DynamicUser= Service runs under a static non-root user identity
✗ DeviceAllow= Service has a device ACL with some special devices 0.1
✓ PrivateDevices= Service has no access to hardware devices
✓ PrivateMounts= Service cannot install system mounts
PrivateTmp= Service runs in special boot phase, option does not apply
✗ PrivateUsers= Service has access to other users 0.2
ProtectHome= Service runs in special boot phase, option does not apply
✓ ProtectKernelLogs= Service cannot read from or write to the kernel log ring buffer
✓ ProtectKernelModules= Service cannot load or read kernel modules
✓ ProtectKernelTunables= Service cannot alter kernel tunables (/proc/sys, …)
ProtectSystem= Service runs in special boot phase, option does not apply
✓ SupplementaryGroups= Service has no supplementary groups
...
→ Overall exposure level for systemd-resolved.service: 2.1 OK 🙂
$ systemd-analyze security httpd.service
NAME DESCRIPTION EXPOSURE
...
✗ User=/DynamicUser= Service runs as root user 0.4
✗ DeviceAllow= Service has no device ACL 0.2
✗ PrivateDevices= Service potentially has access to hardware devices 0.2
✓ PrivateMounts= Service cannot install system mounts
✓ PrivateTmp= Service has no access to other software's temporary files
✗ PrivateUsers= Service has access to other users 0.2
✗ ProtectHome= Service has full access to home directories 0.2
✗ ProtectKernelLogs= Service may read from or write to the kernel log ring buffer 0.2
✗ ProtectKernelModules= Service may load or read kernel modules 0.2
✗ ProtectKernelTunables= Service may alter kernel tunables 0.2
✗ ProtectSystem= Service has full access to the OS file hierarchy 0.2
SupplementaryGroups= Service runs as root, option does not matter
...
→ Overall exposure level for httpd.service: 9.2 UNSAFE 😨
Sekali lagi, ini tidak berarti bahwa layanan tidak aman, tetapi tidak menggunakan systemd
primitif keamanan.
Melihat tingkat distribusi secara keseluruhan:
$ systemd-analyze security '*'
Kami melihat bahwa sebagian besar layanan mendapat skor sangat tinggi (mis., Buruk). Kami tidak dapat mengumpulkan statistik semacam itu tentang berbagai layanan internal, tetapi tampaknya masuk akal untuk berasumsi bahwa mereka serupa. Tentu saja ada banyak buah yang menggantung rendah, dan menerapkan beberapa sandbox yang relatif sederhana akan membuat sistem kami lebih aman.
Selesai
Membiarkan systemd
manage services dan sandboxing bisa menjadi cara yang bagus untuk menambahkan lapisan keamanan ke server Linux Anda. Pertimbangkan untuk menguji konfigurasi di atas untuk melihat apa yang mungkin bermanfaat bagi organisasi Anda.
Dalam artikel ini, kami sengaja menghindari penyebutan jaringan. Ini karena angsuran kedua akan berbicara tentang aktivasi soket dan sandboxing layanan menggunakan jaringan.
[ Jangan lupa untuk memeriksa lembar contekan systemd untuk petunjuk lebih bermanfaat. ]