原文 by 绝不避谈docker安全

容器技术的发展如火如荼,相关的安全技术也在不断的发展。早在容器诞生之初,行业就将其与虚拟机进行对比,其中安全的维度一直被诟病。随着容器对于软件封装优势的逐渐普及,以及安全改善的渐入佳境,这样的比较也就逐渐消散。
然而容器技术的安全问题,究竟如何?理应一个避无可避的话题。一项技术要在企业中落地,技术本身必须是安全的,同时技术的使用也必须安全。只有两者的有效结合,才能达到安全的最佳境界。举个简单的例子,上个月全球轰动的“GitLab误删”事件让所有的IT人员看到了,安全问题爆发带来的危害。通过该事件,也许我们并不能直接定位到某项技术的漏洞,但是流程安全以及人员的操作不规范,已然造成了非常严重的信息安全后果。

那么容器的世界当中,是否存在同样的流程安全以及操作不规范?除此之外,技术本身是否存在隐患呢?

答案是肯定的,以下我将从这两个角度来谈容器技术的安全。由于Docker几乎可以认为是容器技术的代表,以下绝大多数以Docker为典型进行分析。

安全问题的披露,一般所造成的影响都会比较大。因此,行业中多数都是浓墨描述安全问题可能造成的后果,关于如何重现或者利用安全问题的过程,一般都讳莫如深,本文有些攻击方式也将点到即止。另外,安全问题,细分的领域比较多,分析不全,还望见谅。

Docker Daemon安全

Docker Daemon是掌握容器命运的指挥官,那么指挥官的安全,可想而知。举一个夸张的例子,一旦虎符交出,整个军队有可能面临灭顶之灾,最后还有可能牵连国家。

大家都知道目前Docker抽象出了规范的API,提供unix socket和tcp两种访问方式,我们亦可以简单地认为供本地或远程访问。存在通信,我们往往会关注通信是否认证。据笔者所知,使用TLS保障用户与Docker Daemon通信认证的依然是少数,甚至很多企业的生产环境或者容器公有云服务,也是毫不知觉。

那么,没有TLS的保障,会造成什么结果呢?答案是:一!无!所!有!

首先说一个笔者自身的经历。早在2年前,容器技术刚开始走热的时候,国内诞生了一大批公有容器服务提供商。尝试体验了之后,我发现有几家在这方面根本没有意识到问题的严重性。当然,我也寻找合适机会,提醒了这些厂商。

Docker Daemon监听了TCP端口并且没有TLS认证,很自然的,我们会联想到:如果外界获悉Docker Daemon的IP地址,那立即可以控制该Docker Daemon;同时倘若不知道IP地址,该Docker Daemon管理的Docker容器也可以通过网桥间接来控制Docker Daemon。

那么,为什么控制Docker Daemon就会造成一无所有呢?控制Docker Daemon,可以增删改查所有的容器,这一点不难理解。难道除了容器,连宿主机上的内容,都无法保全吗?答案是肯定的,且看如下的分析:

宿主机上的进程如何影响?通过Docker Daemon创建一个容器docker run --it --pid host --ipc host ubuntu:14.04 bash,将该容器的PID命名空间和IPC命名空间都设定为Host模式,那么容器中的进程就有能力看到宿主机上的所有进程,并且还会有能力管理宿主机上的进程。

宿主机上的数据如何影响?通过Docker Daemon创建一个容器,将宿主机的根目录挂载到容器内部docker run -it -v /:/data ubuntu bash, 那么在容器内部完全可以查阅宿主机上的所有内容。恶意用户,可以选择窥私,可以选择窃取,同样也可以选择摧毁。

原则上只要让容器中的进程拥有强大的root权限(拥有Privilege权限的root可以认为是强大的root,默认创建容器内的root用户并不具有最为强大的权限),如docker run --it --privileged ubuntu bash,就可以让容器内的进程肆意做与内核交互的事情,比如使用sysctl管理宿主机的操作系统配置等。

除以上之外,当然还有其他方面的操作,同样会造成安全隐患,比如在容器内部关闭整台宿主机等。

