在Docker容器中实现安全与隔离

Saturday, November 7, 2015

在云计算中,多租户技术被认为是一个非常重要的功能。如果我们将容器中运行的应用看作一个租户,那么优秀的安全隔离技术设计可以确保租户只能使用它们可用的资源。

docker.jpg

随着容器技术的发展,它的安全、隔离和资源控制的功能也在不断进步。本文中,我们将回顾Dockers容器如何仅仅使用linux的原始功能来实现安全与隔离,比如namespaces, cgroups, capabilities等。

虚拟化和隔离

操作系统级的虚拟化,容器,空间,以及“chroot with steroids”,其实都定义了同一个概念:用户空间隔离。类似Dockers的产品都使用了操作系统级的虚拟化,通过用户空间隔离可以提供额外的安全性。

0.9版本起,Dockers包含了libcontainer库作为它直接虚拟化的方法,这个功能由Linux内核提供。 此外,它还通过 LXC[1],systemd-nspawn[2],和libvert[3]使用了抽象虚拟接口。
这些虚拟化库全部利用了Linux的原始容器(参见上图)

  • namespace
  • cgroup
  • capabilities

等。

Dockers在一个包装中联合了以上功能,并称之为容器格式。

Libcontainer

默认的容器格式被称为libcontainer。

Dockers也支持使用LXC的传统Linux容器。在将来,Dockers可能会支持其他的容器格式,比如结合BSD jails或者Solaris Zones。

执行驱动程序是一种特殊容器格式的实现,用来运行docker容器。在最新的版本中,libcontainer有以下特性:

  • 是运行docker容器的默认执行驱动程序。
  • 和LXC同时装载。
  • 使用没有任何其他依赖关系的Go语言设计的库,来直接访问内核容器的API。
    • 目前的Docker涵盖的功能有:命名空间使用,cgroups管理,capabilities权限集,进程运行的环境变量配置以及网络接口防火墙设置——所有功能是固定可预测的,不依赖LXC或者其它任何用户区软件包。
    • 只需提供一个根文件系统,和libcontainer对容器的操作配置,它会帮你完成剩下的事情。
    • 支持新建容器或者添加到现有的容器。
    • 事实上,对libcontainer最迫切的需求是稳定,开发团队也将其设为了默认。
      • 在Dockers 0.9中,LXC现在可以选择关闭。
        • 注意:LXC在将来会继续被支持。
      • 如果想要重新使用LXC驱动,只需输入指令docker -d –e lxc,然后重启Docker。

用户命名空间(namespace)

Docker不是虚拟化,相反的,它是一个支持命名空间抽象的内核,提供了独立工作空间(或容器)。当你运行一个容器的时候,Docker为容器新建了一系列的namespace。

一些Dockers使用的linux命名空间:

  • pid namespace
    • 用作区分进程(PID: Process ID)。
    • 容器中运行的进程就如同在普通的Linux系统运行一样,尽管它们和其他进程共享一个底层内核。
  • net namespace
    • 用作管理网络接口。
    • DNAT允许你单独配置主机中每个用户的的网络,并且有一个方便的接口传输它们之间的数据。
    • 当然,你也可以通过使用网桥用物理接口替换它。
  • ipc namespace
    • 用作管理对IPC (IPC: InterProcess Communication)资源的访问。
  • mnt namespace
    • 用作管理mount-points (MNT: Mount)。
  • uts namespace
    • 用作区分内核和版本标识符(UTS: Unix Timesharing System)。

cgroups (or Control Groups)

Linux上的Docker使用了被称为cgroups的技术。因为每个虚拟机都是一个进程,所有普通Linux的资源管理应用可以被应用到虚拟机。此外,资源分配和调度只有一个等级,因为一个容器化的Linux系统只有一个内核并且这个内核对容器完全可见。

