使用用户命名空间隔离容器
Linux 命名空间为正在运行的进程提供隔离,限制它们对系统资源的访问,而正在运行的进程并不知道这些限制。有关 Linux 命名空间的更多信息,请参阅 Linux 命名空间。
防止从容器内部进行权限提升攻击的最佳方法是将容器的应用程序配置为以非特权用户身份运行。对于其进程必须在容器内以root
用户身份运行的容器,您可以将此用户重新映射到 Docker 主机上的特权较低的用户。映射的用户被分配一个 UID 范围,该范围在命名空间内作为 0 到 65536 的普通 UID 运行,但在主机本身没有任何权限。
关于重新映射和从属用户和组 ID
重新映射本身由两个文件处理:/etc/subuid
和 /etc/subgid
。每个文件的工作方式相同,但一个与用户 ID 范围相关,另一个与组 ID 范围相关。考虑/etc/subuid
中的以下条目
testuser:231072:65536
这意味着testuser
被分配了231072
的从属用户 ID 范围和接下来的 65536 个连续整数。UID 231072
在命名空间内(在本例中为容器内)映射为 UID 0
(root
)。UID 231073
映射为 UID 1
,依此类推。如果进程试图在命名空间之外提升权限,则该进程将在主机上以非特权的高编号 UID 运行,该 UID 甚至不会映射到真实用户。这意味着该进程在主机系统上根本没有任何权限。
注意
可以为给定的用户或组分配多个从属范围,方法是在
/etc/subuid
或/etc/subgid
文件中为同一用户或组添加多个不重叠的映射。在这种情况下,Docker 只使用前五个映射,这符合内核仅在/proc/self/uid_map
和/proc/self/gid_map
中包含五个条目的限制。
当您配置 Docker 以使用userns-remap
功能时,您可以选择指定现有用户和/或组,也可以指定default
。如果指定default
,则会创建一个用户和组dockremap
并将其用于此目的。
警告
某些发行版不会自动将新组添加到
/etc/subuid
和/etc/subgid
文件中。如果是这种情况,您可能必须手动编辑这些文件并分配不重叠的范围。此步骤在先决条件中介绍。
范围不重叠非常重要,这样进程就无法在不同的命名空间中获得访问权限。在大多数 Linux 发行版上,系统实用程序会在您添加或删除用户时为您管理范围。
此重新映射对容器是透明的,但在容器需要访问 Docker 主机上的资源(例如绑定挂载到系统用户无法写入的文件系统区域)的情况下会引入一些配置复杂性。从安全的角度来看,最好避免这种情况。
先决条件
从属 UID 和 GID 范围必须与现有用户关联,即使这种关联只是实现细节。用户拥有位于
/var/lib/docker/
下的命名空间存储目录。如果您不想使用现有用户,Docker 可以为您创建一个并使用它。如果您想使用现有的用户名或用户 ID,则它必须已经存在。通常,这意味着相关的条目需要在/etc/passwd
和/etc/group
中,但是如果您使用不同的身份验证后端,此要求可能会有所不同。要验证这一点,请使用
id
命令$ id testuser uid=1001(testuser) gid=1001(testuser) groups=1001(testuser)
主机上处理命名空间重新映射的方式是使用两个文件:
/etc/subuid
和/etc/subgid
。添加或删除用户或组时,通常会自动管理这些文件,但在某些发行版上,您可能需要手动管理这些文件。每个文件包含三个字段:用户的用户名或ID,后跟起始 UID 或 GID(在命名空间内被视为 UID 或 GID 0)以及用户可用的最大 UID 或 GID 数量。例如,给定以下条目
testuser:231072:65536
这意味着由
testuser
启动的用户命名空间进程由主机 UID231072
(在命名空间内看起来像 UID0
)拥有,直到 296607(231072 + 65536 - 1)。为了确保命名空间进程无法访问彼此的命名空间,这些范围不应重叠。添加用户后,检查
/etc/subuid
和/etc/subgid
以查看您的用户在每个文件中是否有条目。如果没有,则需要添加它,并注意避免重叠。如果您想使用 Docker 自动创建的
dockremap
用户,请在配置并重新启动 Docker 后检查这些文件中是否存在dockremap
条目。如果 Docker 主机上存在任何非特权用户需要写入的位置,请相应地调整这些位置的权限。如果您想使用 Docker 自动创建的
dockremap
用户,这也适用,但您必须在配置并重新启动 Docker 后才能修改权限。启用
userns-remap
会有效地屏蔽现有的镜像和容器层,以及/var/lib/docker/
中的其他 Docker 对象。这是因为 Docker 需要调整这些资源的所有权,并实际将它们存储在/var/lib/docker/
中的子目录中。最好在新安装的 Docker 上启用此功能,而不是在现有安装上启用。同样,如果您禁用
userns-remap
,则无法访问启用它时创建的任何资源。检查用户命名空间的限制,以确保您的用例是可行的。
在守护进程上启用 userns-remap
您可以使用--userns-remap
标志启动dockerd
,或者按照此过程使用daemon.json
配置文件配置守护进程。推荐使用daemon.json
方法。如果您使用该标志,请使用以下命令作为模型
$ dockerd --userns-remap="testuser:testuser"
编辑
/etc/docker/daemon.json
。假设该文件以前为空,则以下条目使用名为testuser
的用户和组启用userns-remap
。您可以通过ID或名称来指定用户和组。只有当组名或ID与用户名或ID不同时,才需要指定组名或ID。如果您同时提供用户名和组名或ID,请用冒号 (:
) 分隔它们。以下格式都适用于该值,假设testuser
的UID和GID为1001
testuser
testuser:testuser
1001
1001:1001
testuser:1001
1001:testuser
{ "userns-remap": "testuser" }
注意
要使用
dockremap
用户并让 Docker 为您创建它,请将值设置为default
而不是testuser
。保存文件并重新启动 Docker。
如果您使用的是
dockremap
用户,请使用id
命令验证 Docker 是否已创建它。$ id dockremap uid=112(dockremap) gid=116(dockremap) groups=116(dockremap)
验证该条目是否已添加到
/etc/subuid
和/etc/subgid
$ grep dockremap /etc/subuid dockremap:231072:65536 $ grep dockremap /etc/subgid dockremap:231072:65536
如果这些条目不存在,请以
root
用户身份编辑这些文件,并分配一个起始 UID 和 GID,该值等于最高分配的 UID 和 GID 加上偏移量(在本例中为65536
)。注意不要让任何范围重叠。使用
docker image ls
命令验证以前的镜像是否不可用。输出应为空。从
hello-world
镜像启动一个容器。$ docker run hello-world
验证
/var/lib/docker/
中是否存在一个名为命名空间用户的 UID 和 GID 的命名空间目录,该目录由该 UID 和 GID 拥有,并且不可由组或世界读取。一些子目录仍然由root
拥有,并且具有不同的权限。$ sudo ls -ld /var/lib/docker/231072.231072/ drwx------ 11 231072 231072 11 Jun 21 21:19 /var/lib/docker/231072.231072/ $ sudo ls -l /var/lib/docker/231072.231072/ total 14 drwx------ 5 231072 231072 5 Jun 21 21:19 aufs drwx------ 3 231072 231072 3 Jun 21 21:21 containers drwx------ 3 root root 3 Jun 21 21:19 image drwxr-x--- 3 root root 3 Jun 21 21:19 network drwx------ 4 root root 4 Jun 21 21:19 plugins drwx------ 2 root root 2 Jun 21 21:19 swarm drwx------ 2 231072 231072 2 Jun 21 21:21 tmp drwx------ 2 root root 2 Jun 21 21:19 trust drwx------ 2 231072 231072 3 Jun 21 21:19 volumes
您的目录列表可能有一些差异,尤其是在您使用与
aufs
不同的容器存储驱动程序时。由重新映射用户拥有的目录将用于代替
/var/lib/docker/
下的相同目录,并且未使用的版本(例如此示例中的/var/lib/docker/tmp/
)可以删除。启用userns-remap
时,Docker 不使用它们。
为容器禁用命名空间重新映射
如果在守护程序上启用用户命名空间,则默认情况下,所有容器都将启用用户命名空间启动。在某些情况下,例如特权容器,您可能需要为特定容器禁用用户命名空间。请参阅用户命名空间已知限制,了解其中一些限制。
要为特定容器禁用用户命名空间,请将--userns=host
标志添加到docker container create
、docker container run
或docker container exec
命令。
使用此标志时存在一个副作用:该容器将不会启用用户重新映射,但是,由于只读(镜像)层在容器之间共享,因此容器文件系统的所有权仍然会被重新映射。
这意味着整个容器文件系统将属于--userns-remap
守护程序配置中指定的用户(在上例中为231072
)。这可能会导致容器内程序出现意外行为。例如sudo
(它检查其二进制文件是否属于用户0
)或带有setuid
标志的二进制文件。
用户命名空间已知限制
以下标准 Docker 功能与运行启用用户命名空间的 Docker 守护程序不兼容
- 与主机共享 PID 或 NET 命名空间 (
--pid=host
或--network=host
)。 - 不知道或无法使用守护程序用户映射的外部(卷或存储)驱动程序。
- 在
docker run
上使用--privileged
模式标志,而不同时指定--userns=host
。
用户命名空间是一项高级功能,需要与其他功能协调。例如,如果从主机挂载卷,则如果需要读取或写入卷内容,则必须预先安排文件所有权。
虽然用户命名空间容器进程内的 root 用户拥有容器内超级用户的许多预期权限,但 Linux 内核会根据内部知识(这是一个用户命名空间进程)施加限制。一个值得注意的限制是无法使用mknod
命令。当 root 用户运行时,容器内设备创建权限被拒绝。