以上分析并没有丝毫的危言耸听,仔细想来,一步步回溯,我们发现Docker Daemon的控制权几乎就代表着将宿主机的root权限,因此必须得慎重处理。

不过理性看来,这并不是Docker的漏洞,不是Docker的安全问题,而是属于Docker的使用安全范畴。举个例子,就很好理解,倘若用户将MySQL的3306端口暴露在公网上,并没有设置MySQL 引擎的密码,那么数据安全,管理安全均无法保障。这些道理都是一样的。读到这里,回顾下危害,也许有人就会有点不安了,如果你恰巧这方面的隐患,解决方案很重要,后续我们可以再交流。

不过,我相信依然有很多的读者,看到这会心一笑,因为他们肯定早就看到这方面的安全隐患,并且有效的通过各种方式,将其规避了。然而,我想说的是,这样做很好,但是安全无具细,后面的这些你注意到了吗?Docker Daemon管理所有的容器,Docker Daemon就仿佛是SDN中的网络控制器,很多入口从它而来,Docker Daemon的配置安全了吗?

容器间的网络访问权限?

允许Docker修改iptables?

使用了AUFS存储驱动?

是否设置ulimit ? (2年前有人使用ulimit,声称自己解决了fork bomb,事情往往并无这么简单)

是否开启experiemental功能?

……

Docker 容器的安全

说到 Docker 容器的安全,那就不得不回到对“容器”的理解上。容器可以认为是:寄宿于操作系统的一组进程,为应用提供互相隔离的运行环境。既然是进程,我们往往会联想到进程的计算、存储、网络能力等。

说到进程的计算能力,如果对操作系统熟悉,我们很容易就想到CPU负责计算,而内核负责进程在CPU上运行的调度。进一步去了解Docker、内核与CPU三者之间的关系,我们可以发现Docker为容器支持了很多与CPU相关的内核参数,比如cpuset,cpu-period, cpu-quota 等众多参数,以满足容器的个性化需求。既然Docker通过内核来完成CPU调度,那么多个容器是否在存在内核竞争问题呢。

内核只有一个,要用内核的“容器”有多个,竞争存在吗?我们可以这么看,内核只有一个,要用内核的“进程”有多个,内核诞生至今,似乎从未有过进程竞争内核的言论。其实,内核本身的设计就有很大一部分在服务进程,两者的关系由来已久,而且臻入佳境。出人意料的是,“容器”也就是“进程组”的概念诞生了。当前专注“容器(进程组)”与以往专注“进程”存在很大的差异,其中就有一点是“容器(进程组)”是否会竞争内核?

内核同样可以认为是一种资源,传统我们认为它掌控CPU,内存,网络,存储等,而对于容器,它还负责管理内核范畴更多其他的资源,比如进程数、信号量、inotify(docker 1.8.0就曾经遭受到inotify资源竞争的安全问题)等。一旦对其没有严加防范,竞争现象很容易被利用,换言之,资源隔离难以为继。

Fork Bomb就是一个典型的例子。宿主机内核仅能分配指定数量的进程数,即PID上限比如为32768。倘若一个容器内的进程组消耗了宿主机上所有的进程,那么其他容器将不能被创建,甚至连宿主机都没有办法创建新进程,整个宿主机将陷入瘫痪状态。因此,容器世界中,一个小小的Fork Bomb,不断地fork新进程,将轻松炸毁一台宿主机。3年前,国内的容器市场中,我甚至认为Fork Bomb是容器公有云的头号敌人。好在Linux内核4.2开始支持Pid cgroup,可以实现限制容器的进程数上限。当然如果Linux内核版本并没有这么高的时候,想要解决这个安全隐患难度非常大,但也不是毫无办法。

内核也是资源,竞争需防范。不过,“好戏”才刚刚开始,我们再来聊聊Docker容器的存储。说到存储,存储之上大家自然想到的是文件系统(filesystem)。也许,大家听说过容器技术中有一个命名空间叫mount命名空间,可以实现文件系统隔离。然而,文件系统隔离,仅仅是一个非常基本的要求。上文中提到,不建议使用aufs做存储驱动,也与这存在一定关系。倘若使用aufs,创建出的容器虽然文件系统互相隔离,但是在存储空间方面却没有任何限制。换言之,一个容器如果不断写文件,将会写满存储介质,其他容器将无法执行写操作。

