淺談Docker的安全性支援(下篇)

小米運維發表於2019-05-28

前面我們為大家介紹了Docker支援容器root使用者的 Capability 能力限制、映象簽名、Apparmor的MAC訪問控制、使用Seccomp限制系統呼叫等安全性支援,這篇文章我們會為大家介紹Docker其他安全性特性支援。

User Namespace隔離

Linux 名稱空間為執行中的程式提供了隔離,限制他們對系統資源的訪問,而程式沒有意識到這些限制。為防止容器內的特權升級攻擊的最佳方法是將容器的應用程式配置為非特權使用者執行,對於其程式必須作為容器中的 root 使用者執行的容器,可以將此使用者重新對映到 Docker 主機上許可權較低的使用者。對映的使用者被分配了一系列 UID,這些 UID 在名稱空間內作為從 0 到 65536 的普通 UID 執行,但在主機上沒有特權。

重新對映由兩個檔案處理:/etc/subuid 和 /etc/subgid,其中前者關注使用者 ID 範圍,後者關注使用者組 ID 範圍。

例如,如下 /etc/subuid 中的條目:

testuser:231072:65536複製程式碼

這意味著 testuser 將從 231072 開始,在後面的 65536 個整數中按順序為使用者分配一個 ID。例如,名稱空間中的 UID 231072 對映到容器中的 UID 0(root),UID 231073 對映為 UID 1,依此類推。如果某個程式嘗試提升特權到名稱空間外部,則該程式將作為主機上無特權的高數字 UID 執行,該 UID 甚至不對映到真實使用者,這意味著該程式完全沒有主機系統的許可權。

在Docker1.10以後,可以通過在Dockerd啟動引數中指定userns-remap 來啟用這個功能。

下面我們做一下演示:

1、檢視Docker Daemon是否以root使用者身份執行

lynzabo@ubuntu:~$ ps -ef | grep dockerdroot 1557 1 0 12:54 ? 00:05:08 /usr/bin/dockerd -H fd://lynzabo 36398 23696 0 21:41 pts/1 00:00:00 grep --color=auto dockerdlynzabo@ubuntu:~$複製程式碼

2、執行容器,指定 id 命令

lynzabo@ubuntu:~$ docker run --rm alpine idUnable to find image 'alpine:latest' locallylatest: Pulling from library/alpine4fe2ade4980c: Pull complete Digest: sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528Status: Downloaded newer image for alpine:latestuid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon)...lynzabo@ubuntu:~$複製程式碼

上面輸出的最後一行顯示容器以root身份執行:uid = 0(root)和gid = 0(root)。

3、執行docker run 指定引數--user ,指定容器以當前使用者身份來執行

lynzabo@ubuntu:~$ iduid=1000(lynzabo) gid=1000(lynzabo) groups=1000(lynzabo)...lynzabo@ubuntu:~$ docker run --rm --user 1000:1000 alpine iduid=1000 gid=1000lynzabo@ubuntu:~$複製程式碼

可以看到容器使用的我們設定的使用者和組來執行。

有時候,我們更希望容器裡面是以root使用者來執行,但是並不需要具有宿主機上root許可權,可以使用User Namespace做到這些。使用User Namespace,容器中的root使用者會被重新對映到宿主機上一個非特權使用者,這意味著該程式完全沒有主機系統的許可權。

下面我們帶大家一起演示一下:

1、停止Docker Daemon

lynzabo@ubuntu:~$ sudo systemctl stop dockerlynzabo@ubuntu:~$複製程式碼

2、指定在User Namespace模式下執行Docker Daemon

lynzabo@ubuntu:~$ sudo dockerd --userns-remap=default &lynzabo@ubuntu:~$複製程式碼

當你將 Docker 配置為使用 userns-remap 功能時,可以指定為現有使用者或組,也可以指定為 default。如果指定為default,則會為此建立並使用使用者和組 dockremap 。也可以在 daemon.json 配置檔案中指定。

通過 id 命令驗證 Docker 已經建立了這個使用者。

lynzabo@ubuntu:~$ id dockremapuid=123(dockremap) gid=132(dockremap) groups=132(dockremap)lynzabo@ubuntu:~$複製程式碼

