Podman 和用户命名空间:天作之合

了解如何使用 Podman 在独立的用户命名空间中运行容器。
298 位读者喜欢这篇文章。
Architecture and design planning layouts

opensource.com

Podmanlibpod 库的一部分,使用户能够管理 pod、容器和容器镜像。在我的上一篇文章中,我写了关于 Podman 作为一种更安全的方式来运行容器。在这里,我将解释如何使用 Podman 在独立的用户命名空间中运行容器。

我一直认为 用户命名空间,主要由 Red Hat 的 Eric Biederman 开发,是分离容器的一个重要特性。用户命名空间允许您指定用户标识符 (UID) 和组标识符 (GID) 映射来运行您的容器。这意味着您可以在容器内部以 UID 0 运行,在容器外部以 UID 100000 运行。如果您的容器进程逃逸容器,内核会将它们视为 UID 100000。不仅如此,任何 UID 拥有的文件对象,如果没有映射到用户命名空间,都将被视为由“nobody”(65534,kernel.overflowuid)拥有,并且除非该对象可以被“其他”(世界可读/可写)访问,否则容器进程将不允许访问。

如果您有一个由“真实” root 拥有的文件,权限为 660,并且用户命名空间中的容器进程尝试读取它,它们将被阻止访问,并且会将该文件视为由 nobody 拥有。

一个例子

以下是它可能的工作方式。首先,我在我的系统中创建一个由 root 拥有的文件。

$ sudo bash -c "echo Test > /tmp/test"
$ sudo chmod 600 /tmp/test 
$ sudo ls -l /tmp/test 
-rw-------. 1 root root 5 Dec 17 16:40 /tmp/test

接下来,我将该文件卷挂载到一个使用用户命名空间映射 0:100000:5000 运行的容器中。

$ sudo podman run -ti -v /tmp/test:/tmp/test:Z --uidmap 0:100000:5000 fedora sh
# id
uid=0(root) gid=0(root) groups=0(root)
# ls -l /tmp/test
-rw-rw----. 1 nobody nobody 8 Nov 30 12:40 /tmp/test
# cat /tmp/test
cat: /tmp/test: Permission denied

上面的 --uidmap 设置告诉 Podman 在容器内部映射 5000 个 UID 的范围,从容器外部的 UID 100000 开始(因此范围是 100000-104999)到从容器内部的 UID 0 开始的范围(因此范围是 0-4999)。在容器内部,如果我的进程以 UID 1 运行,则在主机上是 100001。

由于真正的 UID=0 没有映射到容器中,因此任何由 root 拥有的文件都将被视为由 nobody 拥有。即使容器内的进程具有 CAP_DAC_OVERRIDE,它也无法覆盖此保护。 DAC_OVERRIDE 使 root 进程能够读取/写入系统上的任何文件,即使该进程不是由 root 拥有或世界可读或可写的。

用户命名空间功能与主机上的功能不同。它们是命名空间功能。这意味着我的容器 root 仅在容器内具有功能——实际上仅在映射到用户命名空间的 UID 范围内。如果容器进程逃逸容器,它将对未映射到用户命名空间的 UID(包括 UID=0)没有任何功能。即使进程可以以某种方式进入另一个容器,如果该容器使用不同的 UID 范围,它们也不会具有这些功能。

请注意,SELinux 和其他技术也限制了容器进程脱离容器后会发生什么。

使用 `podman top` 显示用户命名空间

我们已将功能添加到 podman top,允许您检查在容器内运行的进程的用户名,并识别它们在主机上的真实 UID。

让我们首先使用我们的 UID 映射运行一个 sleep 容器。

$ sudo podman run --uidmap 0:100000:5000 -d fedora sleep 1000

现在运行 podman top

$ sudo podman top --latest user huser
USER   HUSER
root   100000

$ ps -ef | grep sleep
100000   21821 21809  0 08:04 ?    	00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000

请注意 podman top 报告用户进程在容器内部以 root 身份运行,但在主机上以 UID 100000(HUSER)身份运行。此外,ps 命令确认 sleep 进程以 UID 100000 运行。

现在让我们运行第二个容器,但这次我们将选择从 200000 开始的单独的 UID 映射。

$ sudo podman run --uidmap 0:200000:5000 -d fedora sleep 1000
$ sudo podman top --latest user huser
USER   HUSER
root   200000

