使用用户命名空间隔离容器
Linux 命名空间为运行的进程提供隔离,限制其对系统资源的访问,而运行的进程并不察觉这些限制。 有关 Linux 命名空间的更多信息,请参阅 Linux 命名空间.
防止容器内部发生权限提升攻击的最佳方法是
配置您的容器应用程序以非特权用户身份运行。对于
必须在容器内以 root 用户身份运行的进程的容器,您
可以将该用户重新映射为 Docker 主机上权限较低的用户。映射后的
用户被分配了一系列 UID,这些 UID 在命名空间内作为从 0 到 65536 的正常
UID 起作用,但在主机本身上没有任何权限。
关于重新映射和从属用户及组 ID
重新映射本身由两个文件处理:/etc/subuid 和 /etc/subgid。
每个文件的工作方式相同,但一个关注用户 ID 范围,另一个关注组 ID 范围。考虑 /etc/subuid 中的以下条目:
testuser:231072:65536这意味着将 testuser 分配了一个从属用户 ID 范围 231072
以及接下来的 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
您可以使用 dockerd 标志启动 --userns-remap,或者按照以下步骤使用 daemon.json 配置文件来配置守护进程。
推荐使用 daemon.json 方法。如果您使用该标志,请参考以下命令:
$ dockerd --userns-remap="testuser:testuser"
编辑
/etc/docker/daemon.json。假设文件之前为空, 以下条目启用了userns-remap,使用名为testuser的用户和组。 您可以通过 ID 或名称来指定用户和组。仅当组名或 ID 与用户名或 ID 不同时,才需要指定组名或 ID。如果您同时提供用户和组名或 ID,请用冒号 (:) 字符分隔它们。以下格式均适用于该值,假设testuser的 UID 和 GID 是1001:testusertestuser:testuser10011001:1001testuser:10011001:testuser
{ "userns-remap": "testuser" }注意
要使用
dockremap用户并让 Docker 为您创建它, 请将值设置为default而不是testuser。保存文件并重启 Docker。
如果您正在使用
dockremap用户,请验证 Docker 是否使用id命令创建了它。$ 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,该值为当前最高分配值加上偏移量(在本例中为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=host 标记。
使用此标志时存在副作用:将不为此容器启用用户映射,但因为只读(镜像)层在容器之间共享,所以容器文件系统的所有者仍将被重新映射。
这意味着整个容器文件系统都属于在--userns-remap守护程序配置中指定的用户(上例中的231072)。这可能会导致容器内的程序出现意外行为。例如检查其Binaries是否属于用户sudo的0或具有setuid标志的Binaries。
用户命名空间的已知限制
具有用户命名空间启用的Docker守护程序不兼容以下标准Docker功能:
- 在主机上共享PID或NET命名空间(
--pid=host或--network=host)。 - 外部(卷或存储)驱动程序,这些驱动程序对外部系统不熟悉或无法使用
用户命名空间是高级功能,需要与其他功能进行协调。例如,如果从主机挂载卷,则必须提前安排文件所有权,以便需要对卷内容具有读或写访问权限。
虽然在用户命名空间中的容器进程中的 root 用户具有超级用户在容器内的预期的大多数特权,但 Linux 内核会根据这是一个用户命名空间化进程的内部知识应用限制。一个值得注意的限制是不允许使用 mknod 命令。当由 root 用户运行时,在容器内创建设备时会出现权限被拒绝。