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

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

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(工具地址:)來檢測容器內是否有異常活動,可以使用GRSEC 和 PAX來加固系統核心等等。

這次分享我們帶大家瞭解一下Docker對前四項是如何安全支援的,下一篇文章會帶大家瞭解剩餘內容。


1Linux核心Capability能力限制


Capabilities簡單來說,就是指開放給程式的許可權,比如允許程式可以訪問網路、讀取檔案等。Docker容器本質上就是一個程式,預設情況下,Docker 會刪除 必須的 capabilities 外的所有 capabilities,可以在 Linux 手冊頁 中看到完整的可用 capabilities 列表。Docker 0.6版本以後支援在啟動引數中增加 --privileged 選項為容器開啟超級許可權。

Docker支援Capabilities對於容器安全意義重大,因為在容器中我們經常會以root使用者來執行,使用Capability限制後,容器中的 root 比真正的 root使用者許可權少得多。這就意味著,即使入侵者設法在容器內獲取了 root 許可權,也難以做到嚴重破壞或獲得主機 root 許可權。

當我們在docker run時指定了--privileded 選項,docker其實會完成兩件事情:

  • 獲取系統root使用者所有能力賦值給容器;

  • 掃描宿主機所有裝置檔案掛載到容器內。

下面我們給大家實際演示一下:

當執行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 能力,可以修改系統時間了。


2Docker映象簽名機制


當我們執行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來暫時取消這個限制。


3Apparmor的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 <tunables/global>

profile docker-nginx flags=(attach_disconnected,mediate_deleted) {
 #include <abstractions/base>
 ...
 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 配置檔案可以對容器進行保護。


4Seccomp系統呼叫過濾


Seccomp是Linux kernel 從2.6.23版本開始所支援的一種安全機制,可用於限制程式能夠呼叫的系統呼叫(system call)的範圍。在Linux系統裡,大量的系統呼叫(systemcall)直接暴露給使用者態程式,但是,並不是所有的系統呼叫都被需要,而且不安全的程式碼濫用系統呼叫會對系統造成安全威脅。透過Seccomp,我們限制程式使用某些系統呼叫,這樣可以減少系統的暴露面,同時使程式進入一種“安全”的狀態。每個程式進行系統呼叫(system call)時,kernel 都會檢查對應的白名單以確認該程式是否有許可權使用這個系統呼叫。從Docker1.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文件列舉了所有不在白名單而被有效阻止的重要(但不是全部)系統呼叫以及每個系統呼叫被阻止的原因

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

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


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559359/viewspace-2644659/,如需轉載,請註明出處,否則將追究法律責任。

相關文章