驗證條目已經新增到了 /etc/subuid 和 /etc/subgid 檔案中。

lynzabo@ubuntu:~$ grep dockremap /etc/subuiddockremap:165536:65536lynzabo@ubuntu:~$ grep dockremap /etc/subgiddockremap:165536:65536lynzabo@ubuntu:~$複製程式碼

如果這些條目不存在,需要以 root 使用者身份編輯檔案,並且分配起始的 UID 和 GID(在最高的已經分配的值的基礎上加上偏移,65536)。注意不要使範圍重疊。

3、使用 docker info 命令驗證Docker是否正確啟用了使用者名稱空間支援

lynzabo@ubuntu:~$ docker info...Docker Root Dir: /home/docker/165536.165536...lynzabo@ubuntu:~$ lynzabo@ubuntu:~$ ls -ld /home/docker/165536.165536drwx------ 14 165536 165536 4096 Sep 17 21:44 /home/docker/165536.165536lynzabo@ubuntu:~$ sudo ls -l /home/docker/165536.165536/total 48drwx------ 2 165536 165536 4096 Sep 17 21:44 volumesdrwx--x--x 3 root root 4096 Sep 17 21:44 containerddrwx------ 2 165536 165536 4096 Sep 17 21:44 containersdrwx------ 3 root root 4096 Sep 17 21:44 imagedrwxr-x--- 3 root root 4096 Sep 17 21:44 networkdrwx------ 4 165536 165536 4096 Sep 17 21:44 overlay2...lynzabo@ubuntu:~$複製程式碼

可以看到Docker 工作目錄在原有/var/lib/docker/ 目錄下多了一層以“使用者UID.GID”命名的目錄。檢視該目錄下各個子目錄許可權,有些子目錄仍有 root 擁有,有些子目錄已經繼承了上級目錄許可權。

4、檢視本地映象

lynzabo@ubuntu:~$ docker imagesREPOSITORY TAG IMAGE ID CREATED SIZElynzabo@ubuntu:~$複製程式碼

可以看到本地沒有任何映象,很奇怪,我們在上面使用的alpine映象消失了。

5、下面我們以互動模式執行一個容器,將宿主機的/bin目錄掛載到容器中

lynzabo@ubuntu:~$ docker run -it --rm -v /bin:/host/bin busybox /bin/shUnable to find image 'busybox:latest' locallylatest: Pulling from library/busybox8c5a7da1afbc: Pull complete Digest: sha256:cb63aa0641a885f54de20f61d152187419e8f6b159ed11a251a09d115fdff9bdStatus: Downloaded newer image for busybox:latest/ # iduid=0(root) gid=0(root) groups=10(wheel)/ #複製程式碼

上面的輸出顯示容器內部是以root使用者的安全上下文下執行。

6、下面我們嘗試執行命令

/ # rm /host/bin/shrm: can't remove 'sh': Permission denied複製程式碼

操作失敗並顯示許可權被拒絕,這是因為要刪除的檔案存在於Docker宿主機的本地檔案系統中,並且容器在其所在的名稱空間之外沒有root訪問許可權。如果未啟用User Namespace,執行相同的操作,操作將成功。

SELinux支援

我們知道系統的使用者主要分為系統管理員與一般使用者,而這兩種身份能否使用系統檔案資源與 rwx 的許可權設定有關,這種存取檔案系統的方式被稱為“自主式存取控制(DAC)”。不過你要注意的是,各種許可權設定對 root 是無效的,這個時候就可以使用委任式存取控制(MAC)了,使用MAC可以針對特定的程式與特定的檔案資源來進行許可權的控管,也就是說,即使是root使用者,那麼在使用不同的程式時,你所能取得的許可權並不一定是root,而要根據當時程式的設定而定。

SELinux 就是通過 MAC 的方式來控管程式,他控制的主體是程式, 而目標則是該程式能否讀寫的“檔案資源”。下面是使用SeLinux基本流程:

淺談Docker的安全性支援(下篇)

由上圖我們可以發現:

(1) 主體程式必須要通過 SELinux 政策內的規則放行後,就可以與目標資源進行安全性本文的比對。

(2) 比對安全性本文,比對成功就可以訪問目標,比對失敗,記錄拒絕資訊。

