浅谈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 <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配置文件可以对容器进行保护。

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.jpg

由上图我们可以发现:

(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审核通过了。

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为系统提供更丰富的安全限制。等等。


原文链接:https://mp.weixin.qq.com/s/-fzrhcbbrAl0OYvQIaQmEAhttps://mp.weixin.qq.com/s/nXtpvV0byDHyLpXD8aFWcg

0 个评论

要回复文章请先登录注册