Alan Hou的个人博客

精通Docker第三版 – 第十二章 Docker安全

《精通Docker第三版》完整目录:

第一章 Docker概览
第二章 创建容器镜像
第三章 存储和发布镜像
第四章 管理容器
第五章 Docker Compose
第六章 Windows容器
第七章 Docker Machine
第八章 Docker Swarm
第九章 Docker和Kubernetes
第十章 在公有云上运行Docker
第十一章 Portainer – 一个Docker的GUI
第十二章 Docker安全
第十三章 Docker工作流
第十四章 Docker进阶

本章中我们将来学习Docker安全的相关知识,这是当今每个人都最为关心的一个话题。我们会将本章划分为如下五个部分:

技术准备

本章中,我们将使用桌面Docker,并且我们将使用Docker Machine来在云端启动一个Docker主机。和此前章节一样,作者将使用个人偏好的操作系统macOS。同样,所使用的Docker命令在安装过 Docker 的三种操作系统均可使用。但是,部分支持命令可能仅能用于macOS和Linux操作系统。

观看如下视频来查看实时操作效果:

容器的考虑因素

首次发布Docker时,有大量关于Docker对比虚拟机的讨论。我还记得在杂志中阅读文章、Reddit上的讨论以及无休止的博客文章。早期的Docker alpha和beta版本,人们把Docker容器视作虚拟机,因为没有其实的东西可作参照了,我们把它视为微型虚拟机。

以前我会启动SSH,在容器中启动多个进程,甚至通过启动容器来创建容器镜像以及运行命令来安装我们的软件栈。这在第二章 创建容器镜像中已经讨论过,应当避免这么做,因为这是不良实践。

因为,我们不讨论容器和虚拟机的对比,来看一些运行容器而非虚拟机时一些考量因素。

优势

在启动Docker容器时,Docker引擎在后台做了大量的工作。Docker引擎所做的工作之一是在启动容器时,设置命名空间和控制组。什么意思呢?通过设置命名空间,Docker将每个容器的进程进行隔离,不仅是容器之间,与宿主机的系统同样隔离。控制组保障每个容器获取自己份额的资源,如CPU、内存和磁盘I/O。最重要的是,它们确保一个容器不会耗尽给定Docker主机上的所有资源。

在此前的章节中也看到了,在Docker控制的网络中启动容器表示你可以在应用级别隔离你的容器,所有应用A 的容器在网络层都无法访问应用 B 的容器。

此外,这种网络隔离可以通过使用默认的网络驱动器来在单个Docker主机上运行,或者可以使用Docker Swarm内置的多主机网络驱动器或Weave的Weave Net驱动器来横跨多个Docker主机。

最后,我所认为Docker相对典型的虚拟机的最大优势是你无需登录容器。Docker尽最大的努力来保持你无需登录容器即可管理它所运行的进程。通过docker container exec, docker container top, docker container logs和docker container stats等命令,你可以做所需做的所有事情,而不暴露不必使用的更多服务。

Docker主机

在处理虚拟机时,你可以控制谁使用哪台虚拟机。假设你只希望开发人员User 1使用开发虚拟机。但User 2是运维人员,它负责开发和生产环境,因此他需要能进入所有虚拟机。大部分虚拟机管理工具都允许你对虚拟机按角色进行赋权。

使用Docker时就存在一点劣势了,因为能够使用Docker主机的Docker引擎的任何人,不论是通过sudo赋权或是将用户加入到Docker Linux组中,都可以进入你运行的所有Docker容器。他们可以运行新容器,停止已有容器,他们也可以删除镜像。一定要当心主机上的Docker引擎访问权限赋给谁。他们掌管着通过你的容器王国的大门钥匙。了解了这个,推荐将Docker主机仅用于Docker,将其它服务与Docker主机进行分离。

镜像可信度

如果你运行的是虚拟机,极有可能会从头开始进行配置。因为下载文件在大小(以及启动所花费的精力),你不太可能下载一个随便什么人在网上创建的预置机器镜像。通常会使用来自可信的软件提供者的预建虚拟应用。

因此,你会清楚虚拟机内部有什么和没有什么,因为你负责构建和维护它。

