歡迎您光臨本站 註冊首頁

安全 Linux 容器實現指南

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  
輕量級容器 又稱作 Virtual Private Servers (VPS) 或 Jails,它們是經常用於限制不可信應用程序或用戶的工具。但是最近構造的輕量級容器沒有提供充分的安全保證。使用 SELinux 或 Smack 策略增強這些容器之後,就可以在 Linux® 中實現更加安全的容器。本文介紹如何創建受 Linux 安全模塊保護的更加安全的容器。SELinux 和 Smack 策略的開發都在進行當中,並且在各自社區的幫助下不斷得到改善。

人們聽到容器時的第一反應是 “如何才能創建安全的容器?”。本文通過使用 Linux 安全模塊(Linux Security Modules,LSM)增強容器的安全性來解決這個問題。本文特別演示了如何設定安全目標,並通過 Smack 和 SELinux 安全模塊實現目標。

要了解 Linux 容器的背景知識,請閱讀 “LXC:Linux 容器工具”(developerWorks,2009 年 2 月)。

Linux 容器是根據幾種 Linux 技術構建的概念性工件:

  • 資源名稱空間 允許在容器內部查找進程、文件、SYSV IPC 資源、網路介面等等。
  • 控制組(Control groups)允許限制放置到容器中的資源。
  • 功能綁定(Capability bounding)設置 限制容器的訪問特權。

必須協調使用這些技術,以實現符合設想的容器。目前已有兩個項目提供這個功能:

  • Libvirt 是能夠使用 Xen 管理程序、qemu 模擬器、kvmis 甚至是輕量級容器創建虛擬機的大型項目。
  • Liblxc 是一個較小的庫和用戶空間命令集合,它們的目的之一是幫助內核開發人員快速輕鬆地測試容器的功能。

因為 “LXC:Linux 容器工具” 是基於 liblxc 編寫的,所以我在這裡繼續使用 liblxc;不過這裡完成的操作也能夠使用 libvirt 的容器支持輕鬆完成。

主要元素 1:LSM

在開始之前,如果不太了解 LSM,現在可以快速瀏覽一下。根據 Wikipedia 中的定義:Linux Security Modules (LSM) 是一個允許 Linux 內核支持各種計算機安全模型的框架,同時也避免依賴於特定安全實現。這個框架由 GNU General Public License 條款授權使用,並且是 Linux 2.6 之後的 Linux 內核的標準部分。設計 LSM 的目的是為成功實現強制訪問控制模塊提供一切必要元素,同時最小化對 Linux 內核的更改。LSM 避免了 Systrace 中的系統調用插入,因為它不支持多處理器內核,並且容易受 TOCTTOU (race) 攻擊。相反,當某個用戶級別的系統將要訪問重要的內部內核對象(比如 inode 和任務控制塊)時,LSM 將在內核中插入 “鉤子(hook)”(向上調用模塊)。這個項目專門用於解決訪問控制問題,以避免對主流內核進行大量的複雜修改。該項目並不打算成為通用的 “鉤子” 或 “向上調用” 機制,也不支持虛擬化。LSM 訪問控制的目標與解決系統審計問題密切相關,但又有所區別。審計要求記錄每次訪問嘗試。LSM 不能解決這個問題,因為這需要大量的鉤子,以檢測內核 “短路” 故障系統在什麼地方發出調用,並在接近重要對象時返回錯誤代碼。

系統安全包括兩個有些衝突的目標。第一個目標是實現完整的細粒度訪問控制。必須對有可能泄露或損壞信息的位置實施控制。過於粗粒度的控制和不進行控制沒有區別。例如,如果必須將所有文件歸為一種類型,並且有任何一個文件是公開的,則所有文件都是公開的。

另一方面,配置必須簡單,否則管理員就需要管理很多訪問(但是再次強調,這和不進行控制是一樣的)。例如,如果使程序正常工作需要大量訪問規則,那麼管理員就會為程序添加許多訪問許可權,而不是測試這些訪問規則是否有必要。