总之,cgroup可以让Docker:

  • 实现组进程并且管理它们的资源总消耗。
  • 分享可用的硬件资源到容器。
  • 限制容器的内存和CPU使用。
    • 可以通过更改相应的cgroup来调整容器的大小。
    • 通过检查Linux中的/sys/fs/cgroup对照组来获取容器中的资源使用信息。
  • 提供了一种可靠的结束容器内所有进程的方法。
    ##Capabilities
    Linux使用的是“POSIX capabilities”。这些权限是所有强大的root权限分割而成的一系列权限。在Linux manpages上可以找到所有可用权限的清单。Docker丢弃了除了所需权限外的所有权限,使用了白名单而不是黑名单。

一般服务器(裸机或者虚拟机)需要以root权限运行一系列进程。包括:

  • SSH
  • cron
  • syslogd
  • 硬件管理工具 (比如负载模块)
  • 网络配置工具 (比如处理DHCP, WPA, or VPNs)

等。

每个容器都是不同的,因为几乎所有这些任务都由围绕容器的基础设施进行处理。默认的,Docker启用一个严格限制权限的容器。大多数案例中,容器不需要真正的root权限。举个例子,进程(比如说网络服务)只需要绑定一个小于1024的端口而不需要root权限:他们可以被授予CAP_NET_BIND_SERVICE来代替。因此,容器可以被降权运行:意味着容器中的root权限比真正的root权限拥有更少的特权。
Capabilities只是现代Linux内核提供的众多安全功能中的一个。为了加固一个Docker主机,你可以使用现有知名的系统:

如果你的发行版本附带了Docker容器的安全模块,你现在就可以使用它们。比如,装载了AppArmor模板的Docker和Red Hat自带SELinux策略的Docker。

图片版权

Docker Blog

引用

  1. LXC—Linuxcontainers.
  2. Control Centre:ThesystemdLinuxinitsystem
  3. The virtualization API:libvirt
  4. Solomon Hykes and others.What is Docker?
  5. How is Docker different from a normal virtual machine?(Stackoverflow)
  6. Docker 0.9:introducing execution driver sand libcontainer
    • UseslayeredfilesystemsAuFS.
  7. Is there a formula for calculating the overhead of a Docker container?
  8. An Updated Performance Comparisonof VirtualMachine sand Linux Containers
  9. capabilities(7)-Linuxmanpage
  10. Netlink (Wikipedia)
  11. The lost packages of docker
  12. ebtables/iptables interaction on a Linux-based bridge
  13. Comparsion Between AppArmor and Selinux
  14. The docker-proxy (netfilter)
  15. Hardware isolation
  16. Understand the architecture (docker)
  17. Linux kernel capabilities FAQ
  18. Docker: Differences between Container and Full VM(Xml and More)
  19. Docker vs VMs
    • There is one key metric where Docker Containers are weaker than Virtua lMachines, and that’s “Isolation”. Intel’s VT-d and VT-x technologies have provided Virtual Machines with ring-1 hardware isolation of which, it takes full advantage. It helps Virtual Machines from breaking down and interfering with each other.
  20. Docker Security

转载请注明出处和原文出处

需要译稿请联系QQ:545870054
或者邮件:fan_xq@live.com

用户命名空间已经来到Docker!

by estesp · October 13, 2015

好的,在如何实现和展示给用户的问题上,经过一路的拖延和很多细节的讨论,我很高兴的宣布:Docker容器用户命名空间的最初版本已经可用了!正如下面这条来自Arnaud的Tweet中所言,在Docker 1.9 发布时间表中,启用用户命名空间的功能已在试验版本可用,我们已经更加了解这个功能的边界和它是如何与Docker生态系统中的其他功能如何协作的。

Phase 1 @Docker support for user namespaces is now in experimental branch! Congratulations @estesp! 👏 https://t.co/a25LizygRV

—Arnaud Porterie (@icecrime) October 10, 2015