$ ps -ef | grep sleep
100000   21821 21809  0 08:04 ?    	00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000
200000   23644 23632  1 08:08 ?    	00:00:00 /usr/bin/coreutils --coreutils-prog-shebang=sleep /usr/bin/sleep 1000

请注意,podman top 报告第二个容器在容器内部以 root 身份运行,但在主机上以 UID=200000 身份运行。

还要看看 ps 命令——它显示了两个 sleep 进程正在运行:一个作为 100000,另一个作为 200000。

这意味着在单独的用户命名空间中运行容器为您提供了进程之间的传统 UID 分离,这从一开始就是 Linux/Unix 的标准安全工具。

用户命名空间的问题

多年来,我一直提倡用户命名空间作为每个人都想要但几乎没有人使用的安全工具。原因是没有任何文件系统支持或转移文件系统。

在容器中,您希望在许多容器之间共享 base 镜像。上面的例子在每个例子中使用 Fedora base 镜像。Fedora 镜像中的大多数文件由真正的 UID=0 拥有。如果我在此镜像上使用用户命名空间 0:100000:5000 运行一个容器,默认情况下它会将所有这些文件视为由 nobody 拥有,因此我们需要转移所有这些 UID 以匹配用户命名空间。多年来,我一直希望有一个挂载选项来告诉内核重新映射这些文件 UID 以匹配用户命名空间。上游内核存储开发人员继续调查并在此功能上取得进展,但这是一个难题。

Podman 可以在同一镜像上使用不同的用户命名空间,因为 containers/storage 内置了自动 chowning,由 Nalin Dahyabhai 领导的团队开发。 Podman 使用 containers/storage,并且 Podman 第一次在新用户命名空间中使用容器镜像时,container/storage 会将镜像中的所有文件的所有权“chown”(即更改所有权)为用户命名空间中映射的 UID,并创建一个新镜像。 可以将此视为 fedora:0:100000:5000 镜像。

当 Podman 使用相同的 UID 映射在镜像上运行另一个容器时,它会使用“预 chown”镜像。 当我在 0:200000:5000 上运行第二个容器时,containers/storage 会创建第二个镜像,我们称之为 fedora:0:200000:5000

请注意,如果您正在执行 podman buildpodman commit 并将新创建的镜像推送到容器注册表,Podman 将使用 container/storage 反转转移并将镜像推送回去,并将所有文件的所有权 chown 回到真正的 UID=0。

这可能会导致在新 UID 映射中创建容器的速度变慢,因为 chown 的速度可能会很慢,具体取决于镜像中的文件数量。 此外,在正常的 OverlayFS 上,镜像中的每个文件都会被复制上去。 正常的 Fedora 镜像可能需要长达 30 秒才能完成 chown 并启动容器。

幸运的是,Red Hat 内核存储团队,主要是 Vivek Goyal 和 Miklos Szeredi,在内核 4.19 中为 OverlayFS 添加了一个新功能。 该功能称为 仅元数据复制。 如果您使用 metacopy=on 作为挂载选项来挂载覆盖文件系统,则当您更改文件属性时,它不会复制底层的内容; 内核会创建新的 inode,其中包含指向底层数据的属性的引用。 如果内容发生更改,它仍然会复制内容。 如果您想试用一下,此功能在 Red Hat Enterprise Linux 8 Beta 中可用。

这意味着容器 chowning 可以在几秒钟内完成,并且您不会使每个容器的存储空间翻倍。

这使得使用 Podman 等工具在单独的用户命名空间中运行容器成为可能,从而大大提高了系统的安全性。

展望未来

我想添加一个新标志,例如 --userns=auto,到 Podman,它会告诉它自动为运行的每个容器选择一个唯一的用户命名空间。 这类似于 SELinux 使用单独的多类别安全性 (MCS) 标签的方式。 如果您设置环境变量 PODMAN_USERNS=auto,您甚至不需要设置该标志。

Podman 最终允许用户在单独的用户命名空间中运行容器。 诸如 BuildahCRI-O 等工具也将能够利用用户命名空间。 但是,对于 CRI-O,Kubernetes 需要了解哪个用户命名空间将运行容器引擎,上游正在研究这一点。

在我的下一篇文章中,我将解释如何在用户命名空间中以非 root 身份运行 Podman。

标签
User profile image.
Daniel Walsh 在计算机安全领域工作了近 30 年。 Dan 于 2001 年 8 月加入 Red Hat。

评论已关闭。

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.