Linux 中的兩個基本安全模塊使用不同的方法來平衡這個矛盾。

  • SELinux 首先對所有東西實施控制,同時使用強大的策略語言簡化策略的管理。
  • Smack 主要提供簡單的訪問控制。




主要元素 2:SELinux

到目前為止,SELinux 是針對 Linux 的最有名的 MAC 系統(強制訪問控制)。儘管仍然有人反對它,但流行的 Fedora® 發行版從幾年前開始就和 SELinux 一起部署,這是它取得成功的有力證明。

SELinux 使用模塊化策略語言配置,因此用戶可以輕鬆更新已安裝的策略。這種語言還提供一些介面,允許使用更高級的語句表達一組低級的語句。

在本文中,我們將使用一個新的介面來定義容器。雖然為容器添加許多訪問許可權使介面本身變得非常大,但是使用介面創建新的容器卻很簡單。這個介面很有希望成為核心發布策略的一部分。





主要元素 3:Smack

Smack 是簡化的強制訪問控制內核(Simplified Mandatory Access Control Kernel)的縮寫。它首先使用簡單的文本標籤標記所有進程、文件和網路流量。使用創建進程的標籤創建最新的文件。通常存在一些帶有明確定義的訪問規則的默認類型。進程常常可以對具有同一標籤的對象進行讀寫。繞過 Smack 訪問規則的特權由 POSIX 功能控制,因此帶有 CAP_MAC_OVERRIDE 的任務可以覆蓋規則;帶有 CAP_MAC_ADMIN 的任務可以更改規則和標籤。“POSIX file capabilities: Parceling the power of root”(參考資料)演示了這些特權。





安全目標

我們不能盲目地應用策略並期望帶來某些價值,而是應該首先定義明確的安全目標。Smack 的簡單性實際上限定了可實現的目標的範圍,我們將力求實現以下目標:

  1. 使用提供 Web 和 ssh 服務的隔離的文件系統創建容器。
  2. 容器應該是獨立的,以保護彼此的安全。容器 vs1 不能讀取另一個容器 vs2 的文件或中斷它的任務。
  3. 主機的關鍵文件不受容器的影響。
  4. 外部能夠訪問容器中的 Web 伺服器和 ssh 伺服器。




一般設置

在本文中,我們將進行兩個實驗:首先設置由 SELinux 保護的容器,然後設置由 Smack 保護的容器。這兩個實驗演示了大部分初級設置。

您可以使用真實的機器來完成這兩個實驗,但是使用虛擬機更加方便。要使用 qemu 或 kvm,可以通過 qemu-img create vm.img 10G 創建一個硬碟。

使用命令 kvm -hda vm.img -cdrom cdrom.iso -boot d -m 512M 從 CDROM 引導虛擬機。要獲得 CDROM 映像,可以從 fedoraproject.org/get-fedora 下載 Fedora 10 for i386 的安裝 DVD。用下載獲得的文件的名稱替換上一命令中的 cdrom.iso。安裝過程基本可以使用默認值,但一定不要選擇 office and productivity,而是選擇 software development。此外,還需要使用 yum 包管理器安裝 bridge-utils、debootstrap 和 ncurses-devel rpms。

現在需要編譯一個定製內核。下載內核源代碼 rpm 並對其使用補丁 enable-netns.patch(參見 下載 小節)以提供網路名稱空間(在 2.6.29 中為上游(upstream),但在 Fedora 10 中不是),然後更改配置並完成編譯和安裝。這需要作為根用戶用以下指令來實現:

yumdownloader --source kernel  rpm -i kernel*  cd rpmbuild  rpmbuild -bc SPECS/kernel-*  cd BUILD/kernel-2.6.27/linux-2.6*  patch -p1 < ~/enable-netns.patch  make menuconfig  make && make modules_install && make install  

對於這兩個實驗,在 make menuconfig 步驟中都需要選擇 Network Namespaces(在 Networking support -> Networking options 菜單下)。對於 Smack 實驗,還需要進入 Security options 菜單,取消選定 SELinux,並選擇下一個選項 Smack。您還需要將 /boot/grub/grub.conf 中的 default 引導項改為 0 而不是 1。