Docker的一个优势是易于使用,但这种易于使用也容易让人忽略一个相当重要安全的考量:你是否知道容器内运行的是什么?

我们已经在前面章节中涉及到了镜像可信度的问题。比如,我们说到过不要发布或下载没有使用Dockerfile定义的镜像,不要把自定义代码呀密钥等等嵌入到会发布到Docker Hub上镜像中。

虽然容器有命名空间、控制组和网络隔离的保护,我们讨论了随意下载的镜像可能会为你的环境带有安全问题和风险。比如,一个完全没问题的容器运行着一个未打补丁的软件会为应用和数据的可用性带来风险。

Docker 命令

我们来看能帮助强化安全以及查看你可能使用的镜像信息的一些Docker命令。

我们会集中在两个命令上。第一个是docker container run命令,通过这一命令可以了解一些对你有优势的项目。第二个我们会来看docker container diff命令,我们可以来了解计划使用的镜像发生了什么。

run命令

就docker run而言,我们将主要集中在允许你以只读的方式在容器中进行所有设置的选择,而不是具体到目录或数据卷。这有助于限制恶意软件所导致的损失程序,这种软件通过更新它的二进行文件可能会劫持一个欠保护的应用。

我们来看如何启动一个只读容器,然后再分解其中进行的操作,如下:

这里,我们运行了一个MySQL容器并将整个容器除以下文件夹外置为只读:

这些会以三个独立的数据卷进行创建,然后以可读/可写进行挂载。如果你不添加这些数据卷,MySQL将无法启动,因为它需要读写权限来完成这些操作:在/var/run/mysqld中创建套接字文件、 在/tmp中创建一些临时文件以及在/var/lib/mysql中创建数据库自身文件。

容器中的其它位置都不允许进行写入。如果你运行如下命令,会执行失败:

以上命令会得到如下消息:

这在你需要控制容器内何处可写(何处不可写)非常的有用。进行完善的测试,因为应用不能在某些位置写入可能会产生问题。

和前面的命令类似,使用docker container run我们设置了所有内容只读(除指定数据卷外),我们可进行相反的操作来设置单个数据卷(如果你使用-v 参数可以设置更多的数据卷)为只读。有关数据卷需要记住的一件事是,在使用数据卷并将其挂载到容器中时,它会在容器内部目录之上挂载一个空的数据卷,除非你使用–volumes-from参数或以其它方式在启动之后将数据加入到容器中:

这将从Docker宿主机挂载/local/path/to/html/到/var/www/html/并将其设为只读。如果你不希望运行的容器写入到数据卷,并保持数据数据文件和配置文件的完整性,这会非常有用。

diff命令

我们再来看看docker diff命令,因为它与容器的安全方面有关,你可能会希望使用Docker Hub或其它相关仓库上托管的镜像。

记住有权限访问Docker主机和Docker守护进程的人都有权限访问所有运行中的Docker容器。所以,如果你没有设置监控,有人就可以对你的容器执行命令并进行恶意操作。

我们来看上一部分中所启动的MySQL容器:

你会看到没有任何文件返回。什么原因呢?

diff命令告诉我们在容器启动后镜像所发生的变化。在上一部分中,我们以镜像只读模式启动了MySQL容器,然后对已知MySQL会需要读取和写入的目录挂载了数据卷,也就是说我们所下载的镜像与容器中运行的那个没有任何文件上的变化。

运行如下命令停止并删除该MySQL容器,然后清理数据卷:

然后,再次启动容器,去除掉只读标记和数据卷,这次结果会有所不同,如下:

可以看到创建了两个目录并添加了一些文件:

这对于定位容器中所进行的异常或非预期操作是一种很好的方式。

最佳实践

这一部分中,我们来看Docker的最佳实践,以及互联网安全中心指南,来恰如其分地保障你的Docker环境各方面的安全。

Docker最佳实践

在进入到互联网安全中心指南之前,我们来过一遍使用Docker的一些最佳实践,如下:

互联网安全中心基准

互联网安全中心(CIS)是一个独立的非营利组织,目标是提供安全的上网体验。它们发布基准和管控,被认为是 IT 各方面的最佳实践。

