在我之前关于用户命名空间和无根容器的文章中,我讨论了如何使用 Podman 和 Buildah 运行和构建容器而无需成为 root 用户。
我展示了你可以做一些很棒的事情,包括使用许多不同的用户 ID (UID) 运行容器、安装软件、设置网络以及在 Quay.io、Docker.io 或几乎任何其他容器注册表中运行容器。
话虽如此,无根容器并非万能药。它们有很多缺点,人们需要了解可能出错的地方。
挂载其他内容的卷
我最近在 GitHub 上回复了关于 Podman 的一个问题。该用户尝试在容器中运行 Plex,并希望将 /run 卷挂载到容器中。他知道禁用权限分离,因为 SELinux 会阻止在容器内使用 /run。当他以 root 用户身份使用 Podman 运行容器时,它工作正常。但是当他以非 root 用户身份运行容器时,它崩溃并出现错误
/59b0879bc9f255137c05850c307d8c9f34543d1fa08658a44c40f43bd950a17a/merged/run/lock
到 /tmp/runctop091524734/runctmpdir731776453:打开
/home/travis/.local/share/containers/storage/overlay/59b0879bc9f255137c05850c307d8c9f3454
3d1fa08658a44c40f43bd950a17a/merged/run/lock/lvm:权限被拒绝\"""
:内部 libpod 错误
此错误表明容器内的进程尝试打开容器内 /run/lock 中的 lvm 文件失败,并返回 permission denied(权限被拒绝)。用户理所当然地感到困惑,因为容器是以特权模式运行的。
“特权模式不意味着容器拥有完整的 root 权限吗?”
为什么会失败?
失败的原因是容器在用户命名空间中运行。即使容器报告为 root,运行容器的进程仍然以其实际 UID 运行。在无根容器中运行不会给用户在主机上额外的权限,除了允许他们使用 /etc/subuid 和 /etc/subgid 文件中定义的一些额外的 UID。
如果用户没有将 /run 挂载到容器中,那么就不会发生此故障,因为 /run 将会使用用户的 UID 创建。并且 /run 中的所有内容都将归用户所有。
如果要从主机将内容卷挂载到无根容器中,则需要确保内容对于用户来说是可读的,而无需 root 权限;并且如果容器需要写入卷挂载,则该内容归用户的 UID 或 /etc/subuid 或 /etc/subgid 中列出的分配给用户使用的 UID 所有。
放弃功能
在 Red Hat Summit 2019 上,我们进行了一个关于容器安全性的精彩实验,该实验说明了从安全角度来看与容器交互的所有方式。其中一个实验涉及在内部运行带有网络时间协议守护进程 (ntpd) 的容器。 ntpd 程序尝试修改运行容器的主机上的系统时间。除非你使用如下命令启动容器,否则当它在非特权容器中以 root 身份运行时,该命令将失败
sudo podman run -d --cap-add SYS_TIME ntpd
Podman 将执行此容器,并在容器中允许 CAP_SYS_TIME 功能,这允许在其中运行的进程修改系统时间。
CAP_SYS_TIME
Set system clock (settimeofday(2), stime(2), adjtimex(2)); set
real-time (hardware) clock.
当用户尝试在无根模式下运行此确切命令时,它会失败。为什么?
如果用户检查容器内的功能,它显示容器具有 CAP_SYS_TIME,那么为什么仍然会被拒绝权限?
同样,运行无根容器不会为你的容器提供任何你的进程在外部不具备的特殊权限。当在无根容器中运行时,你获得的是用户命名空间功能。这些命名空间功能允许 root 进程在容器内部执行一些特权操作。但是,不允许更改系统时间;这需要真正的 CAP_SYS_TIME 系统功能。
由于没有命名空间时间,因此此功能对容器来说有点无用,因此人们通常会抱怨:为什么还要有功能?这是因为许多功能仍然有用。例如,CAP_SETUID 和 CAP_SETGID 允许容器内的进程将其 UID 和组标识符 (GID) 更改为容器内定义的任何 UID 或 GID。仍然禁止修改容器外部进程的 UID 和 GID。还有许多其他示例,只有当进程具有用户命名空间功能时才允许执行这些操作。
绑定到小于 1024 的端口
无根 Podman 缺点的最后一个例子是能够在主机上小于 1024 的端口上监听传入连接。这实际上只是用户命名空间功能的另一个例子。
例如,如果你想运行一个容器并让它监听主机上的端口 80,你需要以 root 身份运行它,或者至少具有 CAP_NET_BIND_SERVICE 功能。
CAP_NET_BIND_SERVICE
Bind a socket to Internet domain privileged ports (port numbers
less than 1024).
命令
sudo podman run -d --net=host httpd
工作正常,并绑定到主机上的端口 80。默认情况下,启用 Podman 以 root 身份运行容器允许 CAP_NET_BIND_SERVICE 功能。但是,如果你以非特权用户身份运行 Podman,这将受到阻止。例如,
podman run -d --net=host httpd
将失败并显示权限被拒绝,因为用户进程不允许绑定到主机上 <1024 的端口,因为它没有对主机的网络命名空间具有 CAP_NET_BIND_SERVICE 功能。
运行
podman run -d httpd
应该可以工作,因为它正在创建一个网络命名空间,并且用户命名空间中的 root 进程对于在容器内创建的网络命名空间具有 CAP_NET_BIND_SERVICE。但是,此端口不是主机上的端口 80,而是容器网络地址上的端口 80。
我们在 无根 Podman 的缺点 GitHub 页面上跟踪这些问题。
结论
运行无根 Podman 和 Buildah 可以完成人们想要使用容器做的大部分事情,但有时仍然需要 root 权限。有时很难知道为什么你会收到权限被拒绝的错误,但希望本文能说明一些主要原因。理解它们将帮助你排除故障并相应地更改你的设计。
1 条评论