現在我們試試 liblxc。“LXC:Linux 容器工具” 詳細描述了 liblxc 的基本用法,因此這裡不再細談。僅需使用 container_setup.sh 腳本(參見 下載 小節)設置網橋(bridge),容器網路設備將在此進行對話。它還會清除防火牆(默認情況下不處理網橋),並且在進行 Smack 實驗時設置 Smack 策略(我們稍後將在文件 /etc/smackaccesses 中創建)。每次重新引導之後必須運行 container_setup.sh,或者設置為在引導時自動運行它(如果知道怎麼做的話)。

現在已經準備好虛擬機!我們來試試 liblxc。可以從 lxc.sf.net 通過 cvs 下載最新的源代碼,並按照以下方法編譯它:

cvs -d:pserver:anonymous@lxc.cvs.sourceforge.net:/cvsroot/lxc login  cvs -z3 -d:pserver:anonymous@lxc.cvs.sourceforge.net:/cvsroot/lxc co -P lxc  cd lxc  ./bootstrap && ./configure && make && make install  

現在,如果您查看 README 文檔的話,將發現有好幾個入口點可以選擇。容器是非常輕量級的,因為它們與系統共享許多資源 —— 包括文件系統。但我們的目標是提供一些簡單的隔離,因此將使用腳本 lxc-debian 為每個容器創建完整的 debian chroot 映像。首先創建一個名為 vsplain 的容器:

mkdir /vsplain  cd /vsplain  lxc-debian create  	container name: vsplain  	hostname: vsplain  	IP 10.0.2.20  	gateway: 10.0.2.2  

這個容器的配置存儲在 /usr/local/var/lxc/vsplain 目錄下。如果查找名為 cgroup 的文件,將會看到一些以 devices. 開頭的行。這些是設備白名單 cgroup 的指令,它協調由容器執行的設備創建、讀和寫。

使用命令 lxc-start -n vsplain 啟動這個容器。這時將出現登錄提示。通過不需要密碼的根用戶名登錄到容器。最後,當容器運行時,則需要執行下面的命令:

apt-get install openssh-server  apt-get install apache  

現在可以使用 ssh 技術安全地從 kvm 主機轉移到容器,並使用 vsplain 和主機的 ip 地址(分別是 10.0.2.20 和 10.0.2.15)查看它的 Web 頁面。您隨時可以通過命令 lxc-stop -n vsplain 從 kvm 主機的根終端關閉容器。

在這裡,通過從這個模板克隆兩個新的虛擬機可以節省一些時間。關閉 vm 並執行:

cp vm.img selinux.img  cp vm.img smack.img  





受 SELinux 保護的容器

我們將在容器上使用的 SELinux 策略包含一個 策略模塊;這個模塊已經發布到 refpolicy -- SELinux Reference Policy 開發郵件列表。將這個策略分別下載到 /root/vs 目錄下的 vs.if、vs.fc 和 vs.te 文件中。像下面這樣編譯和安裝新的模塊:

cp -r /usr/share/selinux/devel /usr/share/selinux/vs  cp /root/vs.?? /usr/share/selinux/vs/  cd /usr/share/selinux/vs  make && semodule -i vs.pp  

然後使用 lxc-debian 創建 /vs1 and /vs2 容器,並且使用

mkdir /vs1; cd /vs1  lxc-debian create  	container name: vs1  	hostname: vs1  	address: 10.0.2.21  	gateway: 10.0.2.2  	arch: 2 (i386)  mkdir /vs2; cd /vs2  lxc-debian create  	container name: vs2  	hostname: vs2  	address: 10.0.2.22  	gateway: 10.0.2.2  	arch: 2 (i386)  fixfiles relabel /vs1  fixfiles relabel /vs2  

重新標記它們的文件系統。