Docker的CIS基准可以免费下载。你会看到当前它是一份230页的 PDF,通过知识共享证书发布,涵盖Docker CE 17.06及之后的版本。

你会在实际运行扫描(本章的下一部分)时参照这一指南,并获取结果来确定什么需要(或应该)修复。指南划分为如下部分:

主机配置

指南的这一部分有关Docker主机的配置。这是你所有容器运行的Docker环境部分。因此,保持它的安全至关重要。这是对抗攻击人员的第一道防线。

Docker守护进程配置

指南的这一部分有保障运行中Docker守护进程安全的建议。你对Docker守护进程做的每一操作都影响所有容器。有一些开关参数可以附加到Docker守护进程上,前面有看到,在下一部分中运行工具时还有针对配置项的。

Docker守护进程配置文件

指南的这一部分处理Docker守护进程使用的文件和目录。包括从权限到所有权的方方面面。有时,这些区域会包含一些你不希望其他人知道的信息,可能会是普通文件格式。

容器镜像/运行时和构建文件

指南的这一部分包含加固容器文件以及构建文件的信息。

第一部分包含镜像,涵盖基础镜像和使用的构建文件。我们在前面提到过,你需要准确了解所使用的镜像,不仅是基础镜像,还有Docker体验的任何方面。指南的这一部分涵盖在创建你的基础镜像时应遵循的项目。

容器运行时

这一部分此前是下面一部分的内容,但在CIS指南中将其移为自己的部分。容器运行时包含大量的安全相关项。

注意你所使用的运行变量。某些情况下,在你使用它们时攻击者会利用它们。在你的容器中暴露过多,比如将应用密钥和数据库连接暴露为环境变量,不仅会破坏你的容器的安全,还会影响到Docker主机及其上运行的其它容器。

Docker安全运维

指南的这一部分涵盖部署相关的安全领域,这些项目与Docker最佳实践结合更紧密。因此,最好是遵循这些建议。

Docker Bench Security应用

这一部分中,我们将涉及你可以安装并运行的Docker基准安全应用。该工具会检查如下内容:

看起来很熟?因为这和前一节中所回顾的是相同的项,只是构建在未来会做大量工作的应用。它们向你展示你的配置内产生的警告,并提供其它配置项的信息,甚至是我们传给test的各项。

下面我们会来看如何运行该工具、一个实际实例,以及进程输出的意思。

在MacOS 和Windows的Docker上运行该工具

运行这个工具非常简单。已经为我们在Docker容器中进行了打包。虽然你可以获取源代码并自定义输出或以某些方式操作它(比如,邮件发送输出),你可能只需默认的即可。

该工具的 GitHub 项目地址为https://github.com/docker/docker-bench-security/,在macOS或Windows电脑上运行这个工具,你只需要将如下命令拷贝到终端即可。以下命令缺少了检查systemd所需的行,因为Docker for macOS和Docker for Windows的底层操作系统Moby Linux,并不运行systemd。我们一会儿再来看带有systemd的系统:

镜像下载好之后,它启动启动并立即开始审计你的Docker主机,打印运行的结果,如以下所示:

可以看到,有一些警告([WARN])、备注([NOTE])和信息([INFO]),但因类主机由Docker所管理,如同预期不和过度担心。

Ubuntu Linux上运行该工具

我们在深入了解审计的输出之前,我会先在DigitalOcean中启动一个常规的Ubuntu 16.04.5 LTS服务器,并使用Docker Machine安装一个全新的Docker,执行命令如下:

译者注:先运行eval $(docker-machine env docker-digitalocean)再运行如下的命令

安装之后,我会启动一些容器,每个容器都不涉及敏感设置。我会从 Docker Hub启动如下两个容器:

然后,我们会基于Ubuntu 16.04构建一个自定义镜像,使用如下Dockerfile来运行SSH:

使用如下命令来构建和启动它:

可以看到,在一个镜像中,我们在root-nginx容器中以可读可写权限挂账主机的根文件系统。我们还在priv-nginx中以扩展权限进行了运行,最后在sshd中运行SSH。

