拒绝 root 权限(在容器中)

即使是聪明的管理员也可能做出错误的决定。
418 位读者喜欢这篇文章。
tree roots breaking through brick wall

Rikki Endsley。CC BY-SA 4.0

我经常被问及用于控制系统上容器进程行为的不同安全措施。其中大部分内容我在 Opensource.com 之前的文章中已经介绍过

以上几乎所有文章都关于控制系统上的特权进程在容器中可以做什么。例如:如何在容器中以 root 身份运行,又不允许其逃逸?

用户命名空间完全是为了在容器中运行特权进程,这样即使它们逃逸,在容器外部也不再具有特权。例如,在容器中我的 UID 是 0(零),但在容器外部我的 UID 是 5000。由于文件系统支持不足的问题,用户命名空间并没有像人们吹捧的那样成为万灵药。直到现在。

OpenShift 是红帽的容器平台,构建于 Kubernetes、Red Hat Enterprise Linux 和 OCI 容器之上,它具有出色的安全功能:默认情况下,不允许任何容器以 root 身份运行。管理员可以覆盖此设置,否则所有用户容器都将在不以 root 身份运行的情况下运行。这在多租户 OpenShift Kubernetes 集群中尤为重要,在这些集群中,单个集群可能为多个应用程序和多个开发团队提供服务。为每个集群运行单独的集群并不总是实际或可取的。遗憾的是,关于 OpenShift 最大的抱怨之一是用户无法轻松运行 docker.io 上提供的所有社区容器镜像。这是因为当今世界上绝大多数容器镜像都需要 root 权限。

为什么所有这些镜像都需要 root 权限?

如果您实际检查在系统上成为 root 用户的理由,它们其实非常有限。

修改主机系统

  • 在系统上成为 root 用户的一个主要原因是更改系统的默认设置,例如修改内核的配置。
  • 在 Fedora、CentOS 和 Red Hat Enterprise Linux 中,我们有系统容器的概念,这是一种特权容器,可以使用 atomic 命令安装在系统上。它们可以以完全特权模式运行,并允许修改系统以及内核。在系统容器的情况下,我们将容器镜像用作内容交付系统,而不是真正寻求容器隔离。系统容器更适用于核心操作系统主机服务,而不是大多数容器运行的用户应用程序服务。
  • 在应用程序容器中,我们几乎从不希望容器内部的进程修改内核。默认情况下,这绝对不是必需的。

Unix/Linux 传统

  • 操作系统软件供应商和开发人员早就知道以 root 身份运行进程是危险的,因此内核添加了许多 Linux 功能,允许进程以 root 身份启动,然后尽快放弃特权。大多数 UID/GID 功能允许诸如 Web 服务之类的进程以 root 身份启动,然后变为非 root 用户。这样做是为了绑定到 1024 以下的端口(稍后会详细介绍)。
  • 容器运行时可以首先以非 root 用户身份启动应用程序。事实上,systemd 也可以做到这一点,但是过去 20 年中构建的大多数软件都假定它是以 root 身份启动并放弃特权的。

绑定到端口 < 1024

  • 早在 20 世纪 60 年代和 70 年代,计算机还很少的时候,非特权用户无法绑定到 1024 以下的网络端口被认为是一种安全功能。因为只有管理员才能做到这一点,所以您可以信任监听这些端口的应用程序。端口 > 1024 可以由系统上的任何用户绑定,因此它们不可信。这种安全优势已基本消失,但我们仍然受此限制。
  • 此限制的最大问题是 Web 服务,人们喜欢让他们的 Web 服务器监听 80 端口。这意味着 apache 或 nginx 最初以 root 身份运行的主要原因是为了它们可以绑定到 80 端口,然后变为非 root 用户。
  • 容器运行时,使用端口转发,可以解决这个问题。您可以设置容器监听任何网络端口,然后让容器运行时将该端口映射到主机上的 80 端口。

在此命令中,podman 运行时将在您的机器上运行一个 apache_unpriv 容器,监听主机上的 80 端口,而容器内部的 Apache 进程永远不是 root 用户,而是以 apache 用户身份启动,并被告知监听 8080 端口。

podman run -d -p 80:8080 -u apache apache_unpriv

或者

docker run -d -p 80:8080 -u apache apache_unpriv

将软件安装到容器镜像中

  • 当 Docker 引入使用 docker build 构建容器时,容器中的内容只是用于发行版的标准打包软件。该软件通常通过 rpm 包或 Debian 包提供。好吧,发行版打包软件以便由 root 用户安装。软件包期望能够执行诸如通过添加用户来操作 /etc/passwd 文件之类的操作,以及使用不同的 UID/GID 将内容放到文件系统上。许多软件还期望通过 init 系统 (systemd) 启动,并以 root 身份启动,然后在启动后放弃特权。
  • 遗憾的是,在容器革命五年后,这仍然是现状。几年前,我曾尝试让 httpd 包知道它何时由非 root 用户安装,并拥有不同的配置。但我在这方面失败了。我们需要让打包者和软件包管理系统开始理解这种差异,然后我们才能创建在不需要 root 权限的情况下运行的优秀容器。
  • 我们现在可以做的一件事来解决这个问题是将构建系统与安装系统分开。我对 #nobigfatdaemons 的一个问题是 Docker 守护进程导致容器在运行容器时与构建容器镜像时具有相同的特权。
  • 如果我们更改系统或使用不同的工具,例如使用 Buildah 构建具有较宽松约束的容器,并使用 CRI-O/Kubernetes/OpenShift 在生产环境中运行容器,那么我们可以使用提升的特权进行构建,但随后以更严格的约束运行容器,或者希望以非 root 用户身份运行。