在啟動容器時(例如通過使用命令 lxc-start -n vs1),很可能會收到一些關於 SELinux 訪問拒絕的審計消息。但不要擔心 —— 容器將正常啟動,並且會啟用網路服務並 隔離容器。如果在啟動容器之前使用 mount --bind / /vs1/rootfs.vs1/mnt 幫助容器 vs1 進行偽裝,那麼您將會發現,即使是根用戶,也會重用 ls /mnt/root。

為了了解其中的原理,我們看看 vs.if 介面文件。這個文件定義一個稱為 container 的介面,它帶有一個參數(即容器將要定義的基本名稱)。vs.te 文件使用容器名 vs1 和 vs2 兩次調用這個函數。在這個介面中,$1 被擴展到這個參數,因此當我們調用 container(vs1) 時,$1_t 就會變成 vs1_t(從這裡開始,假設我們定義的是 vs1)。

包含 vs1_exec_t 內容的行是最重要的。這個容器以 vs1_t 類型運行。當 unconfined_t 執行容器的 /sbin/init(類型為 vs1_exec_t)時,它將進入這種類型。

剩餘的策略主要是授與容器充分的特權,以訪問系統的各個部分:網路埠、設備和控制台等。該介面很長,這是由現有 SELinux 引用策略的細粒度特性決定的。正如我們將要看到的一樣,受 Smack 保護的容器具有更加簡單的策略;但是它在系統服務行為失誤時提供的靈活保護要少得多。

還有一件事情需要做。需要注意的是,雖然容器不能夠重寫它的 $1_exec_t(即 /sbin/init),但它能夠執行

mv /sbin /sbin.bak  mkdir /sbin  touch /sbin/init  

生成的 /sbin/init 的類型為 vs1_file_t。容器管理員為什麼需要這樣做呢?因為它會在 unconfined_t 域中啟動容器,包括 ssh daemon,這使他能夠獲得有特權的 shell,並且能夠繞過我們將要實施的 SELinux 限制。

為了避免這樣做,需要通過定製腳本實際啟動容器,並在啟動容器前將 sbin/init 重新標記為 vs1_exec_t。事實上,如果容器管理員不介意的話,可以將一個 init 原始副本複製回容器中並重新標記它。但我們僅重新標記現有的 init:

cat >> /vs1/vs1.sh << EOF  #!/bin/sh  chcon -t vs1_exec_t /vs1/rootfs.vs1/sbin/init  lxc-start -n vs1  EOF  chmod u+x /vs1/vs1.sh  

現在需要使用 /vs1/vs1.sh 啟動容器,而不是使用 lxc-start 手工啟動。





受 Smack 保護的容器

在啟用 Smack 時重新編譯內核。您必須能夠進入 /root/rpmbuild/BUILD/kernel*/linux* 目錄的 make menuconfig,然後轉到 security 菜單禁用 SELinux 並啟用 Smack。接下來僅需重複步驟 make && make modules_install && make install。

此外,也要停止用戶空間對 SELinux 的配置。這可以在 SELinux 管理 GUI 上實現,或編輯 /etc/selinux/config 並設置 SELINUX=disabled。要在引導時安裝 Smack 策略還需要幾個步驟:

mkdir /smack  cd /usr/src  wget http://schaufler-ca.com/data/080616/smack-util-0.1.tar  tar xf smack-util-0.1.tar; cd smack-util-0.1  make && cp smackload /bin  

實際的 Smack 策略類似於清單 1:


清單 1. smackaccesses
				  vs1 _ rwa  _ vs1 rwa  vs2 _ rwa  _ vs2 rwa  _ host rwax  host _ rwax  

應該將它複製到一個名為 /etc/smackaccesses 的文件中。下次運行 /bin/container_setup.sh 時會將這個文件載入到 smackload。

這個策略十分簡單。默認情況下,任何標籤都可以讀取標記有 _ 的數據。我們為容器不能訪問的主機的私有數據定義一個新標籤 host;並且將這個標籤應用到 container_setup.sh 腳本中的 cgroups 文件系統。其他敏感文件,比如 /etc/shadow,應該使用這個標籤。