当然,兴奋之余,你的第一个问题肯定是,“用户命名空间到底是什么呢?”。用户命名空间是Linux内核中的一个可用的容器命名空间(最年轻的那个),类似于用来创建容器的关键命名空间——mount, uts, pid, and network。如果命名空间对你而言是一个全新的话题,那么Michael Crosby的文章 “Creating Containers”值得一读。如果想要了解Linux中用户命名空间的更多详尽细节,可以在LWN.net上Michael Kerrisk整理的“Namespaces in Operation, part 5”中找到。一旦你对命名空间有所了解,深入学习了一些用户命名空间的知识,你就会明白,用户命名空间最重要的功能之一就是可以让容器拥有看起来和宿主系统中不同的uidgid。特别的,进程(在我们的例子中,是容器中的进程)可以作为宿主系统中的一组映像被提供。当这个进程以为它是以uid 0(一般被认为是“root”)运行的时候,它在宿主系统中实际上可能是uid 1000,或者是uid 10000,甚至是uid 34934322。这完全由我们在用户命名空间中创建这个进程的时候提供的mappings决定。从安全方面来说,这是一个伟大的功能,因为这允许进程在容器中以root权限运行,但容器在宿主系统中却没有真正拥有root权限。

在Docker的实例中,我们已经提供了一个新的后台程序启动标志,来允许管理员提供一个用户名,或者一个可选的用户组(或者用户和组的数字ID),作为remapping user。Linux命名空间功能的创建者已经考虑到,通过提供名为下级范围的用户和组的数字ID,来区分使用/etx/subuid/etc/subgid文件的用户,Docker可以接着在用户下属范围内寻找 remapping user的范围。这个范围被用来创建程序创建过程中的映像,无论这些程序是通过docker run还是docker exex启动的。有关Docker是如何使用这些范围的更多完整文档,可以在用户命名空间试验文档中可以找到。

当然,用户(尤其是容器云操作员)下一步想要在每个容器而不是每个后台程序中指定某个映像。对公有云操作员来说,这将允许每个新增的租户(tenant)安全的拥有自己的uidgid范围,并且不会与其它租户的范围重叠。当然,我们提供了这个功能。然而,这里有一个关键功能的缺失,那就是在Linux系统中挂载文件系统同时“平移转换”每个文件系统的所有属性条目。如果没有这个功能,Docker就必须“不共享”现在通过各种写拷贝后台已经共享的文件系统中的内容。这意味着Docker中原本提供的磁盘空间节省功能的失效:容器间共享的文件系统层,比如ubuntu:14.04镜像的层。由于没有mount-with-uid-gid、shift-like功能,Docker不得不拷贝每个根文件系统,手动平移转换修改整个文件树的属权,来获取指定的容器在文件系统中运行所需的镜像的权限。在Linux社区已经有人在尝试从文件系统层上来实现,但这遥遥无期,因此,用户命名空间的“第二阶段”很难预测能否被正确执行。

已经开始使用用户命名空间的人们,请提供关于这个实验性功能的反馈,来让我们继续完善出一个稳定的版本。如果这一切听起来很有趣,但你不知道如何开始,那就阅读用户命名空间的文档,然后从https://experimental.docker.com/获取一个试验构建来尝试吧!

重要笔记:由于将映像提供的Docker的层数据在本地缓存中的内容,一旦使用了带有用户命名空间的实验版本,镜像根目录(默认是/var/lib/docker)就会多出一个间接附加的重映像的根uid和gid。例如,如果提供给--userns-remap标志的remapping user,有从10000开始的用户和组的ID,那么所有运行映像设置的镜像和容器的根目录将存放在/var/lib/docker/10000.10000中。如果你使用不提供用户命名空间重映射的实验版本,
如果你使用的是没有用户命名空间的实验版本,现有内容将会被转移到/var/lib/docker/0.0来与重映像的内容相区分。如果需要返回到没有用户命名空间的Docker版本,最简单的方法是终止现有的试验后台进程,将/var/lib/docker/0.0中的内容移回到/var/lib/docker中,最后重启没有用户命名空间的Docker二进制程序文件。

期待你们的来信,等待用户命名空间已久的用户们!

转载请注明出处和原文出处

需要译稿请联系QQ:545870054
或者邮件:fan_xq@live.com

Java序列化漏洞威胁着上百万的应用

https://dzone.com/articles/java-serialization-vulnerability-threatens-million

