deniz.in

Piyasalar

Hava durumu

Hava durumu yükleniyor

· kaynak dev.to (home feed)

Rootless Rust container engine boxr, namespace ve uid mapping tasarımını anlatıyor

boxr projesinin dev.to'daki yazısı, Rust ile yazılmış OCI engine'in container'ları rootsuz nasıl izole ettiğini açıklıyor: tek iş parçacıklı bir trampoline süreci ve ebeveyn süreç tarafından yazılan uid haritaları kullanılıyor.

Rootless Rust container engine boxr, namespace ve uid mapping tasarımını anlatıyor

Rust ile yazılmış rootless bir OCI container engine olan boxr, container'ları bir daemon, sudo ya da setuid yardımcısı olmadan nasıl izole ettiğini anlatan bir yazı yayımladı. dev.to'daki yazı, boxr run komutunun verilmesinden container sürecinin başlamasına kadar geçen her Linux adımını ele alıyor. Temel ilke şu: önce user namespace'e girin, kalan tüm kurulumu onun içinden yürütün.

İş parçacıkları neden bariz yaklaşımı engelliyor

dev.to yazısına göre boxr CLI'ı çok iş parçacıklı bir tokio uygulaması ve Linux kernel, halihazırda iş parçacığı bulunan herhangi bir süreçten unshare(CLONE_NEWUSER) çağrısını EINVAL döndürerek reddediyor. Tokio, işçi iş parçacıklarını uygulama kodu çalışmadan önce başlattığı için CLI'ın kendisi hiçbir zaman user namespace oluşturamaz.

Çözüm bir yeniden çalıştırma (re-exec). Async runtime başlamadan önce boxr, kendisini dahili bir trampoline alt süreci olarak tekrar başlatır. O süreç tek iş parçacıklıdır, dolayısıyla user namespace'i sorunsuz şekilde unshare edebilir ve tüm namespace kurulumu, herhangi bir async runtime var olmadan, orada düz sıralı kod olarak çalışır. Yazar, bunun namespace kurulumunu async runtime ile karıştırmaktan doğan sorunların tümünden kaçındığını savunuyor.

uid mapping için iki süreçli el sıkışma

Bir alt süreç user namespace'i unshare ettiğinde o namespace içinde root olur, ancak bir uid mapping olmadan hiçbir şey başaramaz ve haritaları yazma izni yalnızca ebeveyndedir. Bu yüzden trampoline fork yapar: alt süreç CLONE_NEWUSER ile unshare ederken iki süreç, ebeveyn /proc/[pid]/uid_map dosyasını yazarken bir Unix socketpair üzerinden basit bir hazır/tamam mesaj alışverişiyle senkronize olur.

Yazıya göre iki mapping yolu var. newuidmap ve newgidmap kuruluysa ve /etc/subuid ile /etc/subgid kullanıcı için aralıklar tanımlıysa bu araçlar kullanılır: container uid 0 host kullanıcısına, 1'den yukarıdaki container uid'leri ise subordinate aralığına eşlenir. Aksi halde boxr haritaları doğrudan yazar — tek bir girdiyle uid 0'ı host uid'sine eşler ve bunu, setgroups'a "deny" gönderdikten sonra yapar; kernel, bir gid_map'i kabul etmeden önce bu adımı zorunlu kılar.

Önce ağ, sonra kalan namespace'ler

Namespace içinde root hakkı sağlandıktan sonra alt süreç sıradaki olarak network namespace'i unshare eder — bilinçli olarak diğerlerinden önce — böylece ebeveyn, namespace topolojisi daha karmaşık hale gelmeden önce bir pasta süreci bağlayabilir ya da saf Rust ile yazılmış usernet TAP engine işçisini başlatabilir.

Ardından alt süreç PID, mount, UTS ve IPC namespace'lerini unshare eder. Cgroup namespace annotation yoluyla isteğe bağlıdır; IPC ve UTS da aynı şekilde host'ta bırakılabilir. İkinci bir fork, torun süreci yeni PID namespace'inde PID 1 yapar. Oradan itibaren engine rootfs'yi kendisine bind-mount eder (pivot_root bir mountpoint gerektirir), chroot'a geri düşerek pivot_root çağırır ve container init'i çalıştırır. Yazar, zincirdeki hiçbir adımın host'ta root yetkileriyle yürütülmediğini vurguluyor.

Projenin kabul ettiği sınırlar

Yazı, tasarımın neyi sunmadığı konusunda açık sözlü. Subuid/subgid yapılandırılmamışsa container tek bir eşlenmiş uid görür; bu geliştirme container'larına uyar ama tam bir çok kullanıcılı mapping'in gerisinde kalır. Rootless ağ, veth çifti yerine pasta veya kullanıcı modu TAP engine üzerinden gider; bu yüzden ham socket'ler ve bazı paket türleri farklı davranır. Ayrıca anlatılan yol yalnızca Linux'a özgüdür; macOS ve Windows runtime'ları Virtualization.framework ve WSL2 üzerinden tamamen farklı yollar izler.

Meraklı okuyucular trampoline ve namespace sıralamasını kchaitanya863/boxr deposundaki src/runtime/linux.rs içinde, uid/gid mapping stratejisini ise src/security/mod.rs içinde bulabilir.

Neden önemli

Rootless container'lar, container kullanımının en keskin sorunlarından birini ortadan kaldırır: ayrıcalıklı bir daemon'a bağımlılığı. Yazı hem bir uygulama notu hem de çoğu container kullanıcısının asla görmediği mekanizmaların özlü bir açıklaması olarak iş görüyor — uid haritalarının neden var olduğu, bir gid_map kabul edilmeden önce setgroups'un neden reddedilmesi gerektiği, pivot_root'un neden bir mountpoint talep ettiği. Ayrıca namespace isteyen herhangi bir async runtime'ın çözmesi gereken bir kısıtı belgeliyor: kernelın iş parçacıklı süreçlerden unshare etmeyi reddetmesini; ve yeniden çalıştırmanın bunun temiz bir yanıtı olduğunu gösteriyor. Tek uid'e geri düşme konusundaki ve ağ farklarındaki açıklık, potansiyel kullanıcıların genç bir runtime'ın iş yüklerine uyup uymadığını değerlendirmesini sağlayan türden bir ayrıntı.

  • #rust
  • #containers
  • #linux
  • #namespaces
  • #open-source

İlgili yazılar