Docker的安全性
Docker的安全性主要体现在如下几个方面:
-
Docker容器的安全性这是指容器是否会危害到宿主机或其他容器;
-
镜像的安全性用户如何确保下载下来的镜像是可信的、未被篡改过的;
-
Docker daemon的安全性如何确保发送给daemon的命令是由可信用户发起的。用户通过CLI或者REST API向daemon发送命令已完成对容器的各种操作,例如通过docker exec命令删除容器里的数据,因此需要保证client与daemon的连接时可信的。
Docker容器的安全性
容器的安全性问题的根源在于容器和宿主机共用内核,因此受攻击的面特别大,另外,如果容器里的应用导致Linux内核崩溃,那么毫无疑问,整个系统哥都会崩溃。这一点与虚拟机是不同的,虚拟机与宿主机的接口非常有限,而且虚拟机崩溃一般不会导致宿主机崩溃。
在共用内核的前提下,容器主要通过内核的Cgroup和Namespace这两大特性来达到容器隔离和资源限制的目的。目前Cgroup对系统资源的限制已经比较完善了,但Namespace的隔离还是不够完善,只有PID、mount、network、UTS、IPC和user这几种。而对于未隔离的内核资源,容器访问时也就会存在影响到宿主机及其他容器的风险。
比如,procfs里的很多接口都没有被隔离,因此通过procfs可以查询到整个系统的信息,例如系统的CPU、内存等资源信息。这也是为什么Docker容器的procfs是以只读方式挂载的,否则修改procfs里的内核参数将会影响甚至破坏宿主机。还有内核syslog也是没有被隔离的,因此在容器内可以看到容器外其他进程产生的内核syslog。
Namespace的隔离非但是不完善的,甚至可以说是不可能完善的。这是共用内核导致的固有缺陷,并且未来Linux内核社区也不会对此做太多的改进。
安全策略 – Cgroup
Cgroup用于限制容器对CPU、内存等关键资源的使用,防止某个容器由于过度使用资源,导致宿主机或者其他容器无法正常运作。
限制CPU
Docker能够指定一个容器的CPU权重,这是一个相对权重,与实际的处理速度无关。事实上,没有办法限制一个容器只可以获得1GHZ的CPU。每个容器默认的CPU权重是1024,简单的说,假设只有两个容器,并且这两个容器竞争CPU资源,那么CPU资源将在这两个容器间平均分配。如果其中一个容器启动时设置的CPU权重是512,那它相对于另一个容器只能得到一半的CPU资源,因此这两个容器可以得到的CPU资源分别是33.3%和66.6%。但如果另外一个容器是空闲的,第一个容器则会被允许使用100%的CPU。简而言之,CPU资源不是预先硬性分配好的,而是跟各个容器在运行时对CPU资源的需求有关。例如,需要为容器设置CPU权重为2000,命令如下:
-
$ docker run --rm -ti -c 2000 ubuntu bash
-
另一方面,Docker也可以明确限制容器对CPU资源的使用上限
-
$ docker run --rm -ti --cpu-period=500000 --cpu-quota=250000 ubuntu /bin/bash
-
上面的命令表示这个容器在每个0.5秒里最多只能运行0.25秒。除此之外,Docker还可以把容器的进程限定在特定的CPU上运行,例如将容器限定在0号和1号CPU上运行
-
$ docker run --rm -ti --cpuset-cpus=0,1 ubuntu bash
-
限制内存
除了CPU,内存也是应用不可或缺的一个资源,因此一般来说必须限制容器的内存使用量。限制命令如下
-
$ docker run --rm -ti -m 200M ubuntu bash
-
这个例子将容器可使用的内存限制在200MB。不过事实并非如此简单,在系统发现内存不足时,会将部分内存置换到swap分区里,因此如果只限制内存使用量,可能会导致swap分区被用光。通过–memory-swap参数可以限制容器对内存和swap分区的使用,如果只是指定-m而不指定–memory-swap,那么总的虚拟内存大小(即memory加上swap)是-m参数的两倍。
限制块设备I/O
对于块设备,因为磁盘带宽有限,所以对于I/O密集的应用,CPU会经常处于等待I/O完成的状态,也就是idle状态,这会造成其他应用可能也要等待那个应用的I/O完成,从而影响到其他的容器。Docker目前只能设置容器的I/O权重,无法限制容器的I/O读写速率的上限,但这个功能已经在开发之中了。现阶段用户可以通过写Cgroup文件来实现。
-
# 创建容器
-
$ docker run --rm -ti --name container1 ubuntu bash
-
# 查询容器的写速率
-
dd if=/dev/zero of=testfile0 bs=8k mount=5000 oflag=direct
-
下面通过修改相应的Cgroup文件来限制写磁盘的速度。
-
# 查找容器挂载的文件系统“/dev/mapper”的位置
-
$ mount|grep ContainerID
-
# 查看容器挂载的文件系统中的文件(Path为上条命令取得的返回结果)
-
# 返回结果“->”标记后会有一个新的路径,我们先称其为DeviceFilePath
-
$ ls -l Path
-
# 查询容器挂载的设备号
-
$ ls DeviceFilePath -l
-
# 返回结果中“root disk”后面会有一串用“,”隔开的数字,假设是128和256
-
# 限制容器的写速度
-
$ sudo echo '128:256 10240000' >/sys/fs/cgroup/blkio/system.slice/docker-DockerID.scope/blkio.throttle.write_bps_device
-
# 10240000是每秒可写入的最多的字节数。
-
ulimit
ulimit是Linux系统中的一个指令,可以对某些类型的资源起到限制作用,包括core dump文件的大小、进程数据段的大小、可创建文件的大小、打开文件的数量、进程栈的大小、CPU时间、单个用户的最大线程数、进程的最大虚拟内存等。
在Docker1.6之前,Docker容器的ulimit设置,继承自Docker daemon。但是很多时候,对于单个容器来说,这样的ulimit实在太高了。在Docker1.6之后,可以设置全局默认的ulimit,如设置CPU时间
$ sudo docker daemon --default-ulimit cpu=1200
或者在启动容器时,单独对其ulimit进行设置
-
$ docker run --rm -ti --ulimit cpu=1200 ubuntu bash
-
进入容器后可以查看
-
# ulimit -t
-
# 返回结果:1200
容器组网
在接入容器隔离不足的情况下,将受信任的和不受信任的容器组网在不同的网络中,可以降低风险。关于Docker的各种网络模型及方案可以参考“Docker网络”
容器 + 全虚拟化
如果将容器运行在全虚拟化环境中(如在虚拟机中运行容器),这样就算容器被攻破,虚拟机还具有保护作用。目前一些安全需求很高的应用场景采用的就是这种方式,如公有云场景。
镜像签名
Docker可信镜像及升级框架(The Update Framework,TUF)是Docker 1.8所提供的一个新功能,可以校验镜像的发布者。当发布者将镜像push到远程仓库时,Docker会对镜像用私钥进行签名,之后其他人pull该镜像的时候,Docker就会用发布者的公钥来校验该镜像是否和发布者所发布的镜像一致,是否被篡改过,是否是最新版。点击查看更多TUF相关内容。
日志审计
Docker 1.6版本开始支持日志驱动,用户可以将日志直接从容器输出到如syslogd这样的日志系统中,通过docker –help可以看到Docker daemon支持log-driver参数,目前支持的类型有none、json-file、syslog、gelf和fluentd,默认的日志驱动是json-file。
除了在启动Docker daemon时可以指定日志驱动以外,也可以对单个容器指定驱动,如
-
$ docker run --rm -ti --log-driver="syslog" ubuntu bash
-
通过docker inspect ContainerID
可以看到容器使用了哪种日志驱动。另外,只有json-file
支持docker logs
命令,docker logs ContainerID
。
监控
在使用容器时,应注意监控容器的信息,以便及时补救。这些信息包括运行状态、容器的资源使用情况等。
查看容器的运行状态,命令:docker ps -a
。
容器的资源使用情况主要指容器对内存、网络I/O、CPU、磁盘I/O的使用情况等,命令:docker stats ContainerID
。
文件系统级防护
Docker可以设置容器的根文件系统为只读模式,只读模式的好处是即使容器与host使用的是同一个文件系统,也不用担心会影响甚至破环host的根文件系统。但需要注意的是,必须把容器里remount进程的文件系统能力禁掉,否则在容器内又可以把文件系统重新挂载为可写。用户甚至可以禁止容器挂载任何文件系统。
可读写挂载:
-
$ docker run --rm -ti ubuntu bash
-
-
只读挂载:
-
$ docker run --rm -ti --read-only ubuntu bash
-
# echo "hello" >/home/test.txt
-
# 返回结果:bash:/home/test.txt:Read-only file system
-
capability
从2.2版开始,Linux有了capability的概念,它打破了Linux操作系统中超级用户/普通用户的概念,让普通用户也可以做只有超级用户才能完成的工作。capability可以作用在进程上,也可以作用在程序文件上。它与sudo不同,sudo可以配置某个用户可以执行某个命令或更改某个文件,而capability则是让程序拥有某种能力。
每个进程有三个和能力有关的位图:Inheritable(I)\Permitted(P)\Effective(E)
,我们可以通过/proc/<PID>/status
来查看进程的capability。
-
命令:cat /proc/$$/status | grep Cap
-
结果:
-
# 能够被当前进程执行的程序继承的capability。
-
CapInh: 0000000000000000
-
# 进程能够使用的能力,可以包含CapEff中没有的能力,这些能力是被进程自己临时放弃的,因此可以把CapEff看作是CapPrm的一个子集。
-
CapPrm: ffffffffffffffff
-
# 当一个进程要进行某个特权操作时,操作系统会检查CapEff的对应位是否有效,而不再是检查进程的有效UID是否为0。
-
CapEff: ffffffffffffffff
如需了解更多请查阅Linux手册。
Docker启动容器的时候,会通过白名单的方式来设置传递给容器的capability,默认情况下,这个白名单只包含CAP_CHOWN等少数的能力。用户可以通过 -–cap-add
和 -–cap-drop
这两个参数来修改这个白名单。
-
$ docker run --rm -ti --cap-drop=chown ubuntu bash
-
# chown 2.2/etc/hosts
-
# 返回结果:chown:changing ownership of '/etc/hosts': Operation not permitted
-
发现禁掉CAP_CHOWN能力后,在容器里就无法改变容器的所有者了。如果不禁掉则正常。如下
-
$ docker run --rm -ti ubuntu bash
-
# chown 2.2/etc/hosts
-
容器应遵循最小权限原则,尽量不要用–privileged参数,不需要的能力全部去掉,甚至禁掉所有的能力。
-
$ docker run --rm -ti --cap-drop=all ubuntu bash
-
SELinux
操作系统中访问控制安全的发展:早期的操作系统几乎没有考虑安全问题,一个用户可以访问任何文件或资源,但很快出现了访问控制机制来增强安全性,其中主要的访问控制在今天被称为自主访问控制(DAC – Discretionary[dɪ’skrɛʃə’nɛri] Access Control)。DAC通常允许授权用户(通过其程序如一个shell)改变客体的访问控制属性,这样就可指定其他用户是否有权访问该客体。大部分DAC机制是基于用户身份访问控制属性的,通常表现为该访问控制列表机制。DAC的主要特性是,单个用户(通常指某个资源的属主)可以指定其他人是否能访问该资源。
但是,DAC也有其自身的安全脆弱性,它只约束了用户、同用户组内的用户、其他用户对文件的可读、可写、可执行权限,这对系统的保护作用非常有限。为了克服这种脆弱性,出现了强制访问控制(MAC – Mandatory[‘mændət(ə)rɪ] Access Control)机制,其基本原理是利用组织的安全策略来控制对客体的访问,且这种访问不被单个程序所影响。此项研究最早由军方资助,目的是保护机密政府部门数据的机密性。
SELinux(Security-Enhanced Linux)是美国国家安全局(NSA)对于强制访问控制的实现,它是Linux历史上最杰出的安全子系统。在这种访问控制体系的限制下,进程只能访问那些在它的任务中所需的文件。对于目前可用的Linux安全模块来说,SELinux功能最全面,而且测试最充分,它是基于对MAC 20年的研究基础上建立的。
SELinux定义了系统中每个用户、进程、应用和文件访问及转变的权限,然后使用一个安全策略来控制这些实体(即用户、进程、应用和文件)之间的交互,安全策略指定了如何严格或宽松的进行检查。另外,SELinux比较复杂。
SELinux跟内核模块一样,也有模块的概念,需要先根据规则文件编译出二进制模块,然后插入到内核中。在使用SELinux前,需要安装一些包,以Fedora 20为例,需要安装以下组件:
-
checkpolicy
-
libselinux
-
libsemanage
-
libsepol
-
policycoreutils
源码可以在https://github.com/SELinuxProject/selinux中找到,但建议不要自己来编译,太耗费时间,而且编译了也不见得好用。若自己开发SELinux策略,还需安装 selinux-policy-devel。
在Fedora 20中,可以直接用yum安装SELinux开发所需的工具
-
$ sudo yum -y install libselinux.x86_64 libselinux-devel.x86_64 libselinux-python.x86_64 libselinux-utils.x86_64
-
$ sudo yum -y install selinux-policy.noarch selinux-policy-devel.noarch selinux-policy-targeted.noarch
-
$ sudo yum -y install crossfire-selinux.x86_64 libselinux-devel.i686 checkpolicy.x86_64 policycoreutils.x86_64
-
$ sudo yum -y install policycoreutils-devel.x86_64 selinux-policy-devel.noarch selinux-policy-targeted.noarch
-
安装完成之后就可以学习一个Github上的例子,获取源码
-
$ git clone https://github.com/pcmoore/getpeercon_server.git
-
查看策略文件
-
$ cd getpeercon_server
-
$ cat selinux/gpexmple.te
-
会发现返回一坨天文似的东西,想看懂还需要看一本《SELinux by Example》。查看测试程序的代码
-
$ cat src/getpeercon_server.c......
-
又是返回一坨。上面的代码中有创建tcp socket及bind tcp端口的动作,但是上面的策略文件中没有bind tcp端口的策略,不能成功的bind tcp端口。要测试该例子,首先需要创建SELinux模块
-
$ sudo make build
-
$ sudo make install
-
然后关闭SELinux,再插入新编译的模块,重启SELinux,并打上正确的标签
-
$ sudo setenforce 0
-
$ sudo semodule -i selinux/gpexmple.pp
-
$ sudo setenforce 1
-
$ sudo restorecon /usr/bin/getpeercon_server
-
之后运行getpeercon_server
-
$ getpeercon_server 8080
-
就像多数事情一样不会一帆风顺,出错了,查看日志
-
$ cat /var/log/audit/audit.log......
-
根据日志中“success=no exit=-13”了解到getpeercon_server执行失败,SELinux阻止了getpeercon_server绑定端口。解决方法
$ cat /var/log/audit/audit.log | audit2allow -m local
这里的audit2allow是用Python写的一个命令,用来处理日志,把日志中违反策略的动作记录转换成access vector。然后把这条命令复制输出到gpexmple.te中,然后编译插入操作,在运行
-
$ getpeercon_server 8080
-
启动成功。感觉例子好复杂。但在Docker中使用SELinux却非常简单。Docker使用SELinux的前提是系统支持SELinux,SELinux功能已经打开,并且已插入了Docker的SELinux模块,目前RHEL 7、Fedora 20都已自带该模块。查看系统是否支持Docker的SELinux环境
$ sudo semodule -l |grep docker
如果有Docker的SELinux模块(即返回类似docker 1.0.0的信息),说明该系统已经支持Docker的SELinux环境。Docker SELinux模块已经帮我们做了复杂的SELinux策略,我们只需在Docker daemon
启动的时候加上--selinux-enabled=true
选项就可以使用SELinux了。
-
$ sudo docker daemon --selinux-enabled=true
-
另外也可以在启动容器时,使用–-security-opt
选项对指定的文件做限制(前提是Docker daemon
启动时加了--selinux-enabled=true
)
AppArmor
AppArmor也是一种MAC控制机制,其主要作用是设置摸个可执行程序的访问控制权限,可以限制程序读/写某个目录/文件,打开/读/写网络端口等。AppArmor是一个高效和易于使用的Linux系统安全特性,它对操作系统和应用程序进行了从内到外的保护,即使是0day漏洞和未知的应用程序漏洞所导致的攻击也可被识破。AppArmor安全策略可以完全定义个别应用程序所能访问的系统资源与各自的特权,它包含了大量的默认策略,并将先进的静态分析和基于学习的工具结合了起来,可以在很短的时间内,为非常复杂的应用制定AppArmor规则。配置文件官方文档。
Docker daemon在启动过程中会判断当前内核是否支持AppArmor,若支持,就创建默认的AppArmor配置文件/etc/apparmor.d/docker,并应用这个配置文件。启动容器时,在初始化过程中Docker会使用相应的AppArmor配置作用于容器。也可以使用–security-opt选项来指定作用于容器的AppArmor配置文件。
制定一个AppArmor规则,并应用到容器上。先拷贝一个模板
-
$ sudo cp /etc/apparmor.d/docker /etc/apparmor.d/container
-
编辑/etc/apparmor.d/container,将
profile docker-default flags=(attach_disconnected,mediate_deleted){
改为
profile container-default flags=(attach_disconnected,mediate_deleted){
上面修改的主要是配置的名字,该名字在传给Docker时要用到,也可以修改配置文件中其他的内容(如在配置文件中加入“deny/etc/hosts rwklx,”一行,容器启动后执行“cat /etc/hosts”命令时会发现容器没有权限读取/etc/hosts的内容了)。使用这个配置
$ docker run --rm -ti --security-opt apparmor:container-default ubuntu bash
在使用Docker的过程中,最好打开SELinux或AppArmor。目前在支持SELinux的系统上,Docker的SELinux是默认关闭的,需要在启动Docker daemon
时加上--selinux-enabled=true
参数。而在支持AppArmor的系统上,AppArmor的功能默认是开启的。
Seccomp
Seccomp(secure computing mode)是一种Linux内核提供的安全特性,它可以实现应用程序的沙盒机制,以白名单或黑名单的方式限制进程进行系统调用。
Seccomp首次于内核2.6.12版合入Linux主线。早期的Seccomp只支持过滤少数几个系统调用。较新版本的内核支持动态Seccomp策略,也就是seccomp-bpf
,因为支持用BPF生成过滤规则,从而使Seccomp可以限制任意的系统调用,并且可以限制系统调用传入的参数。
Seccomp的使用
-
生成BPF形式的过滤规则;
-
调用prctl系统调用将规则传入内核。
在Docker容器启动的过程中,会对Seccomp设置一个默认的配置,但目前还不支持命令行参数做配置。点击了解更多Seccomp相关。
grsecurity
grsecurity提供了一个系统的内核patch,使Linux内核的安全性大大增强,并且它提供了一些工具让用户配置、使用这些安全特性。grsecurity可以用来控制资源访问权限。下面是一张关于grsecurity、SELinux和AppArmor的对比图。
在grsecurity官网有更完整的对比,这里只是一部分。由于grsecurity提供的安全特性是对整个系统都有效的,因此用户不需要对Docker做专门的配置。不过grsecurity并没有被Linux内核主线接受,因此Redhat和Ubuntu等发行版都不支持grsecurity。
与Docker安全相关的项目
-
Notary
Docker对安全模块进行了重构,剥离出了名为Notary的独立项目。Notary的目标是保证server和client之间的交互使用可信任的连接,用于解决互联网的内容发布的安全性。该项目并未局限于容器应用,在容器场景下可以对镜像源认证、镜像完整性等安全需求提供更好的支持。点击了解更多信息。 -
docker-bench-security
docker-bench-security提供一个脚本,它可以检测用户的生产环境是否符合Docker的安全实践。点击了解更多信息。
安全加固
上面提到了Docker的安全策略,接下来将结合上面的安全策略做一个加固Docker安全的实例。
主机逃逸
主机逃逸实为虚拟机逃逸,主要指利用虚拟机软件或虚拟机中运行的软件的漏洞进行攻击,以达到攻击或控制虚拟机宿主操作系统的目的,下文的Shocker攻击就属于这类攻击,在此为了方便理解,暂且简单的把Docker容器当作虚拟机对待。Docker的安全问题主要来源于容器的隔离性,容器隔离性的不足可能导致容器影响host,或者影响其他容器,Shocker攻击就是容器影响host的一个例子。
-
Shocker攻击
Github上有个项目叫Shocker,它描述了怎样逃逸Docker容器并读取到host的/etc/shadow
文件的内容。完整代码。
下载Shocker源码
-
$ git clone https://github.com/gabrtv/shocker.git
-
$ cd shocker
在编译之前,需要将main函数中的
if((fd1 = open("/.dockerinit",O_RDONLY))<0)
改为
if((fd1 = open("/etc/hosts",O_RDONLY))<0)
然后构建镜像
-
$ docker build -t shocker:latest .
-
接着运行Shocker
-
$ docker run --rm -ti --cap-add=all shocker bash
-
# ./shocker
-
接下来容器中的Shocker就会遍历host的文件系统,并读取/etc/shadow文件。
-
Shocker原理
Shocker攻击的核心是利用了一个不常见的系统调用:open_by_handle_at
。它和name_to_handle_at
一起将系统调用open分解成了两步。
handle是由name_to_handle_at
系统调用得来的,它本身对用户是透明的。Shocker攻击没有使用name_to_handle_at
,它通过暴力手段(brute force)猜测出指向host根目录的handle,由此达到读取host文件系统中文件的目的。
另外,之所以要将/.dockerinit
改为/etc/hosts
,是因为旧版Docker的/.dockerinit
是由主机bind mount
到容器的,但较新版本的Docker已经不是这样了,因此要改用/etc/hosts
,这个也是由主机bind mount
的。对此,在容器中输入mount
命令就能看到
-
$ docker run --rm -ti shocker bash
-
# mount
-
另外,除了/etc/hosts
,通过/etc/resolv.conf
或/etc/hostname
,上面的攻击也同样有效。
-
安全加固 – capability
Shocker攻击的关键是执行了系统调用open_by_handle_at,这个系统调用需要用到dac_read_search这个capability,如果去掉这个capability,攻击自然就不奏效了
-
$ docker run --rm -ti --cap-add=all --cap-drop=dac_read_search shocker bash
-
# ./shocker
返回一坨···,结论就是:Operation not permitted
!
其实,Docker默认提供以下十几种能力:
-
Capabilities:[]string{
-
"CHOWN",
-
"DAC_OVERRIDE",
-
"FSETID",
-
"FOWNER",
-
"MKNOD",
-
"NET_RAW",
-
"SETGID",
-
"SETUID",
-
"SETFCAP",
-
"SETPCAP",
-
"NET_BIND_SERVICE",
-
"SYS_CHROOT",
-
"KILL",
-
"AUDIT_WRITE"
-
}
-
这其中并不包括dac_read_search
,也就是说,只要命令行不设置任何capability的参数,那上面的攻击也是不奏效的。从中我们也可以总结出一点,赋予容器的能力越小,相对越安全,即赋予容器必需的最小能力。所以启动容器时最好不要使用--privileged
,并且将不需要的能力尽量都去掉。
-
安全加固 – SELinux
要把SELinux和Docker结合起来,需要内核支持SELinux,而且要在系统打开,并且在启动Docker daemon
时加SELinux选项。以Fedora 20为例,要先确保Docker的SELinux模块已经插入到内核(发行版本中已有)。启动Docker daemon
-
$ sudo docker daemon --selinux-enabled=true
-
同样以Shocker为例,运行Shocker
-
$ docker run --rm -ti --cap-add=all shocker bash
-
# ./shocker
-
返回的结果很有意思,shocker的代码在暴力破解的过程中使用了一个循环,在第一次没有成功的情况下开始执行循环操作。通过查看audit日志就可以看出这种方式永远也不会成功。因为日志信息中显示Shocker成功的找到了/etc/shadow
,但是在读取内容时被SELinux阻止了。目前SELinux是效果最好的Docker安全加固手段。
-
安全加固 – AppArmor
《Docker的进阶与实战》一书的作者的建议是在Ubuntu上做AppArmor的测试,修改上面提到的/etc/apparmor.d/container
配置文件,在“profile container-default flags”
一项下加入一行“deny /etc/hosts rwklx,”
,然后应用该规则,再运行Shocker
-
$ sudo apparmor_parser -r /etc/apparmor.d/container
-
$ docker run --rm -ti --cap-add=all --security-opt apparmor:container-default shocker bash
-
# ./shocker
-
返回的结果最终的结论:open: Permission denied
。也就是说攻击依然不奏效,“deny /etc/hosts rwklx,”
阻止了容器内的其他程序对/etc/hosts
的读取。
Docker安全遗留问题
在Docker的安全问题上Docker社区做了很多的工作,但Docker依然有不少跟安全相关的问题尚未解决。
User Namespace
User Namespace可以将host中的一个普通用户映射成容器里的root用户,不过虽然允许进程在容器里执行特权操作,但这些特权只局限于该容器内。这对容器的安全是一个非常大的提升,恶意程序通过容器入侵host或者其他容器的风险大大降低,但这并不意味着容器就足够安全了。另外,由于内核层面隔离性不足,如果用户在容器的一个特权操作会影响到容器外,那么这个特权操作一般也是不被User Namespace所允许的。
非root运行Docker daemon
目前Docker daemon需要由root用户启动,而Docker daemon创建的容器以及容器里运行的应用实际上也是以root用户运行的。实现由普通用户启动Docker daemon和运行容器,有益于Docker的安全。但这个问题很难解决,因为创建容器需要执行很多特权,包括挂载文件系统、配置网络等。目前社区还没有一个好的方案。
Docker热升级
Docker管理容器的方式是中心式管理,容器由主机上的Docker daemon进程统一管理。中心式管理方式对于第三方的任务编排工具并不友好,因为什么功能都需要跟Docker关联起来。更大的问题是,如果Docker daemon挂掉了,重启daemon后,它无法接管容器,容器也不能运行了。在实际应用中,很多业务都是不能中断的,而停止容器就往往相当于停止业务,但如果因为安全漏洞的原因需要升级Docker,用于就将处于两难的境地。点击了解该问题的进展。
磁盘限额
默认情况下,Docker镜像、容器rootfs、数据卷都存放在/var/lib/docker
目录里,也就是说跟host是共享同一个文件系统的。如果不对Docker容器做磁盘大小的配额限制,容器就可能用完磁盘的可用空间,导致host和其他容器无法正常工作。
但是目前Docker几乎没有提供任何接口用于限制容器的磁盘大小。但graphdriver为devicemapper时,容器会被默认分配一个100GB的空间。这个空间大小可以在启动Docker daemon时设置为另一个默认值,但无法对每个容器单独设置一个不同的值。
-
$ sudo docker daemon --storage-opt dm.basesize=5G
-
除此之外,用户只能通过其他手段自行做一些隔离措施,例如为/var/lib/docker
单独分配一个磁盘或分区。
网络I/O
目前同一台机器上的Docker容器会共享宽带,但这可能出现某个容器占用大部分带宽资源,从而影响其他需要网络资源的容器正常工作的情况。Docker需要一个好的网络方案,除了要解决容器跨主机通信的问题,还要解决网络I/O限制的问题。
小结
Docker的隔离性还远达不到虚拟机的水平,应该避免把Docker容器当成虚拟机来使用,除非在虚拟机里部署容器,否则一般来说Docker容器应该只跑可信应用。另外,用户应该评估自己所需的安全等级,针对自身的需求采取相应的安全策略。而这些策略说白了就是对系统做的加减法,通过各种限制来达到安全性,这也是最主流、有效的加固方法。此外,还应该保证内核的安全和稳定,并启用监控、容错等系统。