如何加固你的微服务容器——Part 1


【编者的话】本篇文章简要的介绍了一些容器安全方面的最佳实践,尽管这些安全措施不会对开发和测试环境产生重大影响,但对于生产环境来说,这是必不可少的,好的习惯难以养成,现在就进行改变吧!

【烧脑式Kubernetes实战训练营】本次培训理论结合实践,主要包括:Kubernetes架构和资源调度原理、Kubernetes DNS与服务发现、基于Kubernetes和Jenkins的持续部署方案 、Kubernetes网络部署实践、监控、日志、Kubernetes与云原生应用、在CentOS中部署Kubernetes集群、Kubernetes中的容器设计模式、开发Kubernetes原生应用步骤介绍等。

上周,“Using Docker”的掌舵人和作者,Adrian Mouat,举办了一个在线研讨会,主题是如何加固Docker微服务容器。Andrian和Sam Newman(“Building Microservices”的作者)将会举办一个为期2天的培训,于6月31号在阿姆斯特丹,8月30号在伦敦分别举办,而这个在线研讨会是这个培训的一个小广告。

下面我们进入正题。Adrian 谈论了太多内容,以致于我不得不将内容分为两部分。第一部分,我将会讨论如何编写安全的Dockerfile的基本内容。第二部分讨论如何安全地部署。

正确的安全是怎样的呢?

Adrian说,对用户来说,实施了正确的安全措施几乎是透明的。一个安全的网站和不安全的网站唯一的区别就是安全事故不会发生,比如说,安全网站不会沦陷,不会丢失用户的敏感数据。

我们明白你不可能做到百分之百的安全。如果你的老婆窃取了你的AWS证书,这谁能够想到呢?但是,就像Sam Newman在研讨会上说的,大多数人不需要百分百的安全。我们只需要提升自己的安全指数,使得攻克我们需要让黑客们付出很高的成本,这样他们自然而然就会放弃我们,扭头走开了。

那么差劲的安全是怎样的呢?

尽管很难去描述好的安全,但要描述差劲的安全是非常简单的。Adrian给我们演示了一个不安全的Dockerfile。具体来说,有4件事情我们经常能够在Dockerfile中看到,这些都是不安全的:
  • 没有版本数字
  • 没有新建用户(容器运行在root用户下)
  • 没有对下载进行验证
  • 没有元数据


我会逐个描述以上的行为,但我们需要认识到,目前存在很多非常棒的Docker安全特性,我们需要使用它们。不幸的是,它们并不是默认的配置。

也许你会偷偷想——也许实际上这些默认的不安全配置是个好事情呢?当存在容易被攻击的容器时,黑客们就不会来攻击我们这些更安全的容器了。当然,这是有一定道理的,只要你保证你一定不会基于这些易被攻击的容器构建新容器就好了。。。

1. 设置版本数字

“Latest”不是你的好朋友。

当你在Dockerfile中指定任何基础镜像时(FROM <image> [:<tag>] [AS <name>]),我们应该提供一个带有具体版本数字的tag(比如FROM alpine:3.4)。

这么做有两个理由:
  • 可重现。一般来说,我们希望我们的容器再每次运行时都是一样的。如果我们使用了“latest”,那么哪个版本的镜像会被下载并执行将不受我们的控制。
  • 可追踪。当我们在诊断问题时(尤其是安全问题),我们希望找到我们执行的代码是从何而来的。如果基础镜像使用了“latest” tag,想要追踪到具体的版本和源代码是非常困难的。


当然,Adrian指出,指定一个具体的版本号不是那么简单的。我们该如何决定呢?

当使用语义版本号(semantic versioning)时,我们可以为一个版本号定义3个级别:MAJOR.MINOR.PATCH。如果你指定了这三个级别的数字,那么你已经获得了很好的可重新性和可追踪性,但不能够自动获得最新的安全补丁。如果你只指定了MAJOR的数字,那么你的容器就可能由于基础镜像的变动而被破坏。(MINOR版本的变动可能会对基础代码造成较大的改动)。Adrian认为一个比较适当的解决方案是使用MAJOR.MINOR来指定版本号,在这种情况下,基础镜像的内容的变动不会对我们的容器造成破坏,我们只需要去关心那些bug的修复和安全补丁的修改就好了。

我在Google上找到的Dockerfile的案例都是没有tag信息或者使用了latest作为tag。因此,我能够推断,目前存在着非常多的不安全的容器。这对那些运行在生产环境的镜像来说是更加重要的。Latest也许对开发和测试环境来说没有那么严重,但坏的习惯是很那被改变的,我们应该尽早培养这些好习惯。

我们已经说过,可重现性非常好,但是在大多数情况下,这是几乎不可得的(当然,Google做到了,但拜托,他们几乎不是人,让我们忽略他们吧)。原因在于你不能够保证你的依赖不会改变。如果你极度追求可重现性,那么你必须维护一个镜像,你也应该去看下Google的新工具Bazel。

2. 设置一个用户

如果你不在Dockerfile中指定一个用户,那么你的容器将会运行在默认用户(root)下。这是不好的。这意味着,如果某些坏蛋控制了容器中的进程,那么他们将会拥有整个容器的root权限,更进一步,如果他们突破了容器,那么他们同样会拥有宿主机的root权限(docker用户没有使用命名空间,容器中的root用户就是宿主机上的root用户)。这是非常不好的,尤其是我们能够花费少量的成本就能修复它。

你可以通过以下指令来指定用户:
USER <user>[:<group>] or USER <UID>[:<GID>] 

仅仅设置一个比root权限更低的用户,你就能够获得成倍的安全提升。

当然,这同样也不是那么简单的。在启动容器时,你可能需要root权限,启动后就不需要了。在这种情况下,目前存在多个方法能够启动时为root用户,之后将用户权限降低。比如,你可以在Dockerfile中创建用户,但在entry point或者cmd脚本中切换到这个用户。具体可以看github上的代码

3. 验证下载

如果我们足够小心,我们能够确保我们下载的每一个镜像都是我们想要的那个,并且没有被修改过的。比如说,它们没有被攻击过或者被替换过。我们可以使用hash或者摘要来完成这件事:
FROM debian@sha256:bla...

这意味着Dockerfile总是继承自同一个基础镜像。好处是,你能够保证,每次都能够生成相同镜像。并且你能够确定基础镜像不会改动,因此也就不会破坏上层代码。当然,版本的具体化带来的坏处就是,你不能够获得任何的更新,包括重要的安全更新。

4. 使用元数据

Dockerfile对元数据(使用LABEL命令设置)有非常好的支持。你可以像git一样为你的镜像添加元数据,任何人调试容器时都能够准确定位源代码。

Dockerfile的LABEL命令能够很好的提升容器的可追踪性。它非常容易使用。具体案例可以在这里查看

后续

我们已经覆盖了Dockerfile安全的基础内容。在下一次的博客中,我们将会学习如何安全地部署Docker微服务容器。

如果你对容器和安全感兴趣,下面是一些有用的资源:


原文链接:Securing Microservices with Docker from Adrian Mouat – Part 1(翻译:杨润青)

===========================
译者介绍
杨润青,90后博士僧,研究方向是网络和信息安全。

0 个评论

要回复文章请先登录注册