使用用户命名空间隔离容器

Linux 命名空间为正在运行的进程提供了隔离,限制了它们对系统资源的访问,而进程本身并不知道这些限制。有关 Linux 命名空间的更多信息,请参阅 Linux 命名空间

防止容器内部发生权限提升攻击的最佳方法是,将容器的应用程序配置为以非特权用户身份运行。对于那些进程必须在容器内以 root 用户身份运行的容器,您可以将此用户重新映射到 Docker 主机上的一个权限较低的用户。该映射用户被分配了一个 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 主机上的资源(例如,绑定挂载到系统用户无法写入的文件系统区域)的情况下,会引入一些配置复杂性。从安全角度来看,最好避免这些情况。

先决条件

  1. 从属 UID 和 GID 范围必须与现有用户关联,即使这种关联是一个实现细节。该用户拥有 /var/lib/docker/ 下的命名空间存储目录。如果您不想使用现有用户,Docker 可以为您创建一个并使用它。如果您想使用现有的用户名或用户 ID,它必须已经存在。通常,这意味着相关条目需要存在于 /etc/passwd/etc/group 中,但如果您使用不同的身份验证后端,此要求可能会有所不同。

    要验证这一点,请使用 id 命令

    $ id testuser
    
    uid=1001(testuser) gid=1001(testuser) groups=1001(testuser)
    
  2. 在主机上处理命名空间重映射的方式是使用两个文件:/etc/subuid/etc/subgid。这些文件通常在您添加或删除用户或组时自动管理,但在某些发行版上,您可能需要手动管理这些文件。

    每个文件包含三个字段:用户的用户名或 ID,后跟一个起始 UID 或 GID(在命名空间内被视为 UID 或 GID 0)以及该用户可用的最大 UID 或 GID 数量。例如,给定以下条目:

    testuser:231072:65536

    这意味着由 testuser 启动的用户命名空间进程由主机 UID 231072(在命名空间内看起来像 UID 0)到 296607 (231072 + 65536 - 1) 所拥有。这些范围不应重叠,以确保命名空间进程无法访问彼此的命名空间。

    添加用户后,检查 /etc/subuid/etc/subgid,看您的用户是否在每个文件中都有一个条目。如果没有,您需要添加它,注意避免重叠。

    如果您想使用由 Docker 自动创建的 dockremap 用户,请在配置并重启 Docker 后检查这些文件中是否有 dockremap 条目。

  3. 如果 Docker 主机上有任何位置需要非特权用户写入,请相应地调整这些位置的权限。如果您想使用 Docker 自动创建的 dockremap 用户,情况也是如此,但在配置并重启 Docker 之后才能修改权限。

  4. 启用 userns-remap 会有效地屏蔽 /var/lib/docker/ 中现有的镜像和容器层以及其他 Docker 对象。这是因为 Docker 需要调整这些资源的所有权,并实际上将它们存储在 /var/lib/docker/ 内的一个子目录中。最好在一个新的 Docker 安装上启用此功能,而不是在现有的安装上。

    同样地,如果您禁用了 userns-remap,您将无法访问在它启用时创建的任何资源。

  5. 请查看用户命名空间的限制,以确保您的用例是可行的。

在守护进程上启用 userns-remap

您可以使用 --userns-remap 标志启动 dockerd,或者按照此过程使用 daemon.json 配置文件来配置守护进程。推荐使用 daemon.json 方法。如果您使用该标志,请使用以下命令作为模型

$ dockerd --userns-remap="testuser:testuser"
  1. 编辑 /etc/docker/daemon.json。假设该文件之前为空,以下条目使用名为 testuser 的用户和组来启用 userns-remap。您可以通过 ID 或名称来指定用户和组。仅当组名或 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。

  2. 如果您正在使用 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,该值是已分配的最高值加上偏移量(在本例中为 65536)。请注意不要让范围有任何重叠。

  3. 使用 docker image ls 命令验证以前的镜像是否不可用。输出应该为空。

  4. hello-world 镜像启动一个容器。

    $ docker run hello-world
    
  5. 验证 /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 不会使用它们。

为容器禁用命名空间重映射

如果您在守护进程上启用用户命名空间,则所有容器默认都会以启用用户命名空间的方式启动。在某些情况下,例如特权容器,您可能需要为特定容器禁用用户命名空间。有关其中一些限制,请参阅用户命名空间的已知限制

要为特定容器禁用用户命名空间,请向 docker container createdocker container rundocker container exec 命令添加 --userns=host 标志。

使用此标志会有一个副作用:用户重映射不会为该容器启用,但是,由于只读(镜像)层在容器之间共享,容器文件系统的所有权仍将被重映射。

这意味着整个容器文件系统将属于 --userns-remap 守护进程配置中指定的用户(在上面的示例中为 231072)。这可能会导致容器内程序的意外行为。例如 sudo(它会检查其二进制文件是否属于用户 0)或带有 setuid 标志的二进制文件。

用户命名空间的已知限制

以下标准的 Docker 功能与启用用户命名空间的 Docker 守护进程不兼容:

  • 与主机共享 PID 或 NET 命名空间 (--pid=host--network=host)。
  • 不了解或无法使用守护进程用户映射的外部(卷或存储)驱动程序。
  • docker run 上使用 --privileged 模式标志,而没有同时指定 --userns=host

用户命名空间是一项高级功能,需要与其他功能协调。例如,如果从主机挂载卷,则如果需要对卷内容进行读或写访问,文件所有权必须预先安排好。

虽然用户命名空间容器进程内的 root 用户拥有许多预期的超级用户权限,但 Linux 内核会根据其内部知识(这是一个用户命名空间进程)施加限制。一个显著的限制是无法使用 mknod 命令。当由 root 用户在容器内运行时,创建设备的权限将被拒绝。

© . This site is unofficial and not affiliated with Kubernetes or Docker Inc.