为什么 Docker 容器中的进程不应该以 root 身份运行

已发表: 2022-08-19

显示 Docker 徽标的图形

Docker 容器中的进程不应以 root 身份运行。 以您指定为 Dockerfile 的一部分或使用 docker docker run的非 root 用户身份运行应用程序会更安全。 这通过减少对容器中任何威胁的攻击面来最大限度地降低风险。

在本文中,您将了解以 root 身份运行容器化应用程序的危险。 您还将看到如何创建一个非 root 用户并在不可能的情况下设置命名空间。

为什么以 root 身份运行是危险的?

默认情况下,容器以 root 身份运行。 Docker 守护程序在您的主机上以 root 身份执行,并且运行的容器也将以 root 身份运行。

虽然看起来容器内的 root 是一个独立的用户,但实际上它与您主机上的 root 帐户相同。 分离仅由 Docker 的容器隔离机制提供。 没有强大的物理边界; 您的容器的另一个进程由您的主机内核上的 root 用户运行。 这意味着您的应用程序、Docker 运行时或 Linux 内核中的漏洞可能允许攻击者突破容器并在您的计算机上执行 root 特权操作。

有一些内置的保护措施可以降低发生这种情况的风险。 容器内的根是非特权的并且具有受限的能力。 这会阻止容器使用系统管理命令,除非您在启动容器时手动添加功能或使用特权模式。

尽管有这种缓解措施,但允许应用程序以 root 身份运行仍然存在危险。 就像您在传统环境中限制使用 root 一样,在容器中不必要地使用它是不明智的。 您正在提供一个特权过高的环境,以便在发生违规事件时为攻击者提供更多立足点。

以非 root 用户身份运行容器化应用程序

容器化应用程序以普通用户身份运行是最佳实践。 大多数软件不需要 root 访问权限,因此更改用户提供了针对容器突破的即时防御层。

作为 Dockerfile 的最后阶段之一,您应该创建一个新的用户帐户。 您可以使用USER指令实现此目的:

 来自基本映像:最新
运行 apt install 演示包
用户演示用户:演示组
入口点 [“演示二进制”]

从此镜像启动的容器将以demo-user身份运行。 用户将是demo-group组的成员。 如果您不需要用户在组中,则可以省略组名:

 USER 演示用户

您可以指定用户 ID (UID) 和组 ID (GID) 而不是名称:

 用户 950:950

分配已知的 UID 和 GID 通常是最安全的方法。 它可以防止容器中的用户被映射到特权过高的主机帐户。

USER通常被指定为 Dockerfile 中的倒数第二个阶段。 这意味着您仍然可以在映像构建的早期运行需要 root 的操作。 上面示例中的apt install指令对 root 有合法的需求。 如果USER指令放在它上面, apt将作为缺少必要权限的demo-user运行。 由于 Dockerfile 说明仅适用于映像构建,而不适用于运行容器,因此在 Dockerfile 中稍后更改用户是安全的。

更改您的容器运行的用户可能需要您更新它访问的文件和文件夹的权限。 在您的应用程序将使用的任何路径上设置所有权:

 复制初始配置.yaml /data/config.yaml

用户演示用户:演示组
运行 chown demo-user:demo-group /data

在此示例中, /data目录需要由demo-user拥有,以便应用程序可以更改其配置文件。 较早的COPY语句将以 root 身份复制文件。 使用带有copy--chown标志可以使用速记:

 复制 --chown=demo-user:demo-group initial-config.yaml /data/config.yaml

启动容器时更改用户

虽然您可以在自己的 Dockerfile 中轻松更改用户,但许多第三方应用程序仍以 root 身份运行。 您可以通过在每次调用docker run时设置--user标志来降低与使用这些相关的风险。 这将覆盖图像的 Dockerfile 中的用户集。

 $ docker run -d --user demo-user:demo-group demo-image:latest
$ docker run -d --user demo-user demo-image:latest
$ docker run -d --user 950:950 demo-image:latest

--user标志以指定用户身份运行容器的进程。 它不如 Dockerfile USER指令安全,因为您必须将它单独应用于每个docker run命令。 对于经常使用的图像,一个更好的选择是创建自己的衍生图像,可以设置一个新的用户帐户:

 FROM 以 root 身份运行的映像:最新
USER 演示用户
$码头工人建造。 -t image-that-now-runs-as-non-root:latest

更改第三方映像的用户可能会导致问题:如果容器希望以 root 身份运行,或者需要访问 root 拥有的文件系统路径,则在使用应用程序时会看到错误。 您可以尝试手动更改导致问题的路径的权限。 或者,检查供应商是否支持使用非特权用户帐户运行应用程序的方法。

处理必须以 root 身份运行的应用程序

用户命名空间是一种处理需要一些 root 权限的应用程序的技术。 它允许您将容器内的 root 映射到主机上的非 root 用户。 容器内的模拟 root 具有它需要的权限,但突破不会提供对主机的 root 访问权限。

通过向/etc/docker/daemon.json文件添加userns-remap字段来激活命名空间重新映射:

 {
    “用户重新映射”:“默认”
}

使用default作为userns-remap的值会指示 Docker 在您的主机上自动创建一个名为dockremap的新用户。 容器中的根将映射回主机上的dockremap 。 您可以选择使用 UID/GID 或用户名/组名称组合指定现有用户和组:

 {
    “用户重新映射”:“演示用户”
}

应用更改后重新启动 Docker 守护程序:

 $ sudo 服务码头重新启动

如果您使用的是nsuser-remap: default ,则dockremap用户现在应该存在于您的主机上:

 $ id 码头地图

uid=140(dockremap) gid=119(dockremap) 组=119(dockremap)

用户还应该出现在/etc/subuid/etc/subgid从属 ID 文件中:

 $码头重映射:231500:65535

从 231500 开始,该用户被分配了 65,535 个从属 ID。在用户命名空间中,ID 231500映射到0 ,使其成为容器中的 root 用户。 作为一个高编号的 UID,231500 对主机没有特权,因此容器突围攻击将无法造成如此大的破坏。

除非您使用docker run --userns=host选择退出,否则您启动的所有容器都将使用重新映射的用户命名空间运行。 该机制通过在/var/lib/docker内创建由命名空间用户的从属 UID 和 GID 拥有的命名空间目录来工作:

 $ sudo ls -l /var/lib/docker/231500.231500

共 14 个
drwx------ 5 231500 231500 13 Jul 22 19:00 aufs
drwx------ 3 231500 231500 13 Jul 22 19:00 容器
...

用户命名空间是增加容器隔离、避免突破和保持与需要 root 权限的应用程序兼容性的有效方法。 但是有一些权衡:该功能在新的 Docker 实例上效果最好,从主机安装的卷必须调整其权限,并且一些外部存储驱动程序根本不支持用户映射。 在采用此选项之前,您应该查看文档。

概括

以 root 身份运行容器化应用程序存在安全风险。 虽然容易被忽视,但容器提供的隔离还不足以将内核用户与容器用户完全分开。 容器中的 root 与主机上的 root 相同,因此成功的妥协可以提供对您机器的控制。

作为图像作者,您应该在 Dockerfile 中包含USER指令,这样您的应用程序就可以在没有 root 的情况下运行。 映像用户可以使用docker run --user覆盖它以分配特定的 UID 和 GID。 这有助于缓解映像通常使用 root 的情况。

您可以通过使用--cap-drop=ALL从容器中删除所有功能来进一步加强安全性,然后使用--cap-add标志将所需的功能列入白名单。 结合这些技术,您的应用程序将以非 root 用户身份运行,并具有所需的最低权限集,从而改善您的安全状况。