运行如下命令来在我们的 Ubuntu Docker主机上启动审计:

因为我们运行的操作系统支持systemd,这里挂载了 /usr/lib/systemd,所以我们可对其进行审计。

有大量的输出有待消化,它们的意思究竟是什么呢?我们来逐一进行分解。

掌握输出

我们看到了三类输出,如下:

前面提到,在审计中会包含七大部分,如下:

下面来了解各个部分所扫描的内容。这些扫描结果来自默认的Ubuntu Docker主机,至此还没有对系统做过任何修改。我们集中看每个部分的 [WARN]项。你在自己运行时可能会出现其它警告,但这是大部分人首次会看到的警告。

主机配置

我的主机配置有5项为[WARN] 状态,如下:

默认情况下,Docker使用主机上的/var/lib/docker存储所有文件,包含由默认驱动器创建的所有镜像、容器和数据卷。这表示这个文件夹会快速变大。因为我的主机运行的是单个分区(取决于容器所做的事),这可能会填满整个磁盘,会导致我的主机不可用:

标出这些警告是因为没有安装auditd,以及Docker守护进程和相关联的文件没有审计规则,参见博客文章https://www.linux.com/learn/customized-file-monitoring-auditd/。

Docker守护进程配置

我的Docker守护进程配置标出了8个为[WARN]状态,如下所示:

默认情况下,Docker对相同主机上容器间的通讯不做任何限制。可以改变这一行为,更多有关Docker网络的信息,参见:https://docs.docker.com/engine/userguide/networking/。

译者注:Alan测试时显示为 PASS

早期的Docker中大量的使用了AUFS,但现在它已不再被视作最佳实践,因为可能会带来主机内核的问题:

默认用户命令空间没有被重映射。对它们进行映射如今会导致多个Docker功能的问题,参见https://docs.docker.com/engine/reference/commandline/dockerd/来了解已很有限制的更多信息:

默认Docker的安装不限制Docker守护进程的访问,你可以通过启用一个认证插件来限制访问为认证用户。更多细节,参见https://docs.docker.com/engine/extend/plugins_authorization/:

因为我只运行了单主机,没有使用服务,如rsyslog来将Docker主机的日志发送到中央服务器上,也没有在我的Docker守护进程上配置日志驱动器。参见https://docs.docker.com/engine/admin/logging/overview/了解更多信息:

–live-restore标记启动Docker中无守护进程容器的全面支持,这表示在守护进程关闭时,不停止容器,它们将继续运行,并且在重启时与容器进行恰当的重新连接。默认因向后兼容性的问题并没有启动,更多信息,参见https://docs.docker.com/engine/admin/live-restore/:

你的容器有两种方式路由到外部世界,通过回环NAT或userland代理。对大部分安装,更推荐回环NAT模式,因为它利用了iptable并且性能更佳。在无法使用它时,Docker使用userland代理。大部分现代操作系统上的Docker软件都支持回环NAT。有关如何禁用userland代理的详细信息,参见https://docs.docker.com/engine/userguide/networking/default_network/binding/:

这会停止容器内通过设置suid和sgid无法获取其它权限的进程,这会限制任意尝试访问授权二进制的危险操作。

Docker守护进程配置文件

这部分中没有 [WARN] 状态的项目,这在意料之中,因为Docker使用Docker Machine进行部署。

容器镜像和构建文件

针对容器镜像和构建文件我有三个状态为[WARN]的,你会看到多行警告的状态前缀为*:

我运行的容器中的进程都是以root用户运行的,这是大部分容器的默认动作。更多信息请参见https://docs.docker.com/engine/security/security/:

启动Docker的内容信任确保你所拉取的容器镜像的归属可信,因为在你推送的时候会进行数字签名,这意味着你可总可以运行所想要运行的镜像。有关内容信任的更多信息,参见https://docs.docker.com/engine/security/trust/content_trust/:

在构建你的镜像时,可以在HEALTHCHECK中构建,这样可以保证在容器由你的镜像启动时,Docker会定期检查你的容器的状态,并且在需要的情况下重启或重新发布容器。更多信息请参见https://docs.docker.com/engine/reference/builder/#healthcheck。