我們對 vs1 和 vs2 進行定義以標記容器。默認情況下,它們能夠訪問自己的數據。我們添加一個規則使它們可以寫 _,從而允許發送網路包。由於 vs1 不能訪問 vs2 數據(反之亦然),因此容器之間是彼此獨立的。

如前所述,由 CAP_MAC_ADMIN 和 CAP_MAC_OVERRIDE 功能決定定義或繞過 Smack 規則的能力。因此,容器不應該具有這些能力。這可以通過 helper 程序 dropmacadmin.c 來實現(參見 下載 小節)。必須靜態地編譯它,因為來自主機的容器有不同的版本:

gcc -o dropmacadmin dropmacadmin.c -static  cp dropmacadmin /bin/  

創建一個稱為 vs1 的新容器:

mkdir /vs1; cd /vs1  lxc-debian create  	container name: vs1  	hostname: vs1  	address: 10.0.2.21  	router: 10.0.2.2  	arch: 2 (i386)  

使用標籤 vs1 標記 vs1 文件系統中的所有文件:

for f in `find /vs1/rootfs.vs1`; do  	attr -S -s SMACK64 -V vs1 $f  done  

現在需要創建一個能夠安全啟動容器的腳本。這意味著它能將自己的進程標籤設置為 vs1,並通過 dropmacadmin 打包容器的 /sbin/init。如下所示:

cat >> /vs1/vs1.sh << EOF  #!/bin/sh  cp /bin/dropmacadmin /vs1/rootfs.vs1/bin/  attr -S -s SMACK64 -V vs1 /vs1/rootfs.vs1/bin/dropmacadmin  echo vs1 > /proc/self/attr/current  lxc-start -n vs1 /bin/dropmacadmin /sbin/init  EOF  chmod u+x /vs1/vs1.sh  

再做一件事情就可以讓 vs1 在其即將裝載的 tmpfs 文件系統上執行寫操作:

sed -i 's/defaults/defaults,smackfsroot=vs1,smackfsdef=vs1/' \  	/vs1/rootfs.vs1/etc/fstab  

這導致在 /dev/shm 上裝載 tmpfs 文件系統,以帶上 vs1 標籤,從而讓 vs1 可以對它執行寫操作。否則,vs1 init 腳本將不能在設置網路時創建需要使用的 /dev/shm/network 目錄。類似地,如果希望使用基於 ram 的 /tmp,使用相同的選項即可。

現在,我們再次幫助 vs1 進行偽裝。像創建 vs1 那樣創建 vs2,在每個步驟中將 vs1 替換為 vs2。然後在 vs1 的 /mnt 下綁定裝載根文件系統:

mount --bind /vs1 /vs1  mount --make-runbindable /vs1  mount --rbind / /vs1/rootfs.vs1/mnt  

使用 vs1.sh 啟動容器。注意,您還可以從 kvm 主機看到 vs1 和 vs2 上的 Web 頁面。此外還要注意,vs1 不能通過網路訪問 vs2。它也不能查看 vs2 的文件:

vs1:~# ls /mnt/    (directory listing)  vs1:~# ls /mnt/vs2/rootfs.vs2    ls:/mnt/vs2/rootfs.vs2: Permission denied  vs1:~# mkdir /cgroup  vs1:~# mount -t cgroup cgroup /cgroup  vs1:~# ls /cgroup    ls:/mnt/vs3: Permission denied  vs1:~# mknod /dev/sda1 b 8 1    mknod: `/dev/sda1': Operation not permitted  vs1:~# mount /mnt/dev/sda1 /tmp    mount: permission denied  

它能查看主機文件系統。對於需要保護的任何東西,可以使用 host 標籤進行標記。在 cgroup 文件系統上就採用了這種做法,這正是 ls /cgroup 失敗的原因。

最後,設備白名單 cgroup 防止我們創建磁碟設備,或在它存在的情況下裝載它(因為這需要通過 /mnt 來完成)。

