麒麟學堂:爲解決Linux系統桌面應用而生的Flatpackshell
前言:隨着Linux操做系統桌面應用生態的日益豐富,Linux系統軟件包之間的依賴問題,系統和應用之間的緊耦合,應用和應用之間的依賴庫緊耦合,有時會由於底層庫的升級引發系統和應用不穩定甚至沒法使用的問題。安全
而FlatPack就是爲了解決應用之間的隔離而生的技術,經過Linux內核的cgroups技術,FlatPack是經過OsTree來解決應用多版本的問題。經過把應用之間隔離在沙盒以內,保證不一樣應用之間或者應用不一樣版本之間徹底在文件系統上進行隔離,這樣就完全解決應用之間升級互相影響的問題,並且這兩種技術經過引入應用程序運行時的runtime接口,保證了系統和應用之間有一個穩定的接口,從側面也保證了系統和應用之間的依賴分離。bash
Flatpak(前世爲xdg-app) 是一種用於構建,分發,安裝和運行應用程序的技術。它主要針對的是Linux桌面,經過在沙箱中隔離應用程序來提升Linux桌面的安全性,容許應用程序安裝在任何Linux發行版上。網絡
歷史:app
2013: 在GNOME Developer Experience hackfest, Brussels大會後,萌生在GNOME中使用應用程序容器技術的念頭,次年開始開發。less
2016年5月: 第一個主版本xdg-app發佈。dom
6月:重命名爲flatpak。socket
8月:endless OS 3.0, 第一個默認支持Flatpak的發行版。工具
11月:ClearLinux聲明採用flatpak。ui
2017年2月: 最新的flatpak已經能夠在Arch, Debian, Fedora, Gentoo, Mageia, openSUSE, Ubuntu等的最新版本上運行。
基本概念:
運行時(runtimes)
「運行時」提供應用程序所需的基本依賴。有各類各樣的「運行時」,好比「Freedesktop運行時」,「GNOME 運行時」。「Freedesktop運行時」包含一系列必要的庫和服務,包括D-Bus, GLib, PulseAudio, X11和Wayland 等。「GNOME運行時」基於「FreeDesktop運行時」,增長了一些GNOME平臺相關的庫,好比GStreamer, GTK+, GVFS等。KDE runtime正在開發中。必須針對運行時構建每一個應用程序,而且必須在主機系統上安裝此運行時才能運行應用程序。用戶能夠同時安裝多個不一樣的運行時。
每個運行時能夠看作一個’/usr’ 文件系統,當程序運行時,它的運行時掛載在‘/usr’上。
捆綁庫(Bundled libraries)
當一個程序須要的依賴不在運行時中,使用捆綁庫來綁定這些依賴到程序上。
SDK(軟件開發套件)
SDK也是一個「運行時」,是用於構建應用程序的特殊類型的運行時,它包含了構建和打包工具(‘devel’ parts),如頭文件,編譯器和調試器。一般,SDK與「運行時」配對,由應用程序使用。
擴展(Extensions)
一 個擴展是對於運行時或程序的可選插件,通常用於把translations和debug信息從運行時分離出來,好比, org.freedesktop.Platform.Locale 能夠追加到org.freedesktop.Platform運行時上用來添加翻譯。
沙箱(Sandbox)
使用Flatpak,每一個應用程序都是在孤立的環境中構建和運行的。默認狀況下,應用程序只能「查看」自身及其「運行時」,訪問用戶文件,網絡,graphics sockets,總線和設備上的子系統必須明確授予權限,訪問其餘內容(如其餘進程)是不容許的。(能夠經過Portals機制在沙箱內訪問外面系統,好比打印,截圖等)
原理:
Flatpak主要使用了以下技術:
1. bubblewrap: 依賴它做爲沙箱的底層實現, 限制了應用程序訪問操做系統或用戶數據的能力,而且提供了非特權用戶使用容器的能力。
2. Systemd: 將各個subsystem和cgroup樹關聯並掛載好,爲沙箱建立 cgroups。
3. D-Bus: 爲應用程序提供高層APIs。
4. 使用Open Container Initiative的OCI格式做爲單文件的傳輸格式,方便傳輸。
5. 使用OSTree系統用於版本化和分發文件系統樹。
6. 使用Appstream 元數據,使得Flatpak應用程序在軟件中心能夠完美呈現出來。
而其中最重要的當屬bubblewrap,它是整個應用沙箱構建的關鍵,主要利用了以下內核特性:
Namespaces:
命名空間是對全局系統資源的一個封裝隔離,使得處於不一樣namespace的進程擁有獨立的全局系統資源,改變一個namespace中的系統資源只會影響當前namespace裏的進程,對其餘namespace中的進程沒有影響。它控制了進程的可見範圍,例如網絡、掛載點、進程等等。同時使得非特權用戶能夠建立沙箱。它有如下幾類:
● Mount namespace (CLONE_NEWNS):
用來隔離文件系統的掛載點, 使得不一樣的mount namespace擁有本身獨立的掛載點信息,不一樣的namespace之間不會相互影響,這對於構建用戶或者容器本身的文件系統目錄很是有用。bubblewrap 老是建立一個新的mount namespace, root掛載在tmpfs上,用戶能夠明確指定文件系統的哪一個部分在沙盒中是可見的。
● User namespaces (CLONE_NEWUSER):
用來隔離用戶權限相關的Linux資源,包括用戶ID 和組ID, 在 不一樣的user namespace中,一樣一個用戶的user ID 和group ID能夠不同,換句話說,一個用戶能夠在父 user namespace中是普通用戶,在子user namespace中是超級用戶(超級用戶只相對於子user namespace所擁有的資 源,沒法訪問其餘user namespace中須要超級用戶才能訪問資源)。
● IPC namespaces (CLONE_NEWIPC):
沙箱會獲得全部不一樣形式的IPCs的一份拷貝,好比SysV 共享內存和信號量等。
● PID namespaces (CLONE_NEWPID):
用來隔離進程的ID空間,沙箱內的程序看不見任何沙箱外的進程,此外, bubblewrap 會運行一個pid爲1的程序在容器中,用來處理回收子進程的需求。
● Network namespaces (CLONE_NEWNET):
用來隔絕網絡,在它本身的network namespace中只有一個迴環設備。
● UTS namespace (CLONE_NEWUTS):
容許沙箱擁有本身獨立的hostname和domain name.
Cgroups:
cgroup和namespace相似,也是將進程進行分組,但它的目的和namespace不同,namespace是爲了隔離進程組之間的資源,而cgroup是爲了對一組進程進行統一的資源監控和限制。
Bind Mount:
將一個目錄(或文件)中的內容掛載到另外一個目錄(或文件)上。
Seccomp rules:
Linux kernel 所 支持的一種簡潔的sandboxing機制。它能使一個進程進入到一種「安全」運行模式,該模式下的進程只能調用4種系統調用 (system calls),即read(), write(), exit()和sigreturn(),不然進程便會被終止。
同時,bubblewrap 使用PR_SET_NO_NEW_PRIVS 關閉 setuid 二進制程序。
當一個進程或其子進程設置了PR_SET_NO_NEW_PRIVS 屬性,則其不能訪問一些沒法share的操做,如setuid, 和chroot。
實驗:
接下來,咱們經過以下方式進入到一個flatpak建立的沙箱中:
安裝程序所需的「運行時」和Sdk:
$ flatpak remote-add --from gnome https://sdk.gnome.org/gnome.flatpakrepo
$ flatpak install gnome org.gnome.Platform//3.24 org.gnome.Sdk//3.24
安裝gedit:
$ flatpak remote-add --from gnome-apps https://sdk.gnome.org/gnome-apps.flatpakrepo
$ flatpak install gnome-apps org.gnome.gedit
建立一個‘devel sandbox’中的shell:
$ flatpak run --devel --command=bash org.gnome.gedit
能夠看到此沙箱中有3個進程,且flatpak-bwrap pid爲1。
$ ps
PID TTY TIME CMD
1 ? 00:00:00 flatpak-bwrap
2 ? 00:00:00 bash
5 ? 00:00:00 ps
查看當前進程所屬的namespace,括號裏的數字標識不一樣的namespace:
$ ls -l /proc/&&/ns
total 0
15:48 cgroup -> cgroup:[4026531835]
15:48 ipc -> ipc:[4026531839]
15:48 mnt -> mnt:[4026532241]
15:48 net -> net:[4026532244]
15:48 pid -> pid:[4026532242]
15:48 user -> user:[4026532371]
15:48 uts -> uts:[4026531838]
而後在主機中打開另外一個終端,查看主機中當前進程的namespace:
$ ls /proc/&&/ns
total 0
15:56 cgroup -> cgroup:[4026531835]
15:56 ipc -> ipc:[4026531839]
15:56 mnt -> mnt:[4026531840]
15:56 net -> net:[4026531957]
15:56 pid -> pid:[4026531836]
15:56 user -> user:[4026531837]
15:56 uts -> uts:[4026531838]
能夠看到沙箱中的進程所屬的namespace與主機環境下進程的namespace相比,它們的mount ,net, pid, user namespace不一樣,這時咱們在主機環境下把ping文件拷貝進主目錄(gedit聲明瞭對’/home’的訪問權限),而後在sandbox shell中執行ping 192.168.0.1,會發現報錯:
$ ./ping 192.168.0.1
ping: socket: Operation not permitted
由於gedit沒有申請網絡權限,它和主機在不一樣的network namespaces中。