容器运行时

我们Docker主机上启动容器时对审所知甚少,有大量的漏洞存在,总共有11处:

以上的漏洞是一个误判,我们并没有运行SELinux,因为这是一台Ubuntu机器,SELinux仅用于Red Hat相关机器,而5.1显示的则是我们所要的[PASS]结果:

接下来的两个[WARN]状态由我们自己产生的,如下:

以下也是我们自己产生的:

这些可以被安全地忽略,你应该很少会启动以特权模式启动一个容器。仅当你的容器需要与Docker主机上运行的Docker引擎进行交互时才需要,例如,在你运行图形界面(如Portainer)时,在第十一章 Portainer: 一个Docker的GUI中进行了讲解。

我们还讨论了你不应该在容器中运行SSH,只有极少数的用例,如在特定网络中运行跳板机,但这些是特例。

接下来两个[WARN]状态的标记是因为,Docker中默认Docker主机上所有运行的容器平均共享资源;设置容器的内存和 CPU 优先级限制会确保你希望的优先级较高的容器不会被优先级较低的容器抢占资源。

本章前面已讨论过,如果可能,请以只读方式启动容器,并为已知需写入数据的进程挂载数据卷:

抛出以下标记的原因是我们没有告诉Docker把暴露的端口与Docker主机上指定的IP地址进行绑定:

因为我们的测试Docker主机只有单个NIC(网卡),这不是什么问题,但如果我的Docker有多个网络接口,那么这个容器会暴露在所有这些网络中,就会成为问题了,比如,我有内网和外网。详细信息参见https://docs.docker.com/engine/userguide/networking/:

虽然我们没有使用–restart标记来启动我们的容器,MaximumRetryCount没有默认值。这表示如果容器不断失败,它还在那里尝试重启。这对Docker可能带来负面效果,将MaximumRetryCount添加为5表示容器会尝试重启5次,然后放弃:

默认情况下,Docker不对进程或其子进程通过suid或sgid获取新权限进行限制。有关如何停止这一行为的更多细节,参见http://www.projectatomic.io/blog/2016/03/no-new-privs-docker/:

再次,我们没有使用健康检查,也即Docker不会定期检查容器的状态。查看GitHub问题中引入这一功能的拉取请求,可浏览https://github.com/moby/moby/pull/22719/:

攻击者可能通过在容器中使用一条命令来触发fork炸弹。这会导致Docker主机的崩溃,唯一的恢复方式是重启主机。你可以使用–pids-limit来进行保护。更多信息,但看https://github.com/moby/moby/pull/18697/这一拉取请求。

Docker安全运维

这一部分包含有关最佳实践的[INFO],如下:

译者注:此处结果与原书略有不同

Docker Swarm配置

这一部分包含[PASS]信息,因为我们没有在主机上启用 Docker Swarm:

Docker Bench的总结

可以看出,对Docker主机上运行Docker Bench是种很好的方式,让你理解Docker主机如何一步步地符合CIS Docker基准;它显然比手动的过230页文档的每条测试要更易于管理。

第三方安全服务

结束本章之前,我们将来看一些第三方服务,它们帮助你评估镜像的漏洞,

Quay

Quay是一个CoreOS的镜像仓库服务,被Red Hat收购,类似Docker Hub/仓库,一个区别是Quay实际上会在镜像推送/构建时执行安全检查。

你可以通过所选镜像的仓库标签来查看扫描结果,在那里会有一列安全扫描。在以下截图中可以看到,我们创建的示例镜像没有安全问题:

点击Passed会进入一个镜像中所监测到的漏洞的详细分解。因为我们这里不存在漏洞(好事),所以这个界面没有什么内容。但是,点击左侧菜单的Packages图标会显示扫描所发现的一系列包。就我们的测试镜像而言,有29个无漏洞的包,都与包的版本确定信息显示在这里,还包含它们是如何引入到镜像中的:

你还会看到,Quay在扫描我们公开的镜像,托管在Quay所提供的免费开源方案中。安全扫描是Quay的所有方案的标准服务。

Clair

