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

Linux命名空间为运行中的进程提供了隔离,限制了它们对系统资源的访问,而运行中的进程并不会意识到这些限制。有关Linux命名空间的更多信息,请参见Linux命名空间

防止容器内权限提升攻击的最佳方法是配置容器的应用程序以非特权用户身份运行。对于必须在容器内以root用户身份运行的进程的容器,您可以将此用户重新映射到Docker主机上的较低权限用户。映射的用户被分配了一系列UID,这些UID在命名空间内作为从0到65536的正常UID运行,但在主机本身上没有任何权限。

关于重新映射和从属用户及组ID

重新映射本身由两个文件处理:/etc/subuid/etc/subgid。 每个文件的工作方式相同,但一个涉及用户ID范围,另一个涉及组ID范围。考虑以下在 /etc/subuid 中的条目:

testuser:231072:65536

这意味着 testuser 被分配了一个从 231072 开始的子用户ID范围,以及接下来的65536个连续整数。UID 231072 在命名空间内(在这种情况下,是在容器内)被映射为 UID 0root)。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标志启动dockerd,或者按照以下步骤使用daemon.json配置文件来配置守护进程。 推荐使用daemon.json方法。如果你使用标志,请使用以下命令作为模型:

$ dockerd --userns-remap="testuser:testuser"
  1. 编辑 /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。

  2. 如果您正在使用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)。注意不要允许任何范围重叠。

  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不会使用它们。

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

如果您在守护进程上启用了用户命名空间,默认情况下所有容器都会启用用户命名空间。在某些情况下,例如特权容器,您可能需要为特定容器禁用用户命名空间。有关这些限制的更多信息,请参见用户命名空间的已知限制

要禁用特定容器的用户命名空间,请将--userns=host标志添加到docker container createdocker container rundocker 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用户运行时,容器内创建设备的权限被拒绝。