底线

您在容器中运行的几乎所有软件都不需要 root 权限。您的 Web 应用程序、数据库、负载均衡器、数值计算程序等永远不需要以 root 身份运行。当我们让人们开始构建完全不需要 root 权限的容器镜像,并让其他人基于非特权容器镜像构建他们的镜像时,我们将看到容器安全性的巨大飞跃。

关于在容器内部以 root 身份运行,仍然有很多教育工作需要做。没有教育,聪明的管理员也可能做出错误的决定。

User profile image.
Daniel Walsh 在计算机安全领域工作了近 30 年。Dan 于 2001 年 8 月加入红帽。

5 条评论

软件包期望能够执行诸如通过添加用户来操作 /etc/passwd 文件之类的操作,以及使用不同的 UID/GID 将内容放到文件系统上。许多软件还期望通过 init 系统 (systemd) 启动,并以 root 身份启动,然后在启动后放弃特权。 http://www.animeflvapkapp.com/

这篇文章对于了解容器的 root 安全性很有用。我了解了容器在 root 行为下与内核一起工作的方式。

我认为这篇文章听起来合乎逻辑,但得出了错误的结论。诚然,人们在讨论“需要” root 访问权限时首先想到的是 1024 以下的端口号和修改内核设置。但这篇文章不加批判地接受了“用户应具有完成其工作所需的最低访问权限”的观点。这个想法或最佳实践在今天被广泛接受,但其含义却很少被讨论。这种想法的增加可能会并且确实会显着增加生产故障和安全事件的风险。

原因如下:当彻底调查生产故障时,通常有很多促成因素——通常是四个、五个或更多。我的经验是,人为的无知通常至少是其中两到三个因素的一部分。当“系统管理员”成为一种公认的职业道路时,程序员拥有 root 权限变得越来越少——所有这些都出于非常合乎逻辑的良好意图。这样做的一个意想不到的后果是,开发人员作为一个群体,对他们的软件在生产环境中的实际行为了解较少。Linux 已经发展成为一个更易于观察的操作系统,具有复杂的跟踪工具——其中许多工具需要 root 权限。与此同时,今天的多插槽、多核硬件功能更强大,也更复杂。然而,随着硬件变得越来越强大,性能和安全性似乎仍然停滞不前。为了让应用软件充分利用,渴望开发的开发人员需要了解他们的代码如何与机器交互——虚拟机、容器和 Java(3 个巨大的积极发展)的普及都掩盖了这一点。不幸的是,遵循安全最佳实践可能会导致我们进入不太安全的环境,在那里没有人了解整个系统。

为了进一步说明……以下是我所说的开发人员了解系统如何使用资源的意思
“应用程序 X 有超过 100 个线程。其中三个是热线程,在一个核心上旋转——市场数据事件处理程序和两个工作线程。其余大部分线程是每个连接线程,大部分时间都是冷线程。然后有四个暖线程——logger、persister……我们希望市场数据事件处理程序始终在插槽 1 上运行,因为市场数据 NIC 在第二个 PCI-X 插槽上,并且该线程可以保持 NUMA 本地。此应用程序是延迟比吞吐量更重要的应用程序,因此我们不使用默认的 NIC 中断合并设置……”

验证这些前提条件是否成立的最佳方法是使用 ethtool、perf-test(两者都需要 root 权限)。因此,问题不应该是“我们需要 root 访问权限吗?”,而应该是“拥有 root 访问权限是否有意义?” 我看到由于开发人员和 SA 不了解他们的系统如何运行而造成的损害,远远超过了在 root shell 中偶尔出现的人为错误造成的损害。我认为应该审核 root 访问权限——了解主机最好的学习经验之一就是简单地执行
sudo -s
history | more

回复 by Peter Booth

为了进一步说明……以下是我所说的开发人员了解系统如何使用资源的意思
“应用程序 X 有超过 100 个线程。其中三个是热线程,在一个核心上旋转——市场数据事件处理程序和两个工作线程。其余大部分线程是每个连接线程,大部分时间都是冷线程。然后有四个暖线程——logger、persister……我们希望市场数据事件处理程序始终在插槽 1 上运行,因为市场数据 NIC 在第二个 PCI-X 插槽上,并且该线程可以保持 NUMA 本地。此应用程序是延迟比吞吐量更重要的应用程序,因此我们不使用默认的 NIC 中断合并设置……”

验证这些前提条件是否成立的最佳方法是使用 ethtool、perf-test(两者都需要 root 权限)。因此,问题不应该是“我们需要 root 访问权限吗?”,而应该是“拥有 root 访问权限是否有意义?” 我看到由于开发人员和 SA 不了解他们的系统如何运行而造成的损害,远远超过了在 root shell 中偶尔出现的人为错误造成的损害。我认为应该审核 root 访问权限——了解主机最好的学习经验之一就是简单地执行
sudo -s
history | more

回复 by Peter Booth

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.