浅谈Docker的安全性支持(上篇)

Docker做为最重视安全的容器技术之一,在不少方面都提供了强安全性的默认配置,其中包括:容器root用户的 Capability 能力限制、Seccomp系统调用过滤、Apparmor的 MAC 访问控制、ulimit限制、pid-limits的支持,镜像签名机制等。这篇文章咱们就带你们详细了解一下。
nginx

上篇文章回顾:从HBase offheap到Netty的内存管理git


写在前面

Docker利用Namespace实现了6项隔离,看似完整,实际上依旧没有彻底隔离Linux资源,好比/proc 、/sys 、/dev/sd*等目录未彻底隔离,SELinux、time、syslog等全部现有Namespace以外的信息都未隔离。 其实Docker在安全性上也作了不少工做,大体包括下面几个方面:
github

一、Linux内核 Capability 能力限制docker

Docker支持为容器设置Capabilities,指定开放给容器的权限。这样在容器中的root用户比实际的root少不少权限。Docker 在0.6版本之后支持将容器开启超级权限,使容器具备宿主机的root权限。json

二、镜像签名机制ubuntu

Docker 1.8版本之后提供了镜像签名机制来验证镜像的来源和完整性,这个功能须要手动开启,这样镜像制做者能够在push镜像前对镜像进行签名,在镜像pull的时候,Docker不会pull验证失败或者没有签名的镜像标签。centos

三、Apparmor的MAC访问控制安全

Apparmor能够将进程的权限与进程capabilities能力联系在一块儿,实现对进程的强制性访问控制(MAC)。在Docker中,咱们可使用Apparmor来限制用户只能执行某些特定命令、限制容器网络、文件读写权限等功能。bash

四、Seccomp系统调用过滤网络

使用Seccomp能够限制进程可以调用的系统调用(system call)的范围,Docker提供的默认 Seccomp 配置文件已经禁用了大约 44 个超过 300+ 的系统调用,知足大多数容器的系统调用诉求。

五、User Namespace隔离

Namespace为运行中进程提供了隔离,限制他们对系统资源的访问,而进程没有意识到这些限制,为防止容器内的特权升级攻击的最佳方法是将容器的应用程序配置为做为非特权用户运行,对于其进程必须做为容器中的 root 用户运行的容器,能够将此用户从新映射到 Docker 主机上权限较低的用户。映射的用户被分配了一系列 UID,这些 UID 在命名空间内做为从 0 到 65536 的普通 UID 运行,但在主机上没有特权。

六、SELinux

SELinux主要提供了强制访问控制(MAC),即再也不是仅依据进程的全部者与文件资源的rwx权限来决定有无访问能力。能在攻击者实施了容器突破攻击后增长一层壁垒。Docker提供了对SELinux的支持。

七、pid-limits的支持

在说pid-limits前,须要说一下什么是fork炸弹(fork bomb),fork炸弹就是以极快的速度建立大量进程,并以此消耗系统分配予进程的可用空间使进程表饱和,从而使系统没法运行新程序。提及进程数限制,你们可能都知道ulimit的nproc这个配置,nproc是存在坑的,与其余ulimit选项不一样的是,nproc是一个以用户为管理单位的设置选项,即他调节的是属于一个用户UID的最大进程数之和。这部份内容下一篇会介绍。Docker从1.10之后,支持为容器指定--pids-limit 限制容器内进程数,使用其能够限制容器内进程数。

八、其余内核安全特性工具支持

在容器生态的周围,还有不少工具能够为容器安全性提供支持,好比可使用 Docker bench audit tool(工具地址:https://github.com/docker/docker-bench-security)检查你的Docker运行环境,使用Sysdig Falco(工具地址:https://sysdig.com/opensource/falco/)来检测容器内是否有异常活动,可使用GRSEC 和 PAX来加固系统内核等等。

此次分享咱们带你们了解一下Docker对前四项是如何安全支持的,下一篇文章会带你们了解剩余内容。

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其实会完成两件事情:

  • 获取系统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"f216f9261bb9c3c1f226c341788b97c786fa26657e18d7e52bee3c7f2eef755clynzabo@ubuntu:~$ docker inspect def-cap-con1 -f '{{.State.Pid}}'43482lynzabo@ubuntu:~$ cat /proc/43482/status | grep CapCapInh:    00000000a80425fbCapPrm:    00000000a80425fbCapEff:    00000000a80425fbCapBnd:    00000000a80425fbCapAmb:    0000000000000000lynzabo@ubuntu:~$lynzabo@ubuntu:~$ docker exec def-cap-con1 ls /devcore  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"ad6bcff477fd455e73b725afe914b82c8aa6040f36326106a9a3539ad0be03d2lynzabo@ubuntu:~$ docker inspect pri-cap-con1 -f '{{.State.Pid}}'44312lynzabo@ubuntu:~$ cat /proc/44312/status | grep CapCapInh:    0000003fffffffffCapPrm:    0000003fffffffffCapEff:    0000003fffffffffCapBnd:    0000003fffffffffCapAmb:    0000000000000000lynzabo@ubuntu:~$ docker exec pri-cap-con1 ls /devagpgart  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 652Capabilities 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 配置文件:

一、建立自定义配置文件,假设文件路径为 /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, ...}复制代码

二、加载配置文件

$ sudo apparmor_parser -r -W /etc/apparmor.d/containers/docker-nginx复制代码

三、使用这个配置文件运行容器

$ docker run --security-opt "apparmor=docker-nginx" -p 80:80 -d --name apparmor-nginx nginx12复制代码

四、进入运行中的容器中,尝试一些操做来测试配置是否生效:

$ docker container exec -it apparmor-nginx bash1root@6da5a2a930b9:~# ping 8.8.8.8ping: Lacking privilege for raw socket.root@6da5a2a930b9:/# topbash: /usr/bin/top: Permission deniedroot@6da5a2a930b9:~# touch ~/thingtouch: cannot touch 'thing': Permission deniedroot@6da5a2a930b9:/# shbash: /bin/sh: Permission denied复制代码

能够看到,咱们经过 apparmor 配置文件能够对容器进行保护。

Seccomp系统调用过滤

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

相关文章
相关标签/搜索