一个在Java环境中广泛传播的漏洞使得数千的商务信息被暴露。尽管这个漏洞没有一个酷炫的名字——比如心血(Heartbleed)、破壳(Shellshock)或者POODLE——它却被指出能让黑客通过互联网进行攻击。然而,目前没有一种方法能保护所有的应用。这意味着需要团队们花费很长一段时间来寻找和修复这个漏洞的变体。

Contrast安全公司(以下简称Contrast)开发了一种使用我们的专利,强大的应用安全构建平台,快速准确的修复了这个问题。Contrast可以使用我们的IAST方法来分辨出这个问题。它也可以通过使用我们的RASP功能来立即修复这个问题或者生成安全警报,并且不必重编码。在服务器上安装一个Contrast客户端就可以标记出这个服务器上所有存在问题的Jaca应用程序。

如果你想了解更多关于Jacaranda序列化漏洞的细节,请继续往下读。序列化是一种让开发者将数据结构转化成一串比特流来存储或者发送的方法。反序列化则是一个相反的过程,将接收后的数据转换为特定数据结构。早在2010年之前,序列化就已经开始出现安全问题。完整安全问题历史记录详见http://www.ibm.com/developerworks/library/se-lookahead 。最近,在一系列不同的Java环境中都发现了漏洞——遗憾的是,这可能是唯一一种能让安全问题得到重视的方式。

这些漏洞非常严重,可以被利用来实现执行整个远程命令——任何程序的主机将被接管,只要这个程序接受了序列化对象。

在Java中,读取一个序列化流中的SetBit是非常简单的:

ObjectInputStream in = new ObjectInputStream( inputStream );     
return (Data)in.readObject();

但问题在于,你不知道在解码之前你究竟反序列化了什么。所以一个攻击者可以序列化一串恶意对象,并把它们发送到你的程序。一旦你调用readObject(),一切就都已经迟了。有时候,它就像XXE漏洞,攻击者可以使用恶意文件格式,在XML解析的时候发起攻击。不过,在我们的例子中,没有一个简单的方法来终止解析过程。

现在所需的是一种可以运行序列化,但不会让攻击者有机会创建任意类的实例。比如:

List<Class<?>> safeClasses = Arrays.asList( BitSet.class, ArrayList.class );
Data d2 = safeReadObject( Data.class, safeClasses, new FileInputStream( f ) );

这允许开发者指定返回类型和希望在序列化对象中出现的类清单,一旦出现没有被授权的,我们将抛出一个安全异常并阻止请求。结果表明实现这个并不难。我们只需要重载一下ObjectInputStream的执行就可以了。

以下是是一种替代调用readObject的方法:

@SuppressWarnings("unchecked")
    public static <T> T safeReadObject(Class<?> type, List<Class<?>> safeClasses, InputStream in ) throws IOException, ClassNotFoundException {
        return (T) new ObjectInputStream(in) {
            protected Class<?> resolveClass(ObjectStreamClass d) throws IOException, ClassNotFoundException {
                Class<?> clazz = super.resolveClass(d);
                if (clazz.isArray()
                    || clazz.isPrimitive()
                    || clazz.equals(type)
                    || clazz.equals(String.class)
                    || Number.class.isAssignableFrom(clazz)
                    || safeClasses.contains(clazz)) return clazz;
                throw new SecurityException("Attempt to deserialize unauthorized " + clazz);
            }
        }.readObject();
    }

这个方法重载了ObjectInputStream中的resolveClass(),新增了一些检查,来确保被读取为反序列化进程部分的任何类,都是不可利用或者处于安全类的白名单中。作为一个替代项,你可以将不熟悉的类加入黑名单——那些会被用来开发的——但这注定失败。因为有太多被称为“小工具”的东西能够高效的列出所有可能被利用的漏洞。

这个简单的检查保护了你的程序,并且不用从底层修改代码。第一步要做的是搜索你的代码,找到所有易受攻击的地方——第二天,所有Contrast的用户就会收到一个更新来完成上面的事情。一旦发现程序包中存在由于反序列化不受信任数据而暴露的地方,你就会收到通知。

本文已被发布在程序员资料库

并被推荐到极客头条

转载请注明出处和原文出处

需要译稿请联系QQ:545870054
或者邮件:fan_xq@live.com