在我之前关于用户命名空间和 Podman 的文章中,我讨论了如何使用 Podman 命令启动具有不同用户命名空间的不同容器,从而更好地分隔容器。 Podman 还利用用户命名空间以无根模式运行。 基本上,当非特权用户运行 Podman 时,该工具会设置并加入用户命名空间。 Podman 在用户命名空间内成为 root 用户后,Podman 可以挂载某些文件系统并设置容器。 请注意,这里没有权限提升,只有用户可用的额外 UID,如下所述。
Podman 如何创建用户命名空间?
shadow-utils
大多数当前的 Linux 发行版都包含一个版本的 shadow-utils,它使用 /etc/subuid 和 /etc/subgid 文件来确定用户在用户命名空间中可用的 UID 和 GID。
$ cat /etc/subuid
dwalsh:100000:65536
test:165536:65536
$ cat /etc/subgid
dwalsh:100000:65536
test:165536:65536
useradd 程序会自动为添加到系统的每个用户分配 65536 个 UID。 如果系统上存在现有用户,则需要自行分配 UID。 这些文件的格式为 username:STARTUID:TOTALUIDS。 意味着在我的例子中,dwalsh 被分配了从 100000 到 165535 的 UID 以及我的默认 UID,恰好是 /etc/passwd 中定义的 3265。 在分配这些 UID 范围时,您需要小心,不要与系统上的任何真实 UID 重叠。 如果您有一个用户列为 UID 100001,那么现在我 (dwalsh) 将能够成为此 UID,并且可能会读取/写入/执行 UID 拥有的文件。
Shadow-utils 还添加了两个 setuid 程序(或 setfilecap)。 在 Fedora 上,我有
$ getcap /usr/bin/newuidmap
/usr/bin/newuidmap = cap_setuid+ep
$ getcap /usr/bin/newgidmap
/usr/bin/newgidmap = cap_setgid+ep
Podman 执行这些文件以设置用户命名空间。 您可以通过检查无根容器内的 /proc/self/uid_map 和 /proc/self/gid_map 来查看映射。
$ podman run alpine cat /proc/self/uid_map /proc/self/gid_map
0 3267 1
1 100000 65536
0 3267 1
1 100000 65536
如上所示,Podman 默认将容器中的 root 映射到您当前的 UID (3267),然后将 /etc/subuid 和 /etc/subgid 中分配的 UID/GID 范围从 1 开始映射。这意味着在我的示例中,容器中的 UID=1 是 UID 100000,UID=2 是 UID 100001,一直到 65536,即 165535。
用户命名空间之外的任何由未映射到用户命名空间中的 UID 或 GID 拥有的项目,都属于在 kernel.overflowuid sysctl 中配置的用户,默认值为 35534,我的 /etc/passwd 文件说该名称为 nobody。 由于您的进程无法以未映射的 ID 运行,因此所有者和组权限不适用,因此您只能根据其“其他”权限访问这些文件。 这包括系统上运行的容器上的真实 root 用户拥有的所有文件,因为 root 未映射到用户命名空间中。
Buildah 命令有一个很棒的功能,buildah unshare。 这会将您置于 Podman 运行的同一用户命名空间中,但无需进入容器的文件系统,因此您可以列出主目录的内容。
$ ls -ild /home/dwalsh
8193 drwx--x--x. 290 dwalsh dwalsh 20480 Jan 29 07:58 /home/dwalsh
$ buildah unshare ls -ld /home/dwalsh
drwx--x--x. 290 root root 20480 Jan 29 07:58 /home/dwalsh
请注意,在用户命名空间之外列出主目录属性时,内核报告所有者为 dwalsh,而在用户命名空间内,它报告该目录由 root 拥有。 这是因为主目录由 3267 拥有,并且在用户命名空间内,我们将该 UID 视为 root。
在设置用户命名空间之后,Podman 中接下来会发生什么?
Podman 使用 containers/storage 来提取容器镜像,并且 containers/storage 足够智能,可以将镜像中 root 拥有的所有文件映射到用户命名空间的 root,并将其他 UID 拥有的任何其他文件映射到其用户命名空间 UID。 默认情况下,此内容会被写入 ~/.local/share/containers/storage。 容器存储在无根模式下可以使用 vfs 模式或 Overlay。 注意:只有安装了 fuse-overlayfs 可执行文件才支持 Overlay。
内核只允许用户命名空间 root 挂载某些类型的文件系统; 目前,它允许挂载 procfs、sysfs、tmpfs、fusefs 和绑定挂载(只要源和目标由运行 Podman 的用户拥有。 OverlayFS 尚不支持,但内核团队正在努力允许它)。
然后,Podman 会挂载容器的存储,如果它使用 fuse-overlayfs; 如果存储驱动程序使用 vfs,则不需要挂载。 但是,vfs 上的 Podman 需要大量空间,因为每个容器都会复制整个底层文件系统。
然后,Podman 会挂载 /proc 和 /sys 以及一些 tmpfs 并在容器中创建设备。
为了使用主机网络之外的网络,Podman 使用 slirp4netns 程序来设置 用于非特权网络命名空间的用户模式网络。 Slirp4netns 允许 Podman 将容器中的端口暴露给主机。 请注意,内核仍然不允许非特权进程绑定到小于 1024 的端口。 绑定到端口需要 Podman-1.1 或更高版本。
无根 Podman 可以使用用户命名空间进行容器隔离,但您只能访问 /etc/subuid 文件中定义的 UID。
结论
Podman 工具使人们能够在不牺牲系统安全性的情况下构建和使用容器; 您可以在不给他们 root 权限的情况下为您的开发人员提供他们需要的访问权限。
当您将容器投入生产时,您可以利用用户命名空间提供的额外安全性来使工作负载彼此隔离。
1 条评论