使用容器的一个很大好处是,大多数时候你不必考虑后台发生了什么,像 Docker 和 Kubernetes 这样的工具,在向用户隐藏系统复杂性方面做得很好。
但是,当您需要调试和保护容器环境时,了解如何在底层与容器进行交互会非常有帮助。幸运的是,由于大多数容器化工具都是基于Linux 构建的,因此您可以使用现有的 Linux 工具与它们进行交互和调试。
在做容器环境安全防护时,了解容器是如何进行隔离以及这些隔离有什么问题也很重要,这样才能降低潜在的风险。
本系列将介绍容器的工作原理,并介绍一些用于保护容器化环境并对其进行故障排除的实用想法。在本系列中,我们将主要关注标准的 Docker 样式容器,但我们的示例也将适用于其他容器运行时,如 Podman、containerd 和 CRI-O。
在这篇文章中,我们将通过Linux 工具与容器进行交互,演示容器作为进程的一些特性,并探讨这对容器安全意味着什么。
容器只是进程
关于容器,首先要了解的是,从操作系统的角度来看,它们是进程,就像直接在主机上运行的任何其他应用程序一样。为了演示这一点,我们可以创建一个 Linux 虚拟机并在其上安装Docker。注意:如果你有兴趣遵循本系列中的实际示例,我们建议使用标准 Linux VM,而不是适用于 Windows/MacOS 的 Docker,因为这些工具增加了一些复杂性,使你更难看到发生了什么。
让我们首先检查 VM 上是否存在任何活动的nginx进程。
代码语言:javascript复制ps -fC nginx
这应该返回一个空列表,因为我们目前没有任何 NGINX Web 服务器在运行。现在,让我们使用Docker Hub中的nginx 映像启动 Docker 容器。
代码语言:javascript复制docker run -d nginx:1.23.1
容器启动后,我们将再次运行命令:ps
代码语言:javascript复制ps -fC nginx
这一次,我们得到了如下图所示的内容,它向我们展示了几个 NGINX 进程现在正在机器上运行。就我们的Linux机器而言,有人刚刚在主机上运行了NGINX。
当我们更深入地研究容器是进程的概念时,一个关键的问题是:如何区分从 Docker 镜像启动的 NGINX 服务器和刚刚安装在 VM 上的 NGINX 服务器?有几种方法可以做到这一点,但第一种也是最简单的方法是检查正在运行的容器:
代码语言:javascript复制docker ps
或者,我们可以使用 Linux 进程工具来确定 Web 服务器是否作为容器运行。例如:ps的选项(例如:ps -ef --forest) ,让我们可以看到进程的层次结构。在本例中,我们的 NGINX 进程的父进程为containerd-shim-runc-v2 。您应该会看到主机上运行的每个容器的shim进程。此shim进程是 containerd 的一部分,Docker 使用它来管理其它的进程。shim进程的主要作用是允许重新启动 containerd 或 Docker 守护程序,而无需重新启动主机上运行的所有容器。
与容器进程进行交互
我们现在知道容器只是进程,但这对我们如何与它们交互意味着什么?能够将它们作为进程进行交互,对于故障排除和调查正在运行的容器中的变更(例如,在取证调查中)都很有用。值得一提的是,我们可以使用 /proc 文件系统来获取有关正在运行的容器的更多信息。
Linux 中的/proc文件系统是虚拟或伪文件系统。它不包含真实文件,而是填充了有关正在运行的系统的信息。只要用户在运行 Docker 的主机上具有适当的权限,他们就可以访问主机上运行的任何容器的信息。
让我们看一下我们之前启动的 NGINX 容器的一些信息。在我们使用的测试系统上,我们可以看到nginx进程ID为 2336。如果我们列出/proc中的文件,我们将看到主机上每个进程的编号目录,包括我们的NGINX进程。这些目录中的每一项都包含各种文件和目录,其中包含有关该进程的信息,这意味着我们可以打开2336目录以了解有关进程的更多信息。
使用已删除文件编辑器或进程监视器等工具的加固容器对安全也很有帮助。加固容器镜像是一种常见的安全建议,但它确实使调试更加麻烦。您可以通过对主机上的目录/proc访问容器的根文件系统来编辑容器内的文件。打开/proc/[PID]/root可以看到具有该PID的进程的目录列表。
在这种情况下,运行将向我们显示如下所示的列表:
代码语言:javascript复制sudo ls /proc/2336/root
现在,让我们用touch添加一个文件到此目录,我们可以使用docker exec列出容器上的文件来确认它已添加。docker exec可用于执行诸如从主机编辑容器中的配置文件之类的操作。
容器作为进程的另一个好处是:我们可以使用主机工具来终止这些进程,而无需使用容器工具。通常来说,这不是一个好的使用方式,因为它可能会与 Docker 的重启策略等设置冲突,但有时可能是必要的。
例如,让我们使用命令sudo kill 2336。然后,我们可以运行docker ps以确认我们的容器不再存在。
这对安全意味着什么?
我们已经看到,容器只是 Linux 进程,这对安全产生了一些有趣的影响。那么,我们可以很容易得出一个结论,即任何具备主机访问权限的人都可以通过进程列表来查看有关正在运行的容器的信息,即使他们不能直接访问 Docker 等工具。
例如,使用 Linux 命令访问容器的环境变量,这些变量通常存储机密信息。有权访问主机的用户可以读取/proc进程区域内的文件内容以查看该信息,如下面的示例所示,可以看到其中容器是使用环境变量“TOP_SECRET=API_KEY”启动的。
除此之外,其实还可以使用现有的 Linux 安全工具与容器进行交互,我们将在本系列的后面部分看到这方面的示例,但到目前为止,我们已经证明可以通过/proc来检查容器的根文件系统和其它信息 。这对于调查一些已经发生的攻击行为非常有用,例如查看攻击者已添加到容器中的文件。
结论
了解 Linux 容器如何工作的第一步是意识到它们只是进程。当然,这为许多其他问题打开了大门,比如“容器内的进程如何与底层主机隔离?”