【容器安全系列Ⅳ】- 深入理解Linux Cgroup

图片

       当主机上运行多个进程时,管理系统资源可能是一个挑战。单个行为异常的程序可能会消耗所有可用资源,从而导致整个系统崩溃。为了解决这个问题,Linux 依靠控制组 (cgroups) 来管理每个进程对资源(如 CPU 和内存)的访问。

       Docker 和其他容器化工具使用 cgroups 来限制容器可以使用的资源,这有助于避免相互干扰的问题。这在使用 Kubernetes 时特别有用,因为来自多个应用程序的工作负载经常在同一主机上共享资源。

       在这篇文章中,我们将仔细研究 cgroups,并探讨它们如何确保每个进程都能访问高效运行所需的资源。我们还将介绍 cgroups 的几个安全方面,包括如何使用 cgroups 来降低拒绝服务攻击的风险,并管理容器对主机上特定设备的访问。

cgroups v1 和 v2

       值得注意的是,根据Linux发行版和版本,在给定的主机上可能会使用两个版本的cgroups。与原始实现相比,Cgroup v2 提供了管理优势,并且是某些容器功能(如非root容器)所必需的。

       Cgroup v2 最初是在2016年 4.5 版本的 Linux 内核中引入的,但直到最近才成为某些发行版的默认版本。要确定主机上运行的版本,可以验证挂载的文件系统。例如,如果您在 Ubuntu 20.04 主机上执行该命令 mount | grep cgroup ,您将看到一行展示“cgroup2”,另几行展示“cgroup”,这表示两个系统都已安装。

图片

cgroup 挂载列表 - Ubuntu 20.04

       但是,如果您在 Ubuntu 22.04 系统上运行相同的命令,则只会看到 cgroup v2。

图片

cgroup 挂载列表 - Ubuntu 22.04

       由于 cgroup v2 是最近 Linux 发行版中使用的版本,因此我们将在示例的其余部分重点介绍 v2。

Cgroups 基础知识

       有几种方法可以检查 Linux 主机上使用的 cgroup。一种选择是使用 /proc 文件系统来查看用于特定进程的 cgroup(例如,正在运行的用户的 bash shell)。

       执行命令 cat /proc/[PID]/cgroup 将显示进程所属的 cgroup “slice” 和 “scope”(slice 和 scopes 用于组织 cgroup 和进程)。在以下示例中,我们首先用 ps -fC bash 获取 shell 的进程 ID。然后,我们使用该进程 ID 来发现它使用的 cgroup 会话。

图片

查找 bash shell 的 cgroup 作用域和切片

       要查看可以为该进程修改的可用资源,您可以查看 /sys/ 文件系统,它对应于我们从上一个命令中获得的信息(例如:sys/fs/cgroup/user.slice/user-1000.slice/session-4.scope)

图片

显示给定 cgroup 范围的资源

       此手动过程可能非常耗时,因此您可以利用更便捷的工具,以更有条理的方式显示 cgroup 信息。例如,systemd-cgls 可以显示主机上不同 cgroup 作用域的分层视图。

图片

使用 systemd-cgls 获取 cgroup 信息的分层视图

       此外,cgroup-utils 软件包中的 lscgroup 实用程序可用于检查 cgroup 信息。

图片

使用 lscgroup 显示 cgroup 信息

使用 cgroups 限制资源

       现在我们已经了解了如何查看 cgroup 信息,下一步是探索如何使用 cgroups 来限制进程可用的资源,这有助于缓解拒绝服务风险。为了证明这一点,我们将使用 stress 工具来模拟攻击者或行为不端的应用程序消耗我们主机上的所有 CPU。

       在 Docker 容器中,我们可以利用命令 stress -c 2  ,它将启动两个进程,总共消耗 2 个 CPU 内核。然后,通过在另一个窗口中执行命令 top ,我们可以验证对主机 CPU 的影响。

图片