SELinux的工作模式一共有三種 Enforcing、Permissive和Disabled :

  • Enforcing 模式:將受限主體進入規則比對、安全本文比對,如果失敗,抵擋主體程式的讀寫行為,並且記錄這一行為。 如果成功,這才進入到 rwx 許可權的判斷。

  • Permissive模式:不會抵擋主體程式讀寫行為,只是將該動作記錄下來。

  • Disabled 的模式:禁用SELinux,直接去判斷 rwx。

Docker守護程式的SELinux功能預設是禁用的,需要使用--selinux-enabled來啟用,容器的標籤限制可使用-security-opt載入SELinux或者AppArmor的策略進行配置。

下面演示使用SELinux:

1、我們在宿主機上開啟 SELinux,嘗試啟動一個 Nginx 容器並將 nginx.conf 掛載到容器內。

# 檢視系統Selinux是否開啟,及當前模式,policy[lynzabo@localhost ~]$ sestatus SELinux status: enabledSELinuxfs mount: /sys/fs/selinuxSELinux root directory: /etc/selinuxLoaded policy name: targetedCurrent mode: enforcingMode from config file: enforcingPolicy MLS status: enabledPolicy deny_unknown status: allowedMax kernel policy version: 28[lynzabo@localhost ~]$# Docker開啟Selinux[root@localhost conf]# ps -ef|grep dockerdroot 4401 1 0 08:15 ? 00:00:00 /usr/bin/dockerd --selinux-enabledroot 4549 3117 0 08:15 pts/0 00:00:00 grep --color=auto dockerd[root@localhost conf]# # 執行一個容器,將本地nginx.conf檔案掛載到容器中[root@localhost conf]# docker run --name test-selinux-nginx -v /root/nginx/conf/nginx.conf:/etc/nginx/nginx.conf -d nginxbbef34e4caa4e8c3a19f9eae5859691e3504731568e7e585108e26aade95be76[root@localhost conf]#複製程式碼

使用 docker ps 檢視容器狀態,容器已經退出,退出日誌為“Permission denied”

[root@localhost conf]# docker ps -aCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESbbef34e4caa4 nginx "nginx -g 'daemon of…" 15 seconds ago Exited (1) 13 seconds ago test-selinux-nginx[root@localhost conf]# docker logs -f bbef34e4caa42018/09/17 15:16:02 [emerg] 1#1: open() "/etc/nginx/nginx.conf" failed (13: Permission denied)nginx: [emerg] open() "/etc/nginx/nginx.conf" failed (13: Permission denied)[root@localhost conf]#複製程式碼

可以看到錯誤資訊好像是許可權被拒絕,那麼我們檢查一下nginx.conf 的許可權是否符合我們的要求。

使用 ls -Z 檢視 nginx.conf的 DAC 與 MAC 許可權資訊。

[root@localhost conf]# ls -Z-rw-r--r--. root root unconfined_u:object_r:admin_home_t:s0 nginx.conf[root@localhost conf]#複製程式碼

檔案的許可權為 644。我們在上面檢視 Docker 程式,Docker 程式的許可權為 root,對於644的許可權檔案是可讀可寫的。 看來,問題應該是出在 MAC 許可權上。

分析 ls -Z 的結果,nginx.conf 對應的安全性文字的型別為 admin_home_t:s0,在啟用SELinux後,我們的主體是無法操作這種型別的object的,所以無論 Docker 容器的許可權是否是 root,Docker 容器程式都沒有許可權讀取宿主上的 nginx.conf。

Docker 官方提供了一種解決方案專門用來解決與 SELinux 相關的許可權問題,在將 SELinux 上的檔案掛載到容器中時,在掛載的路徑最後加上:z。如:

docker run -v /var/db:/var/db:z rhel7 /bin/sh複製程式碼

Docker 會自動將被掛載的宿主目錄的安全性文字配置為目標可讀。

[root@localhost conf]# docker run --name test-selinux-z-nginx -v /root/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:z -d nginxdb49bbe352ff1ab800274a17fd18f9c7d86c281e60ac3ffa36ba14e12949285d[root@localhost conf]# docker psCONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESdb49bbe352ff nginx "nginx -g 'daemon of…" 5 seconds ago Up 2 seconds 80/tcp test-selinux-z-nginx[root@localhost conf]#複製程式碼