Clair是一个来自CoreOS的开源项目,它是一款提供Quay托管版本以及商业支持企业版的静态分析功能服务。

它通过创建以下漏洞数据库的本地镜像来运作:

拥有了镜像数据资源后,它挂载镜像文件系统,然后执行所安装包的扫描,将它们与前面的数据源的签名进行比对。

Clair并不是一个直接的服务,它只有API驱动的接口,并且API默认并不带有漂亮的网页界面和命令行工具。它的API文档参见:https://coreos.com/clair/docs/latest/api_v1.html。

安装指南参见项目的GitHub页面:https://github.com/coreos/clair/。

同时,你可以在其集成页面找到支持Clair的工具列表:https://coreos.com/clair/docs/latest/integrations.html。

Anchore

我们要讲解的最后一个工具是Anchore。有多个版本,有云端的版本和本地资产安装的企业版,都带有全面的网页图形界面。有一个与Jenkins关联的版本,同时有开源命令行扫描器,我们下面就要来了解。

这个版本以Docker Compose进行发布,因此我们应先从创建所需的文件夹开始,并且我们还将从项目GitHub仓库下载该Docker Compose和基本配置文件。

现在基本文件已就位,你可以使用如下命令拉取镜像并启动容器:

在我们与Anchore部署交互之前,需要安装命令行客户端。如果你使用macOS,那么你需要运行如下命令,如果已安装 pip 请忽略第一条命令:

对于Ubuntu用户,你应该运行如下命令,这次哪果已安装 pip 请忽略前两条命令:

安装后,你可以运行如下命令来检查安装的状态:

这会显示安装的整体状态,初次启动可能会花费一到两分钟来显示所有内容:

下一条命令显示Anchore处于数据库同步的什么位置:

从下面的输出看以看出,我的软件当前在同步CentOS 6数据库。这一进程会花费数小时,但在我们的例子中,我们将扫描一个基于Alpine Linux的镜像:

接下来我们需要选择一个镜像来扫描,我们来选取一个较老的镜像,如下:

初次运行扫描会花费一到两分钟,可以运行如下命令来检查状态:

过一会儿后,状态应该会由analyzing变为analyzed:

这会显示镜像的总览,如下:

然后你可以通过运行如下命令来查看问题列表(如果有问题):

可以看到,每个列出的包都有当前版本,CVE问题的链接以及一个修复报出问题的版本号确认。

可以使用如下命令来删除Anchore容器:

总结

本章中,我们讲解了Docker安全的方方面面。首先,我们来看一些你在运行容器时安全方面必须考虑的事情(对比一般的虚拟机)。我们看了它的优势以及Docker主机,然后讨论了镜像的可信度。然后我们了解了可用于安全方面的Docker命令。

我们启动了一个只读容器,这样我们可以最小化攻击者在我们运行的容器中带来的潜在损害。因为不是所有的应用可以运行在只读容器中,然后我们来看了如何追踪镜像在启动后所产生的变化。在查看问题时,能够在运行时中发现文件中所做的改变总是很有用的。

然后,我们讨论了互联网安全中心的Docker指南。该指南有助于你设置Docker环境的方方面面。最后,我们来看了Docker Bench安全。我们了解了如何启动并支持它,并且我们快速浏览了一个输出内容的示例。我们分析了输出来了解它的含义。记住主机配置所涵盖的七项:主机配置、Docker守护进程配置、Docker守护进程配置文件、容器镜像和构建文件、容器运行时、容器安全运维和Docker Swarm配置。

下一章中,我们将来看Docker如何融入已有的工作流,以及一些使用容器方法的新的方式。

课后问题

  1. 在启动容器时,我们将它或其一部分设为只读?
  2. 每个容器上你应该运行多少个进程?
  3. 检查Docker安装是否符合CIS Docker基准的最好的方式是什么?
  4. 在运行Docker Bench Security应用时,应该挂载什么?
  5. 是非题:Quay仅支持私有镜像的扫描。

扩展阅读

更多信息,请访问网站https://www.cisecurity.org/;可在https://www.cisecurity.org/benchmark/docker/上找到Docker基准。

 

退出移动版