用户命名空间和能力是使容器安全的重要内核功能。它们允许更好地隔离容器并限制容器可能拥有的权限。不久前,一位用户报告了一个错误,其中注意到在容器之间共享命名空间时出现了一些奇怪的行为,这可能导致安全问题。让我们仔细看看如果您不了解这种行为,可能会出现什么问题。
首先,让我们构建一个安装了 nftables 的容器镜像
$ cat Containerfile
FROM fedora
RUN dnf install -y nftables
$ podman build -t testimg .
现在我们已经在镜像中安装了 nft,可以使用它了。nft 管理防火墙,因此需要 CAP_NET_ADMIN 能力才能运行,否则会报错。所以现在当我们运行
$ podman run --rm testimg nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
$ podman run --rm --cap-add CAP_NET_ADMIN testimg nft list ruleset
<Results omitted for brevity>
只有第二个命令有效,因为默认情况下容器没有 CAP_NET_ADMIN。请注意,该命令不会返回任何输出,因为没有规则。
现在假设我们运行两个容器并希望在它们之间共享网络,这可以通过使用 --network container:<name>
选项来完成。在第一个终端中运行
$ podman run --rm --name test -it testimg
[root@a6a4ddc4a7c8 /]# nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
[root@a6a4ddc4a7c8 /]#
在第二个终端中运行
$ podman run --rm --network container:test -it testimg
[root@6d4b196e75be /]# nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
在这两种情况下,我们都无法按预期修改命名空间。
让我们再次运行这些命令,但这次我们为第一个容器使用 --userns keep-id
。
# terminal 1
$ podman run --rm --name test --userns keep-id --user 0:0 -it testimg
[root@91896ef2fac8 /]# nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
[root@91896ef2fac8 /]#
# terminal 2
podman run --rm --network container:test -it testimg
[root@59ad31cb4de1 /]# nft list ruleset
[root@59ad31cb4de1 /]#
现在,即使我们没有给第二个容器 CAP_NET_ADMIN,它仍然可以修改网络。这对于大多数人来说是出乎意料的,因此如果您这样做而没有意识到这一点,可能会造成安全问题。
现在让我们最后一次这样做,但为第二个容器使用 --userns keep-id
。
# terminal 1
$ podman run --rm --name test -it testimg
[root@8860d6004a8d /]# nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
[root@8860d6004a8d /]#
# terminal 2
$ podman run --rm --network container:test --userns keep-id --user 0:0 -it testimg
[root@10cb690d5b93 /]# nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
[root@10cb690d5b93 /]#
现在,两个容器都无法修改网络。您甚至可以为第二个容器添加 --cap-add CAP_NET_ADMIN
,它仍然无法工作。
发生了什么?
这种行为起初可能看起来出乎意料且有缺陷,但它完全按照内核安全检查的设计工作。能力总是按用户命名空间划分的,当创建一个新的用户命名空间时,进程将在新的命名空间中获得所有能力,但会丢弃父命名空间中的所有能力。同样重要的是要知道,内核总是根据创建其他命名空间的用户命名空间来检查命名空间的权限。如果命名空间是从任何父用户命名空间创建的,那么内核将不允许您修改它,您基本上对它们没有任何能力。另一方面,对于由子用户命名空间创建的命名空间,即使进程丢弃它们,它也总是拥有所有能力。
这正是第二种情况中发生的事情。OCI 运行时首先创建子用户命名空间,然后在其内部创建新的网络命名空间。第二个容器随后位于父用户命名空间中,因此它将始终拥有共享网络命名空间的所有能力。

在第三种情况下,情况正好相反,第二个容器是用户命名空间的一部分,因此是子容器,因此它永远不能修改由父用户命名空间创建的网络命名空间。

要解决这个问题,最好在容器之间也共享用户命名空间,只要两个容器都属于同一个用户命名空间,您就会得到预期的行为,即所赋予的能力受到尊重。让我们再次尝试第二种情况,但这次也为第二个容器使用 --userns container:
。
# terminal 1
$ podman run --rm --name test --userns keep-id --user 0:0 -it testimg
[root@198c6101c2cd /]# nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
[root@198c6101c2cd /]#
# terminal 2
$ podman run --rm --network container:test --userns container:test -it testimg
[root@43644d997e2d /]# nft list ruleset
Operation not permitted (you must be root)
netlink: Error: cache initialization failed: Operation not permitted
[root@43644d997e2d /]#

网络命名空间只是一个例子,此规则也适用于其他命名空间。您可以在手册页 user_namespaces(7) 中阅读有关此行为的更多信息。有了这些知识,您可以避免意外地赋予容器比它们应有的更多权限。
发表评论