我在Opensource.com上写这些有关Docker安全的东西,想阐述的就一点——“纸已经包不住火了(containers do not contain)”
Red Hat和Docker其中一个主要的目标,就是确保这一陈述不是绝对正确的。我在Red Hat的小组仍在努力利用别的安全机制使container更加安全。这些少数的几个安全特色,我们目前正在攻克,他们未来可能成为影响Docker和container的方式。
用户namespace用户namespace基于内核namespace,可以更好地将主机和容器分离起来。
基本方法就是宿主机创建一系列的UID,例如60000-61000,然后映射到内部的namespace 0-1000,也可以用GID实现。内核会把docker容器内的UID 0 识别、鉴定为外部的UID 60000,任何一个没有被映射过的进程或者UID在容器内部都会被识别为UID=-1,从而禁止他们访问容器,包括所有的镜像都不允许访问。如果你想使用有用户namespace认证的镜像,那么你就得切换到容器内部root用户的UID对应的外部ID。另一个问题就是外部UID 0挂载进入容器的卷、硬盘等设备在容器内部是没有权限访问的。你必须用`chown`命令将UID切换成内部可以的UID。
chown -R 60000:60000 /var/lib.content
用户namespace的另一个问题在于,你不许给不同的容器分配不同段的UID进行映射。如果你有成百上千个容器,那么如何宿主机分配映射的UID就是个问题。
用户namespace是一个很炫酷的事情,他们可以直接利用namespace的优势,如果把容器进行如上的操作,那么我们可以抛弃掉所有的宿主系统提供的功能。也就是说,当用户namespace生效后,我们可以完全不再需要宿主系统提供的功能。我们也不再需要selinux提供的安全标签的策略了。
使用案例三个可以用到用户namespace的方案如下:
1.只映射UID=0。增加容器之间的隔离度,甚至可以完全抛弃capabilities机制。这样做无疑可以增强容器和宿主机之间的安全性,但是也不会相对地提升容器之间的隔离度。所以只需要假设所有DOCKER容器的ROOT的外部UID为2,那么,外部的UID=2映射到容器内部UID=0,外部GID=2映射到内部GID=0,凡是大于2的全部映射到自己。这样能最大程度的减少从容器内部获取宿主机的root权限。当然这样也能减少挂载文件系统时遇到的麻烦。这样处理之后,内部root用户mount的磁盘,外部是无法读取,(也就不会有SUID这个问题)。同样,其余的关于用户的namespace的处理也是一样。
2.openshift式的解决办法。每个容器都有自己单独的UID/GID,如同每个用户都有自己的UID和GID一样。只有容器用得到内核capabilities时这样才有必要,用不到的时候意义不大。
3.按照段来映射。每个容器都映射一个UID段,每个容器映射的UID段各不相同。但是复杂性会随着容器数目增加急剧增加。挂载文件系统可能会成为一个大问题。可以增加一个功能,当容器内部mount的时候便执行对应的chown UID:GID /SRC命令,这样一定程度上可以解决挂载的问题。
然而我不认为这三个解决方案可以叠加。我也看过对内核“加入容器时,重设mount目录的UID的提议”,甚至对乐死mount --bind的命令也执行重设UID,但是我觉得这个提议交给那些写内核的人比较好,我也会继续参考那些做安全人的意见。
用户namespace已经并入libcontainer了,也已经打好了能这样让docker运行起来的补丁。
Seccomp有个问题,那就是这里和别的地方提到的容器隔离措施全都是依赖与内河。空气和电脑接触,但是空气无法和电脑内核交流。但是容器就不一样,所有的容器都是直接和内核进行交换信息。如果宿主机有个漏洞,那么这个漏洞就击垮所有的安全措施,docker容器也会变得无法控制。
X86_64的linux内核有超过600个系统调用。只要其中其中有一个有漏洞,那么都可以导致容器的权限提升。因此有些系统调用是基本用不到的,所以应该禁止使用这些系统调用。禁止的越多越安全。
Seccomp是google开发的禁止系统调用的沙盒类型的程序。例如Chrome的插件就得管管,毕竟插件都是来自互联网,没办法保证100%的安全。Google在Chrome浏览器中使用Seccomp执行chrome插件确保系统的安全。
我的同事Paul Moore,决定将Seccomp简化从而构建一个C库来管理系统调用库。现在他的成果Libseccomp也在qemu,lxc,和systemd等软件中开始使用了。
我们也开始用go封装这个库然后再libcontainer中调用,进行过滤系统调用。