當然,我們的設置方式讓容器管理員可以刪除 /mnt/dev/sda1,或用其他方法擾亂主機,因此除了用於演示外,這種綁定裝載是不如人意的!

注意,在 SELinux 系統上,默認(且容易的)路由允許容器通過網路彼此進行對話,而在 Smack 中則恰好相反。目前,允許容器彼此對話還是比較困難的。不久以後,將可以在 IP 地址上設置標籤,並且允許建立策略以實現容器之間的通信。

在如何建立 Smack 網路方面還有另一個問題。命令 kill -9 -1 終止系統上的每個任務。當這個操作由容器中的任務執行時,它將僅終止同一容器中的任務。這種行為已經在上游內核中得到修復,但我們使用的 Fedora 10 內核還存在該問題。因此,每個任務都會發出一個 -9 信號。

在受 SELinux 保護的容器中,SELinux 阻止該信號通過容器邊界,因此 kill -9 -1 實際上是安全的。但在 Smack 中,默認情況下任務被標記為 _(就像網路一樣),因此由於我們允許容器執行 _ 寫操作以寫到網路中,並且終止任務在 Smack 中被認為是寫訪問,所以允許容器管理員在整個系統上終止任何任務。

另一個缺點(SELinux 容器仍然存在該缺點)與 Unix98 偽終端有關。打開兩個圖形化終端。在第一個終端中,啟動 vs1 並查看 /dev/pts。您將看見至少兩個條目(0 和 1),它們分別屬於每個終端。可以從 vs1 容器寫入到與另一個終端對應的條目。

對於 Fedora 內核,有兩個解決方案。可以使用設備白名單 cgroup 拒絕容器打開設備。但是這必須在容器每次啟動時手動操作,以允許它訪問自己的終端;或者應用 SELinux 和 Smack 標籤,結果是一樣的。

更新的 2.6.29 內核支持 devpts 名稱空間。容器必須重新裝載 /dev/pts,在這個操作之後,它將不能訪問屬於主機或其他容器的 devpts 條目。





結束語

本文介紹了構建受 LSM 保護的容器所需的工具,但還有很多工作需要做:

  • 對於 Smack,必須選擇需要標記為 host 的文件。
  • 對於 SELinux,應該對其進行調優,然後將一個 container 介面放置到上游引用策略。

儘管這些工作正在進行當中,在獲得更多關於受 LSM 保護的容器的經驗之前,您不應該完全信賴這些機制來阻止不可信的根用戶。

儘管目前在創建容器方面還沒有最佳實踐,但仍然有一些想法是值得考慮的。首先,記住您正試圖實現兩個有些矛盾的目標:最小化容器(以及主機)之間的複製,同時需要確保安全隔離。實現這些目標的方法之一是,創建一個最小的完整 rootfs,其中不運行任何容器,並且將它的類型標記為所有容器都可以讀取的類型。然後使用 lxc-sshd 腳本的定製版本創建每個基於原型的實際容器,以為容器的大部分文件系統創建只讀裝載,同時為容器提供一個可以存儲文件的私有可寫位置(比如 /scratch)。由於每個容器都有一個私有的裝載名稱空間,所以它能夠綁定裝載任何私有的和/或對於其私有共享目錄可寫的文件或目錄。例如,如果它需要一個私有 /lib,則可以執行 mount --bind /scratch/rootfs/lib /lib。同樣地,管理員也能確保每個容器都在啟動時執行 mount --bind /scratch/shadow /etc/shadow。

對於 SELinux 和 Smack,我演示的這個方法的一個明顯缺點就是容器管理員不能在自己的容器的內部利用 LSM 控制信息流。並且為簡單起見,容器中的所有任務都使用 MAC 策略統一處理。在另一篇文章中,我將探討如何允許容器管理員指定自己的 LSM 策略,同時又能夠約束它們。(責任編輯:A6)



[火星人 ] 安全 Linux 容器實現指南已經有821次圍觀

http://coctec.com/docs/linux/show-post-68954.html