歡迎光臨
每天分享高質量文章

淺談Docker安全性支持

Docker作為最重視安全的容器技術之一,在很多方面都提供了強安全性的預設配置,其中包括:容器root用戶的Capability能力限制、Seccomp系統呼叫過濾、Apparmor的 MAC 訪問控制、ulimit限制、pid-limits的支持,鏡像簽名機制等。這篇文章我們就帶大家詳細瞭解一下。
Docker利用Namespace實現了6項隔離,看似完整,實際上依舊沒有完全隔離Linux資源,比如/proc、/sys、/dev/sd*等目錄未完全隔離,SELinux、time、syslog等所有現有Namespace之外的信息都未隔離。 其實Docker在安全性上也做了很多工作,大致包括下麵幾個方面:
1、Linux內核 Capability 能力限制
Docker支持為容器設置Capabilities,指定開放給容器的權限。這樣在容器中的root用戶比實際的root少很多權限。Docker 在0.6版本以後支持將容器開啟超級權限,使容器具有宿主機的root權限。
2、鏡像簽名機制
Docker 1.8版本以後提供了鏡像簽名機制來驗證鏡像的來源和完整性,這個功能需要手動開啟,這樣鏡像製作者可以在push鏡像前對鏡像進行簽名,在鏡像pull的時候,Docker不會pull驗證失敗或者沒有簽名的鏡像標簽。
3、Apparmor的MAC訪問控制
Apparmor可以將行程的權限與行程Capabilities能力聯繫在一起,實現對行程的強制性訪問控制(MAC)。在Docker中,我們可以使用Apparmor來限制用戶只能執行某些特定命令、限制容器網絡、檔案讀寫權限等功能。
4、Seccomp系統呼叫過濾
使用Seccomp可以限制行程能夠呼叫的系統呼叫(system call)的範圍,Docker提供的預設Seccomp配置檔案已經禁用了大約44個超過300+的系統呼叫,滿足大多數容器的系統呼叫訴求。
5、User Namespace隔離
Namespace為運行中行程提供了隔離,限制他們對系統資源的訪問,而行程沒有意識到這些限制,為防止容器內的特權升級攻擊的最佳方法是將容器的應用程式配置為作為非特權用戶運行,對於其行程必須作為容器中的root用戶運行的容器,可以將此用戶重新映射到Docker主機上權限較低的用戶。映射的用戶被分配了一系列UID,這些UID在命名空間內作為從0到65536的普通UID運行,但在主機上沒有特權。
6、SELinux
SELinux主要提供了強制訪問控制(MAC),即不再是僅依據行程的所有者與檔案資源的rwx權限來決定有無訪問能力。能在攻擊者實施了容器突破攻擊後增加一層壁壘。Docker提供了對SELinux的支持。
7、pid-limits的支持
在說pid-limits前,需要說一下什麼是fork炸彈(fork bomb),fork炸彈就是以極快的速度創建大量行程,並以此消耗系統分配予行程的可用空間使行程表飽和,從而使系統無法運行新程式。說起行程數限制,大家可能都知道ulimit的nproc這個配置,nproc是存在坑的,與其他ulimit選項不同的是,nproc是一個以用戶為管理單位的設置選項,即他調節的是屬於一個用戶UID的最大行程數之和。這部分內容下一篇會介紹。Docker從1.10以後,支持為容器指定–pids-limit 限制容器內行程數,使用其可以限制容器內行程數。
8、其他內核安全特性工具支持
在容器生態的周圍,還有很多工具可以為容器安全性提供支持,比如可以使用Docker bench audit tool(工具地址:https://github.com/docker/docker-bench-security)檢查你的Docker運行環境,使用Sysdig Falco(工具地址:https://sysdig.com/opensource/falco/)來檢測容器內是否有異常活動,可以使用GRSEC 和 PAX來加固系統內核等等。
Linux內核Capability能力限制

Capabilities簡單來說,就是指開放給行程的權限,比如允許行程可以訪問網絡、讀取檔案等。Docker容器本質上就是一個行程,預設情況下,Docker會刪除必須的Capabilities外的所有Capabilities,可以在Linux手冊頁 中看到完整的可用Capabilities串列。Docker 0.6版本以後支持在啟動引數中增加–privileged選項為容器開啟超級權限。
Docker支持Capabilities對於容器安全意義重大,因為在容器中我們經常會以root用戶來運行,使用Capability限制後,容器中的root比真正的root用戶權限少得多。這就意味著,即使入侵者設法在容器內獲取了root權限,也難以做到嚴重破壞或獲得主機root權限。
當我們在docker run時指定了–privileded選項,Docker其實會完成兩件事情:
  1. 獲取系統root用戶所有能力賦值給容器;

  2. 掃描宿主機所有設備檔案掛載到容器內。

下麵我們給大家實際演示一下:
當執行docker run 時未指定–privileded選項:
lynzabo@ubuntu:~$ docker run --rm --name def-cap-con1 -d alpine /bin/sh -c "while true;do echo hello; sleep 1;done"
f216f9261bb9c3c1f226c341788b97c786fa26657e18d7e52bee3c7f2eef755c
lynzabo@ubuntu:~$ docker inspect def-cap-con1 -f '{{.State.Pid}}'
43482
lynzabo@ubuntu:~$ cat /proc/43482/status | grep Cap
CapInh:    00000000a80425fb
CapPrm:    00000000a80425fb
CapEff:    00000000a80425fb
CapBnd:    00000000a80425fb
CapAmb:    0000000000000000
lynzabo@ubuntu:~$
lynzabo@ubuntu:~$ docker exec def-cap-con1 ls /dev
core  fd  full  mqueue  null  ptmx  pts  random  shm  stderr  stdin  stdout  tty  urandom  zero  ...總共15條
lynzabo@ubuntu:~$

如果指定了–privileded選項:
lynzabo@ubuntu:~$ docker run --privileged --rm --name pri-cap-con1 -d alpine /bin/sh -c "while true;do echo hello; sleep 1;done"
ad6bcff477fd455e73b725afe914b82c8aa6040f36326106a9a3539ad0be03d2
lynzabo@ubuntu:~$ docker inspect pri-cap-con1 -f '{{.State.Pid}}'
44312
lynzabo@ubuntu:~$ cat /proc/44312/status | grep Cap
CapInh:    0000003fffffffff
CapPrm:    0000003fffffffff
CapEff:    0000003fffffffff
CapBnd:    0000003fffffffff
CapAmb:    0000000000000000
lynzabo@ubuntu:~$ docker exec pri-cap-con1 ls /dev
agpgart  autofs  bsg  btrfs-control  bus  core  cpu_dma_latency  cuse  dmmidi  dri  ecryptfs
...總共186條
lynzabo@ubuntu:~$

對比/proc/$pid/status ,可以看到兩個容器行程之間能力位圖的差別,加上–privileged的能力位圖與超級用戶的能力位圖一樣。再對比增加–privileged後目錄/dev下檔案變化,可以看到,加了特權後,宿主機所有設備檔案都掛載在容器內。
我們可以看到,使用–privileged引數授權給容器的權限太多,所以需要謹慎使用。如果需要掛載某個特定的設備,可以通過–device方式,只掛載需要使用的設備到容器中,而不是把宿主機的全部設備掛載到容器上。例如,為容器內掛載宿主機聲卡:
$ docker run --device=/dev/snd:/dev/snd …

此外,可以通過–add-cap和–drop-cap這兩個引數來對容器的能力進行調整,以最大限度地保證容器使用的安全。
例如,給容器增加一個修改系統時間的命令:
$ docker run --cap-drop ALL --cap-add SYS_TIME ntpd /bin/sh

查看容器PID,執行getpcaps PID查看行程的能力,執行結果如下:
[root@VM_0_6_centos ~]# getpcaps 652
Capabilities for `652': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_time,...
[root@VM_0_6_centos ~]#

可以看到容器中已經增加了sys_time能力,可以修改系統時間了。
Docker鏡像簽名機制

當我們執行docker pull鏡像的時候,鏡像倉庫再驗證完用戶身份後,會先傳回一個manifest.json檔案,其中包含了鏡像名稱、tag、所有layer層SHA256值,還有鏡像的簽名信息,然後docker daemon會並行的下載這些layer層檔案。Docker 1.8以後,提供了一個數字簽名機制——content trust來驗證官方倉庫鏡像的來源和完整性,簡單來說就是鏡像製作者製作鏡像時可以選擇對鏡像標簽(tag)進行簽名或者不簽名,當pull鏡像時,就可以通過這個簽名進行校驗,如果一致則認為資料源可靠,並下載鏡像。
預設情況下,這個content trust是被關閉了的,你需要設置一個環境變數來開啟這個機制,即:
$ export DOCKER_CONTENT_TRUST=11

當content trust機制被開啟後,Docker不會pull驗證失敗或者沒有簽名的鏡像標簽。當然也可以通過在pull時加上–disable-content-trust來暫時取消這個限制。
Apparmor的MAC訪問控制

AppArmor和SELinux都是Linux安全模塊,可以將行程的權限與行程capabilities能力聯繫在了一起,實現對行程的強制性訪問控制(MAC)。由於SELinux有點複雜,經常都被人直接關閉,而AppArmor就相對要簡單點。Docker官方也推薦這種方式。
Docker自動為容器生成並加載名為docker-default的預設配置檔案。在 Docker 1.13.0和更高版本中,Docker二進制檔案在tmpfs中生成該配置檔案,然後將其加載到內核中。在早於1.13.0的Docker版本上,此配置檔案將在/etc/apparmor.d/docker中生成。docker-default配置檔案是運行容器的預設配置檔案。它具有適度的保護性,同時提供廣泛的應用兼容性。
註意:這個配置檔案用於容器而不是Docker守護行程。運行容器時會使用docker-default策略,除非通過security-opt選項改寫。
下麵我們使用Nginx做演示,提供一個自定義AppArmor配置檔案:
1、創建自定義配置檔案,假設檔案路徑為 /etc/apparmor.d/containers/docker-nginx。
#include 

profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
  #include 
  ...
  deny network raw,
  ...
  deny /bin/** wl,
  deny /root/** wl,
  deny /bin/sh mrwklx,
  deny /bin/dash mrwklx,
  deny /usr/bin/top mrwklx,
  ...
}

2、加載配置檔案:
$ sudo apparmor_parser -r -W /etc/apparmor.d/containers/docker-nginx

3、使用這個配置檔案運行容器:
$ docker run --security-opt "apparmor=docker-nginx" -p 80:80 -d --name apparmor-nginx nginx12

4、進入運行中的容器中,嘗試一些操作來測試配置是否生效:
$ docker container exec -it apparmor-nginx bash1
root@6da5a2a930b9:~# ping 8.8.8.8
ping: Lacking privilege for raw socket.

root@6da5a2a930b9:/# top
bash: /usr/bin/top: Permission denied

root@6da5a2a930b9:~# touch ~/thing
touch: cannot touch 'thing': Permission denied

root@6da5a2a930b9:/# sh
bash: /bin/sh: Permission denied

可以看到,我們通過AppArmor配置檔案可以對容器進行保護。
Seccomp系統呼叫過濾

Seccomp是Linux kernel從2.6.23版本開始所支持的一種安全機制,可用於限制行程能夠呼叫的系統呼叫(system call)的範圍。在Linux系統里,大量的系統呼叫(systemcall)直接暴露給用戶態程式,但是,並不是所有的系統呼叫都被需要,而且不安全的代碼濫用系統呼叫會對系統造成安全威脅。通過Seccomp,我們限製程式使用某些系統呼叫,這樣可以減少系統的暴露面,同時使程式進入一種“安全”的狀態。每個行程進行系統呼叫(system call)時,kernel都會檢查對應的白名單以確認該行程是否有權限使用這個系統呼叫。從Docker 1.10版本開始,Docker安全特性中增加了對Seccomp的支持。
使用Seccomp的前提是Docker構建時已包含了Seccomp,並且內核中的CONFIG_SECCOMP已開啟。可使用如下方法檢查內核是否支持Seccomp:
$ cat /boot/config-`uname -r` | grep CONFIG_SECCOMP=
CONFIG_SECCOMP=y

預設的Seccomp配置檔案為使用Seccomp運行容器提供了一個合理的設置,並禁用了大約44個超過300+的系統呼叫。它具有適度的保護性,同時提供廣泛的應用兼容性。預設的Docker配置檔案可以在moby原始碼profiles/seccomp/下找到。
預設seccomp profile片段如下:
{
 "defaultAction""SCMP_ACT_ERRNO",
 "archMap": [
  {
   "architecture""SCMP_ARCH_X86_64",
   "subArchitectures": [
    "SCMP_ARCH_X86",
    "SCMP_ARCH_X32"
   ]
  },=
  ...
 ],
 "syscalls": [
  {
   "names": [
    "reboot"
   ],
   "action""SCMP_ACT_ALLOW",
   "args": [],
   "comment""",
   "includes": {
    "caps": [
     "CAP_SYS_BOOT"
    ]
   },
   "excludes": {}
  },
  ...
 ]
}

seccomp profile包含3個部分:預設操作,系統呼叫所支持的Linux架構和系統呼叫具體規則(syscalls)。對於每個呼叫規則,其中name是系統呼叫的名稱,action是發生系統呼叫時Seccomp的操作,args是系統呼叫的引數限制條件。比如上面的“SCMP_ACT_ALLOW”action代表這個行程這個系統呼叫被允許,這個call,允許行程可以重啟系統。
實際上,該配置檔案是白名單,預設情況下阻止訪問所有的系統呼叫,然後將特定的系統呼叫列入白名單。
Seccomp有助於以最小權限運行Docker容器。不建議更改預設的Seccomp配置檔案。
運行容器時,如果沒有通過–security-opt選項改寫容器,則會使用預設配置。例如,以下顯式指定了一個策略:
$ docker run --rm \
             -it \
             --security-opt seccomp=/path/to/seccomp/profile.json \
             hello-seccomp

Docker的預設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甚至不映射到真實用戶,這意味著該行程完全沒有主機系統的權限。
在Docker 1.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,執行相同的操作,操作將成功。
SELinux支持

我們知道系統的用戶主要分為系統管理員與一般用戶,而這兩種身份能否使用系統檔案資源與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 (113 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審核通過了。
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 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。
其他內核安全特性工具支持

在容器生態的周圍,還有很多工具可以為容器安全性提供支持。
  1. 可以使用docker-bench-security檢查你的Docker運行環境,如Docker daemon配置,宿主機配置

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

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

本文轉載自公眾號:小米生態雲,點擊查看原文

Kubernetes應用實戰培訓

Kubernetes應用實戰培訓將於2018年10月19日在上海開課,3天時間帶你系統學習Kubernetes本次培訓包括:容器特性、鏡像、網絡;Docker特性、架構、組件、概念、Runtime;Docker安全;Docker實踐;Kubernetes架構、核心組件、基本功能;Kubernetes設計理念、架構設計、基本功能、常用物件、設計原則;Kubernetes的實踐、運行時、網絡、插件已經落地經驗;微服務架構、DevOps等,點擊下方圖片查看詳情。

赞(0)

分享創造快樂