淺談Docker的安全性支援(下篇)
前面我們為大家介紹了Docker支援容器root使用者的 Capability 能力限制、映象簽名、Apparmor的MAC訪問控制、使用Seccomp限制系統呼叫等安全性支援,這篇文章我們會為大家介紹Docker其他安全性特性支援。
1User 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 dockerd
root 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 dockerd
lynzabo@ubuntu:~$
△左右滑動檢視全部△
2、執行容器,指定 id 命令
lynzabo@ubuntu:~$ docker run --rm alpine id
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
4fe2ade4980c: Pull complete
Digest:
sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Status: Downloaded newer image for alpine:latest
uid=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:~$ id
uid=1000(lynzabo) gid=1000(lynzabo)
groups=1000(lynzabo)...
lynzabo@ubuntu:~$ docker run --rm --user 1000:1000
alpine id
uid=1000 gid=1000
lynzabo@ubuntu:~$
可以看到容器使用的我們設定的使用者和組來執行。
有時候,我們更希望容器裡面是以root使用者來執行,但是並不需要具有宿主機上root許可權,可以使用User Namespace做到這些。使用User Namespace,容器中的root使用者會被重新對映到宿主機上一個非特權使用者,這意味著該程式完全沒有主機系統的許可權。
下面我們帶大家一起演示一下:
1、停止Docker Daemon
lynzabo@ubuntu:~$ sudo systemctl stop docker
lynzabo@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 dockremap
uid=123(dockremap) gid=132(dockremap) groups=132(dockremap)
lynzabo@ubuntu:~$
驗證條目已經新增到了 /etc/subuid 和 /etc/subgid 檔案中。
lynzabo@ubuntu:~$ grep dockremap /etc/subuid
dockremap:165536:65536
lynzabo@ubuntu:~$ grep dockremap /etc/subgid
dockremap:165536:65536
lynzabo@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.165536
drwx------ 14 165536 165536 4096 Sep 17 21:44 /home/docker/165536.165536
lynzabo@ubuntu:~$ sudo ls -l /home/docker/165536.165536/
total 48
drwx------ 2 165536 165536 4096 Sep 17 21:44 volumes
drwx--x--x 3 root root 4096 Sep 17 21:44 containerd
drwx------ 2 165536 165536 4096 Sep 17 21:44 containers
drwx------ 3 root root 4096 Sep 17 21:44 image
drwxr-x--- 3 root root 4096 Sep 17 21:44 network
drwx------ 4 165536 165536 4096 Sep 17 21:44 overlay2
...
lynzabo@ubuntu:~$
△左右滑動檢視全部△
可以看到Docker 工作目錄在原有/var/lib/docker/ 目錄下多了一層以“使用者UID.GID”命名的目錄。檢視該目錄下各個子目錄許可權,有些子目錄仍有 root 擁有,有些子目錄已經繼承了上級目錄許可權。
4、檢視本地映象
lynzabo@ubuntu:~$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
lynzabo@ubuntu:~$
可以看到本地沒有任何映象,很奇怪,我們在上面使用的alpine映象消失了。
5、下面我們以互動模式執行一個容器,將宿主機的/bin目錄掛載到容器中
lynzabo@ubuntu:~$ docker run -it --rm -v /bin:/host/bin busybox /bin/sh
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
8c5a7da1afbc: Pull complete
Digest:
sha256:cb63aa0641a885f54de20f61d152187419e8f6b159ed11a251a09d115fdff9bd
Status: Downloaded newer image for busybox:latest
/ # id
uid=0(root) gid=0(root) groups=10(wheel)
/ #
△左右滑動檢視全部△
上面的輸出顯示容器內部是以root使用者的安全上下文下執行。
6、下面我們嘗試執行命令
/ # rm /host/bin/sh
rm: can't remove 'sh': Permission denied
操作失敗並顯示許可權被拒絕,這是因為要刪除的檔案存在於Docker宿主機的本地檔案系統中,並且容器在其所在的名稱空間之外沒有root訪問許可權。如果未啟用User Namespace,執行相同的操作,操作將成功。
2SELinux支援
我們知道系統的使用者主要分為系統管理員與一般使用者,而這兩種身份能否使用系統檔案資源與 rwx 的許可權設定有關,這種存取檔案系統的方式被稱為“自主式存取控制(DAC)”。不過你要注意的是,各種許可權設定對 root 是無效的,這個時候就可以使用委任式存取控制(MAC)了,使用MAC可以針對特定的程式與特定的檔案資源來進行許可權的控管,也就是說,即使是root使用者,那麼在使用不同的程式時,你所能取得的許可權並不一定是root,而要根據當時程式的設定而定。
SELinux 就是透過 MAC 的方式來控管程式,他控制的主體是程式, 而目標則是該程式能否讀寫的“檔案資源”。下面是使用SeLinux基本流程:
由上圖我們可以發現:
(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: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Max kernel policy version: 28
[lynzabo@localhost ~]$
# Docker開啟Selinux
[root@localhost conf]# ps -ef|grep dockerd
root 4401 1 0 08:15 ? 00:00:00 /usr/bin/dockerd --selinux-enabled
root 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 nginx
bbef34e4caa4e8c3a19f9eae5859691e3504731568e7e585108e26aade95be76
[root@localhost conf]#
△左右滑動檢視全部△
使用 docker ps 檢視容器狀態,容器已經退出,退出日誌為“Permission denied”
[root@localhost conf]# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
bbef34e4caa4 nginx "nginx -g 'daemon of…" 15 seconds ago Exited (1) 13 seconds ago test-selinux-nginx
[root@localhost conf]# docker logs -f bbef34e4caa4
2018/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 nginx
db49bbe352ff1ab800274a17fd18f9c7d86c281e60ac3ffa36ba14e12949285d
[root@localhost conf]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
db49bbe352ff nginx "nginx -g 'daemon of…" 5 seconds ago Up 2 seconds 80/tcp test-selinux-z-nginx
[root@localhost conf]#
△左右滑動檢視全部△
這個時候看到Nginx正常啟動了,說明SELinux稽核透過了。
3pid-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 top
5693c8c31284b0f3cb4eb10d4f67e13ad98d1972a27dab094f0ad96154a5ce6a
[lynzabo@VM_0_6_centos ~]$ docker exec -ti test-pids-limit sh
/ # ps -ef
PID 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。
4其他核心安全特性工具支援
在容器生態的周圍,還有很多工具可以為容器安全性提供支援。
1、可以使用 docker-bench-security檢查你的Docker執行環境,如Docker daemon配置,宿主機配置
2、使用Sysdig Falco(地址:)可以監視容器的行為,檢測容器中是否有異常活動。
3、使用GRSEC 和 PAX來加固系統核心,還可以使用GRSecurity為系統提供更豐富的安全限制。等等。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559359/viewspace-2645966/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 淺談Docker的安全性支援(上篇)Docker
- Java API 操作Docker淺談JavaAPIDocker
- 淺談OA系統在應用中安全性
- 淺談卡牌遊戲中給玩家支援的設計遊戲
- OA系統背後“那雙眼睛”-淺談OA系統的安全性
- 談談ES6語法(彙總下篇)
- 談談資料傳輸中的安全性
- 流式處理框架storm淺析(下篇)框架ORM
- .Net Core in Docker極簡入門(下篇)Docker
- PHP安全性漫談PHP
- Docker安全性(一)——Docker容器真的安全嗎?Docker
- 淺淺談ReduxRedux
- 淺談JavaScript中的thisJavaScript
- 淺談DataSet 的用法
- Android 中能夠作為 Log 開關的一些操作以及安全性淺談Android
- 淺淺淺談JavaScript作用域JavaScript
- 淺析skynet底層框架下篇框架
- Docker安全性(二)——帶來了新的安全功能給DockerDocker
- 淺談 Slack Channel 支援的一些提高工作效率的特性
- Celery淺談
- 淺談flutterFlutter
- 淺談JMM
- 淺談反射反射
- 淺談mockMock
- 淺談SYNPROXY
- 淺談Disruptor
- 淺談IHttpHandlerHTTP
- 淺談 PromisePromise
- 淺談PWA
- 淺談vuexVue
- 淺談JavaScriptJavaScript
- 淺談RMQMQ
- 淺談Zilliqa
- 淺談RxJavaRxJava
- 淺談NginxNginx
- 淺談 JavaScriptCoreJavaScript
- 淺談MVPMVP
- 淺談BitMap