生產服務器使用的是CentOS 7系統,安裝Docker也一直都是使用yum命令直接從CentOS自己的源安裝。自從Docker項目改名為moby,進而誕生CE和EE兩個不同版本后。不知是因為版權還是其他的什么原因,CentOS源中的Docker版本不再更新了,default維持在1.12.6,latest為1.13.1。
為了使用新版本的Docker,只能添加官方repo,然后安裝docker-ce。安裝完成后,在默認配置上與CentOS自帶版相比,發現了兩個不同之處:
- 存儲驅動默認換成了overlay
- SELinux默認沒有開啟(指Docker服務配置)
分析
關於存儲驅動的問題,這里暫時不做討論,只是來看一下SELinux的問題。這里說的SELinux沒有開啟不是指在操作系統層面上將其disable掉了,而是說Docker服務的配置中沒有將其enable。畢竟在Docker中開啟SELinux是要在操作系統也就是Linux內核開啟了SELinux的前提下進行的。首先,Docker沒有開啟SELinux,在現象上會有什么不同?
我們有兩台服務器,第一台上Docker開啟了SELinux,而第二台沒有。兩台上面都有運行nginx容器,執行如下命令對比一下:
[admin@server1 ~]$ ps -AZ | grep nginx
system_u:system_r:svirt_lxc_net_t:s0:c375,c378 2285 ? 00:00:22 nginx
system_u:system_r:svirt_lxc_net_t:s0:c245,c772 2407 ? 00:00:00 nginx
system_u:system_r:svirt_lxc_net_t:s0:c245,c772 2453 ? 00:00:02 nginx
system_u:system_r:svirt_lxc_net_t:s0:c375,c378 24771 ? 00:00:00 nginx
[admin@server2 ~]$ ps -AZ | grep nginx
system_u:system_r:spc_t:s0 4375 ? 00:00:00 nginx
system_u:system_r:spc_t:s0 4419 ? 00:00:00 nginx
system_u:system_r:spc_t:s0 4739 ? 00:00:01 nginx
system_u:system_r:spc_t:s0 4752 ? 00:00:01 nginx
system_u:system_r:spc_t:s0 9536 ? 00:00:00 nginx
system_u:system_r:spc_t:s0 9564 ? 00:00:00 nginx
system_u:system_r:spc_t:s0 19178 ? 00:00:00 nginx
system_u:system_r:spc_t:s0 19255 ? 00:00:00 nginx
通過對比,我們發現開啟SELinux后的Docker服務:
- 容器內的進程運行在
svirt_lxc_net_t
domain下 - 不同容器內的進程,安全上下文sensitivity段是不相同的(SELinux的MCS隔離)
對於沒有開啟SELinux的Docker服務:
- 容器進程domain為
spc_t
- 所有容器進程的安全上下文sensitivity完全相同
根據以上對比,顯而易見的是,在SElinux沒有開啟時容器進程擁有相同的sensitivity,所以就無法依賴SELinux實現容器間的安全隔離了。也就是說如果某個容器內的服務進程因為漏洞等原因被入侵,進而被黑客控制,SELinux不會阻止此進程去訪問其他容器的資源。
對於svirt_lxc_net_t
和spc_t
,可能第一眼看來不會有那么多的想法,我們先來看看svirt_lxc_net_t
的進程是個什么體驗:
[admin@server2 ~]$ runcon -u system_u -r system_r -t svirt_lxc_net_t -l s0:c125,c512 /bin/bash
bash: /home/admin/.bashrc: Permission denied
bash-4.2$ ls
ls: cannot open directory .: Permission denied
bash-4.2$ ps -Z
system_u:system_r:svirt_lxc_net_t:s0:c125,c512 23686 pts/0 00:00:00 bash
system_u:system_r:svirt_lxc_net_t:s0:c125,c512 23722 pts/0 00:00:00 ps
bash-4.2$
因為當前目錄的安全上下文類型是user_home_t
,按照SELinux策略,svirt_lxc_net_t
是沒有權限訪問的。雖然能夠感受到進程確實受到了限制,但是還不是很直觀。接下來用sepolicy
命令分析一下安全策略:
[admin@server2 ~]$ sepolicy communicate -s svirt_lxc_net_t -t svirt_lxc_net_t
sysctl_net_unix_t
cifs_t
svirt_lxc_net_t
fusefs_t
cgroup_t
svirt_sandbox_file_t
usermodehelper_t
svirt_home_t
hugetlbfs_t
nfs_t
根據man page中的說明,這個命令用來分析source和target兩個domain可以通過哪些type來通信,也就是哪些type對於source來說可寫,對於target來說可讀。我們把source和target指定為同一domain,來看看該domain能夠讀寫哪些type。
我們看到了,一個很熟悉的svirt_sandbox_file_t
,正是給Docker容器掛載volume時經常看到、用到的。據了解svirt_lxc_net_t相關策略就是為容器、虛擬化技術而設計的,除了可以訪問svirt_sandbox_file_t類型的文件還擁有網絡能力,並且可以執行/usr下大多數的命令。
再用以上命令看一下spc_t
,因為行數太多,所以做一下統計:
[admin@server2 ~]$ sepolicy communicate -s spc_t -t spc_t | wc -l
3816
[admin@server2 ~]$ sepolicy communicate -s unconfined_t -t unconfined_t | wc -l
3816
根據當前的安全策略,3816個type,比起svirt_lxc_net_t
的10個,不可同日而語。而且我們發現,不受限domain unconfined_t
也是3816。那么spc_t
就等於unconfined_t
了嗎?其實也不是。
根據Dan Walsh的Blog所講,unconfined_t
是為管理員而設置的一個user domain,SELinux安全策略會阻止絕大多數的受限domain和unconfined_t
通信。spc_t
全稱為Super Privileged Container,也就是特權容器。根據SELinux策略,Docker daemon container_runtime_t
可以通過transition轉變為spc_t
,而且大多數重要的受限domain可以通過unix domain socket和spc_t
進行通信。
配置
上面的分析只是為了對SELinux對Docker的影響有更深入地了解,同時感受SELinux的重要性。其實在Docker服務配置中開啟SELinux很簡單:
- 一種方法是在dockerd啟動時加上
--selinux-enabled
參數,在CentOS上可以修改systemd Unit文件docker.service - 另一種方法實在
/etc/docker/daemon.json
配置文件中加上:
然后重啟docker服務{ "selinux-enabled": true }
需要注意的是,在SELinux開啟之前創建的容器不會受到影響。如果要為這些容器應用SELinux,可以重建,或者嘗試自己修改容器的配置文件和文件系統。