文件系统隔离不够充分,如果大家认同的话,也许会转向devicemapper等。当前devicemapper存有内核问题等,虽然一定程度上加大运维成本,但是是否就解决了这个问题呢?答案再一次令人失望。Docker中的Volume概念,大大打破了mount namespace。既然命名空间被打破,存储卷空间使用量又回到以上问题,无法限制。其实文件系统只是操作系统用来管理存储的一种手段,回到设备层,文件系统对其的管理除了空间之外,很多情况下还会有inode,那么inode的隔离同样需要考虑。

关于存储,进一步深入,再和大家探讨一个问题,在宿主机上什么是一个容器的存储空间?尤其在公有云环境下,原本以为容器文件系统范畴内的存储属于容器存储,对Docker的架构深入实践之后,发现远没有这么简单。容器的hostname文件,resolv.conf文件,日志文件等都应该属于容器存储范畴,隔离的边界又一次被拉大,隐患存在的维度防御起来难度无疑又一次变大。

再谈Docker容器的网络,网络的可访问能力,网络的带宽控制,网络的控制流数据流分离都应该是值得规划的技术点,如若不予重视,网络有可能会成为最不省心的环节。

Docker 镜像安全

Docker镜像承载了用户应用,甚至行业直接将镜像作为交付件。那么,镜像的安全,优先级理应放在前列。那么,就我个人经验而言,会存在哪些镜像安全问题呢?镜像本身的安全,镜像传输的安全,首当其冲。

何为镜像本身的安全,我将其限定镜像签名,镜像内容两类。镜像签名安全,大家理解起来较为简单。镜像内容,在我看来,就会复杂很多,包括:

镜像基础内容。基础内容包括基础镜像,包含依赖库,操作系统发行版等,这些内容若存在漏洞,或被人植入病毒等,镜像将不会幸免。容器市场上有一些项目,专做镜像安全扫描工作,也是主要针对镜像内容,对比当前市面上的漏洞库,得出扫描结果。

镜像应用内容。应用内容包括用户业务代码相关的构建内容,有无漏洞,有无被人篡改,都将影响。

镜像内容性质。镜像内容存储大小,镜像内容文件个数,都会引发安全问题。比如镜像有可能因为文件个数太大,下载到本地时占用太多的inode,导致机器运行失常等。

关于镜像的传输安全,涉及的内容同样多。传输安全和镜像的组织形式,镜像仓库的安全都息息相关。我曾经在体验公有容器服务时,偶然中嗅探到了集群中的私有镜像仓库地址。令人诧异的是,镜像仓库registry没有开启认证方式,因此我在容器内部可以下拉、上传任意的镜像,同样,我有能力污染所有的镜像,给所有的镜像植入病毒,这又是一起registry使用安全案例。

Docker 安全总结

安全是一个路漫漫其修远兮的话题,笔者不可能通过一篇短文即诠释大部分,但是笔者的态度是:面对安全,绝不避谈。本文主要介绍容器技术(以Docker为例)的Docker Daemon安全,容器安全以及镜像安全的小部分,意欲抛砖引玉。后续Docker应用层的安全隐患,Docker与安全技术(如SELinux,Seccomp等)的结合依然需要用户重视。

个人认为,Docker的创新在于业务创新,严谨来讲并不属于技术创新。Docker虽然诞生即将满4周年,但是在技术普及史上,毋庸置疑它目前并不算得上步入平稳期。Docker的确带来了很多的优势,辩证来看,缺陷同样明显,不同的场景,优劣权衡的比重并不完全一致。随着技术的发展,劣势终将逐渐被重视并解决,只是解决的方式不尽相同。

回到安全,Docker与Linux的结合目前在众多场景下,都逐渐表现出一些疲态。在这里,我也一直坚信一个观点:操作系统市场依然有改进的空间,当前的形势,并不是容器技术与操作系统的最佳结合姿势。