這個時候看到Nginx正常啟動了,說明SELinux稽核通過了。

pid-limits的支援

Linux核心會限制所有程式可以開啟的檔案總數,同時為了防止某個程式消耗過多檔案資源,也會對單個程式設定限制,這個時候ulimit就派上了用場,使用ulimit命令可以限制程式最多開啟檔案控制程式碼數、最多開啟程式數、執行緒棧大小等等。Docker對ulimit也提供了支援,Docker 1.6之前,Docker容器的ulimit設定,繼承自Docker daemon,Docker 1.6之後,既可以設定全域性預設的ulimit,也可以對單個容器指定ulimit。

如下,指定容器最多可開啟檔案控制程式碼數為2048,最多開啟100個程式。

[lynzabo@VM_0_6_centos ~]$ docker run -it --ulimit nofile=2048 --ulimit nproc=100 busybox sh / # ulimit -a-f: file size (blocks) unlimited-t: cpu time (seconds) unlimited-d: data seg size (kb) unlimited-s: stack size (kb) 8192-c: core file size (blocks) unlimited-m: resident set size (kb) unlimited-l: locked memory (kb) 64-p: processes 100-n: file descriptors 2048-v: address space (kb) unlimited-w: locks unlimited-e: scheduling priority 0-r: real-time priority 0/ #複製程式碼

容器程式數限制坑介紹

說起程式數限制,大家可能都知道ulimit的nproc這個配置,nproc是存在坑的,與其他ulimit選項不同的是,nproc是一個以使用者為管理單位的設定選項,即他調節的是屬於一個使用者UID的最大程式數之和。如下面輸出:

# 我們使用daemon使用者啟動4個容器,並設定允許的最大程式數為3$ docker run -d -u daemon --ulimit nproc=3 busybox top$ docker run -d -u daemon --ulimit nproc=3 busybox top$ docker run -d -u daemon --ulimit nproc=3 busybox top# 這個容器會失敗並報錯,資源不足$ docker run -d -u daemon --ulimit nproc=3 busybox top複製程式碼

我們指定使用daemon使用者來在容器中啟動top程式,結果啟動到第4個容器的時候就報錯了。而實際上,我們本來是想限制每個容器裡使用者最多隻能建立3個程式。另外,預設情況下,Docker在容器中啟動程式是以root使用者身份啟動的,而ulimit的nproc引數是無法對root使用者進行限制。

Docker從1.10以後,支援為容器指定--pids-limit 限制容器內程式數,和容器裡使用者無關。如下面例子:

[lynzabo@VM_0_6_centos ~]$ docker run -d --name test-pids-limit --pids-limit=5 busybox top5693c8c31284b0f3cb4eb10d4f67e13ad98d1972a27dab094f0ad96154a5ce6a[lynzabo@VM_0_6_centos ~]$ docker exec -ti test-pids-limit sh                             / # ps -efPID USER TIME COMMAND    1 root 0:00 top    5 root 0:00 sh    9 root 0:00 ps -ef/ # nohup top &/ # nohup: appending output to nohup.out/ # nohup top &/ # nohup: appending output to nohup.out/ # nohup top &/ # nohup: appending output to nohup.out/ # nohup top &sh: can't fork: Resource temporarily unavailable/ #複製程式碼

容器啟動引數中,我們通過--pids-limit設定容器裡最多隻能執行5個程式,可以看到,當程式數達到5個後,在啟動程式時就提示can't fork: Resource temporarily unavailable。

其他核心安全特性工具支援

在容器生態的周圍,還有很多工具可以為容器安全性提供支援。

1、可以使用 docker-bench-security檢查你的Docker執行環境,如Docker daemon配置,宿主機配置

2、使用Sysdig Falco(地址:https://sysdig.com/opensource/falco/)可以監視容器的行為,檢測容器中是否有異常活動。

3、使用GRSEC 和 PAX來加固系統核心,還可以使用GRSecurity為系統提供更豐富的安全限制。等等。

這篇文章的分享就到這裡,希望本次分享對大家有所幫助,歡迎留言與我們交流。


相關文章