使用压力工具消耗 CPU 资源

       Docker 提供了各种选项来限制容器可以使用的 CPU 时间量,但最简单的是--cpus标志,它允许您指定可以使用的 CPU 的十进制数。在幕后,Docker 利用 cgroups 来强制执行此限制。

       例如,执行 docker run --name stress --cpus 0.5 -it stressimage /bin/bash 会将容器限制为 0.5 个 CPU。使用上一个容器中的相同 stress 命令后,我们可以在 top 中观察到此限制的结果。 

图片

CPU 资源使用率受 cgroups 限制

       现在,stress 进程不再能够利用两个 CPU 内核,而是限制为 0.5 个 CPU(每个进程占内核的 25%)。

       我们还可以通过检查底层文件系统来观察 Docker 实现的 cgroup 限制的细节。为此,我们首先使用 docker inspect -f '{{.State.Pid}}' stress 获取 Docker 容器的进程 ID。然后我们可以查找此过程的 cgroup 信息。容器的 cgroup 目录包含一个 cpu.max 文件,其值为 50000 100000 ,相当于 0.5 个 CPU。默认情况下,Docker 不限制进程的 CPU 使用率,因此文件将显示值 max 100000 。如果攻击者有权访问此容器,则可以使用主机上的所有 CPU 资源(例如,挖掘加密货币)。

使用 cgroups 限制fork炸弹

       Linux 系统上常见的拒绝服务攻击称为frok炸弹,当攻击者生成大量进程,最终耗尽系统资源时,就会发生这种攻击。默认情况下,容器(和其他 Linux 进程)在它们可以生成多少个新进程方面不受限制,这意味着任何进程都可以创建fork炸弹。

       Cgroup 能够限制可以生成的进程数量,从而有效地保护主机免受fork炸弹攻击。我们可以使用 docker run 的--pids-limit 参数来演示这一点,这实质上将设置适当的 cgroup。

       要了解其工作原理,我们可以使用 docker run -it --pids-limit 10 ubuntu:22.04 /bin/bash 命令启动容器,这会将容器限制为最多 10 个进程。然后我们可以使用命令执行 bash fork 炸弹 :(){ :|: & };: 

图片

Docker PID 限制在fork炸弹中生效

       很快,容器达到 10 个进程的限制,并显示错误。但是,底层主机将保持响应,从而防止拒绝服务攻击。

使用 cgroups 控制设备访问

       cgroups 的另一个与安全相关的方面是,它们可用于控制对设备的访问。容器提供对主机上一系列设备的访问,详见 runc 的允许设备列表,并且可以利用 Docker 的功能(使用 cgroups)将其他设备添加到该列表中。这允许您向特定容器授予对硬件(例如音频设备)的访问权限。

       您可以将 --device 选项添加到命令 docker run 中以授予对设备的访问权限。例如,执行 docker run -d --rm --device /dev/dm-0 --name webdevice nginx 会生成一个有权访问/dev/dm-0设备的容器。

       与 CPU 或内存等其他资源相比,Linux 工具没有提供那么多的功能来检查 cgroup 对设备的访问。在 cgroup v2 中,一般使用 eBPF 程序用于管理对设备的访问,因此标准工具将不起作用。所以, bpftool 是必需的。您可以使用此程序列出与任何给定 cgroup 关联的 eBPF 程序,从而提供容器对主机访问的一些可见性,尽管不是很详细。

结论

       控制共享资源是确保多个容器可以有效地共享单个服务器的关键。在这篇文章中,我们介绍了 cgroup,这是 Linux 系统用于实现此控制的主要机制。我们还演示了如何利用 cgroup 来帮助缓解常见的拒绝服务攻击,并管理对连接到主机的特定设备的访问。

       到目前为止,我们检查的所有安全机制都在系统上的 root 用户的控制之下。但是,有时我们想要限制 root 用户的操作,那又该怎么办呢?在下一篇文章中,我们将探讨 SELinux 和 AppArmor 等强制访问控制(MAC)系统如何实现这一目标。


  • 18
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值