BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

关于Dockerfile安全加固的那些事

| 作者 Ross Fairbanks 关注 0 他的粉丝 ,译者 薛命灯 关注 23 他的粉丝 发布于 2017年2月3日. 估计阅读时间: 9 分钟 | Google、Facebook、Pinterest、阿里、腾讯 等顶尖技术团队的上百个可供参考的架构实例!

来自MicroBadger的Ross Fairbanks从两个关于容器安全的视频里总结了几个针对dockerfile的安全调整,有助于构建更加安全的容器镜像。以下内容翻译自Ross的文章。

最近我观看了两个有关容器安全的演讲视频,其中一个视频的演讲者是来自Docker的Justin Cormack,另一个是来自Container Solutions的Adrian Mouat。我们已经遵循了很多安全方面的建议,不过仍然有改进的空间。所以,我们觉得是时候对我们的dockerfile来一次安全大调整。

官方镜像

我们是Alpine Linux镜像的忠实用户,因为相比于Debian或Ubuntu,它的体积更小,并且具有更小的可攻击表面。所以,我们使用官方的Alpine镜像作为其它镜像的基础。除此以外,官方镜像还有另一个好处——Docker有一个专门的团队在维护它,并一直遵循着最佳实践。

我们的主要开发语言是Go,不过有时候也会用Ruby来处理一些基于Web和脚本的任务。我们使用ruby:2.3-alpine作为这些镜像的基础。ruby:2.3-alpine的Ruby是从源代码安装的,并非来自Alpine包。通过语义化版本控制(Semantic Versioning),Ruby 2.3能够接收到来自Ruby核心团队的安全更新。同时,Docker团队会更新标签2.3-alpine。

如果不使用ruby:2.3-alpine,我们就要自己从源代码安装Ruby,并持续跟进Ruby新版本的发布情况,或者使用Alpine包,并持续检查Alpine包内是否有新的Ruby版本。

通知和Web钩子(Webhook)

从Ruby的例子可以看到,当底层的基础镜像接收到安全更新时,你的整个镜像也要跟着重新构建。这个时候,我们的MicroBadger通知系统可以帮我们完成一些事情。我们在Alpine和Ruby的官方镜像上使用了通知。我们会从Slack接收到我们所关心的公共镜像的变更通知。

当基础镜像发生变更时,我们还会利用通知来自动触发镜像的构建。Docker Hub也有这个功能,不过我们的通知可以用于任何支持Web钩子的系统,比如CI系统或安全扫描器。

非授权用户

容器和虚拟机之间的一个关键区别是容器与主机共享内核。在默认情况下,Docker容器运行在root用户下,这会导致泄露风险。因为如果容器遭到破坏,那么主机的root访问权限也会暴露。

使用非授权用户来运行容器可以降低这种风险。下面是一个有关Rails应用的例子:

# Create working directory.
WORKDIR /app
# Copy Rails app code into the image
COPY . ./
# Create non privileged user, set ownership and change user
RUN addgroup rails && adduser -D -G rails rails \
 && chown -R rails:rails /app
USER rails

安全扫描

在容器注册中心运行安全扫描可以为我们带来额外的价值。除了存放镜像,镜像注册中心定期运行安全扫描可以帮助我们找出薄弱点。Docker为官方镜像和托管在Docker Cloud的私有镜像提供了安全扫描

来自CoreOS的Clair也很不错,它是开源的,并被用于Quay.io的镜像注册中心安全扫描。Clair将支持Alpine,这是一个好消息,希望可以尽快在Quay上面看到它。还有其它一些扫描器,比如TwistLockAqua,它们都是付费产品。

经过Docker的安全扫描,我们可以得到一个干净的Go镜像。我们把一个二进制版本拷贝到镜像里,然后加入一个CA认证依赖,这样我们就可以建立HTTPS连接。我们的Rails应用会有更多的依赖,因为Ruby是解释型语言。我们需要为我们的应用安装所有的Ruby gem,同时需要为这些gem安装所有的操作系统依赖。

安全扫描会把扫描结果保存到libxml2和libxslt文件里。Nokogiri是XML和JSON解析器,在构建镜像时,它会依赖前面提到的两种文件。为了得到更好的性能,Nokogiri使用了编译过的C语言扩展,不过一旦安装完毕,就不再需要libxml2和libxslt文件了。

下面我们来移除构建依赖,这些命令有点复杂:

# Cache installing gems
WORKDIR /tmp
ADD Gemfile* /tmp/
# Update and install all of the required packages.
# At the end, remove build packages and apk cache
RUN apk update && apk upgrade && \
 apk add --no-cache $RUBY_PACKAGES && \
 apk add --no-cache --virtual build-deps $BUILD_PACKAGES && \
 bundle install --jobs 20 --retry 5 && \
 apk del build-deps

Gemfile包含了需要安装的gem,同时Gemfile.lock用来管理依赖关系链。通过把这些文件缓存在/tmp目录,只有当Gemfile发生变更时,bundle install命令才会被执行。如果Gemfile没有发生变更,它会使用缓存里的文件。这个缓存很有用,因为安装gem是一个很耗时的任务,还会占用很多带宽。

run命令跨了很多行,只有一个层会被添加到镜像里,这个层包含了apk和gem包。构建包是作为虚拟包被添加进去的,在安装完毕之后它们可以很容易被移除。

更新:编译过的二进制仍然可能是不安全的

移除构建依赖会为我们带来一些好处,不过为了gem的扩展,镜像里仍然会存留一个二进制包。这个二进制包很难被扫描器发现,所以它或许仍然是一个会暴露薄弱点的地方。

所以,对这个二进制包进行安全检查是必要的。对于Nokogiri来说,我们使用了1.6.8版本,这个版本包含了最新的libxml和libxslt安全补丁。不过这些包的CVE元数据可能存在一个问题,我已经向Docker安全扫描团队反馈过这个问题。

自动构建

关于容器安全很关键的一点是,当镜像或基础镜像有安全方面的更新时,需要对镜像进行重新构建。镜像被关联到Git仓库,所以自动构建会让整个过程变得很容易。当仓库分支上有新的提交时,如果我们对其进行了跟踪,那么一个构建就会被触发。在之前的例子里我们已经看到,当基础镜像发生变更时也会自动触发构建。

我们的Ruby镜像自动构建会简单一些,因为自动构建可以直接使用本地的Dockerfile。不过我们的Go镜像就麻烦一些,因为在把二进制包加入镜像之前需要先通过编译,我们通过使用本地的makefile来完成编译。

我们可以使用钩子来进行自动构建,并使用Docker容器来编译二进制包。来自CenturyLinkLabsPrometheus的Go语言构建器镜像都是很不错的选择。

可以通过钩子来调用这些构建器镜像。我们也可以使用钩子往镜像里添加动态的元数据,就像我最近在博客里所说的那样。

我无法覆盖视频里所提到的所有主题,所以如果有可能,大家可以自己去看视频。最后我想说的是,我们在MicroBadger里添加了对私有仓库的支持,所以你们可以为Docker Hub上的私有仓库使用通知。

本文已获得原作者翻译授权,查看英文原文:Dockerfile security tuneup


感谢木环对本文的审校。

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT