如何在Ubuntu20.04上使用Systemd沙箱化进程
作为 Write for DOnations 计划的一部分,作者选择了 Free and Open Source Fund 来接受捐赠。
介绍
沙盒是一种计算机安全技术,专注于将程序或进程与正常运行期间不需要与之交互的系统部分隔离开来。 当一个新程序启动时,它具有运行时用户的所有能力。 这些能力通常远远超过程序执行其功能所需的能力。 当不良行为者操纵程序以访问其一些未使用的能力来执行程序通常不会执行的操作时,这可能会导致安全问题。
沙盒的目的是准确确定程序需要哪些能力和资源,然后阻止其他所有内容。
系统管理工具套件 systemd 用于几乎所有主要的 Linux 发行版,以启动、停止和管理程序和进程。 它有许多沙盒选项,限制它启动的进程如何访问主机系统,使其更加安全。
本教程的目的不是创建尽可能严格的沙箱环境,而是使用推荐且易于启用的设置来使您的系统更加安全。
在本教程中,您将通过一个实际演示来演示如何在 Ubuntu 20.04 上使用 systemd 的沙盒技术来实现和测试这些技术的高效工作流程。 使用这些技术可以使在使用 systemd 的 Linux 系统上运行的任何进程更加安全。
先决条件
您将需要以下内容来开始本指南:
- 一个 Ubuntu 20.04 系统。
- 具有 sudo 权限的非 root 用户。 遵循 Initial Server Setup with Ubuntu 20.04 获取有关如何在 Ubuntu 20.04 上执行此操作的信息。
- 熟悉使用 systemd 管理进程。 请参阅 Systemd Essentials: Working with Services, Units, and the Journal 指南以了解基本知识。
- 对 systemd 单元文件有基本的了解会很有帮助。 有关详细信息,请参阅 Understanding Systemd Units and Unit Files 指南。
第 1 步 — 安装 lighttpd
在本教程中,我们将对 lighttpd Web 服务器进行沙箱处理。 选择 lighttpd 不是因为它比其他软件安全性低,而是因为它是一个具有单一功能的小程序,很容易被沙箱化。 这使其成为学习应用程序的绝佳选择。
让我们更新系统开始:
sudo apt update
在输入 y
之前检查将在您的系统上升级的软件包:
sudo apt upgrade
然后安装 lighttpd:
sudo apt install lighttpd
此安装过程将自动安装并启用用于 lighttpd 的 systemd 服务文件。 这将使 lighttpd 在系统重新启动时启动。
现在我们已经在我们的系统上安装并运行了 lighttpd,我们将熟悉我们在开始沙盒时将使用的 systemd 工具。
第 2 步 — 准备系统
在此步骤中,您将熟悉您将使用的 systemd 命令并准备系统以使您能够有效地沙箱化进程。
systemd 是一套工具的总称,每个工具都有不同的名称。 您将使用的两个是 systemctl
和 journalctl
。 systemctl
管理进程及其服务文件,而 journalctl
与系统日志交互。
systemd 使用 服务文件 来定义如何管理进程。 systemd 从文件系统中的多个位置加载这些文件。 以下命令将显示活动服务文件的位置并显示正在使用的任何覆盖:
sudo systemctl cat process.service
您需要将 process
替换为您正在处理的进程。 这里使用了 lighttpd:
sudo systemctl cat lighttpd.service
这是上一个命令的输出:
Output# /lib/systemd/system/lighttpd.service [Unit] Description=Lighttpd Daemon After=network-online.target [Service] Type=simple PIDFile=/run/lighttpd.pid ExecStartPre=/usr/sbin/lighttpd -tt -f /etc/lighttpd/lighttpd.conf ExecStart=/usr/sbin/lighttpd -D -f /etc/lighttpd/lighttpd.conf ExecReload=/bin/kill -USR1 $MAINPID Restart=on-failure [Install] WantedBy=multi-user.target
此输出显示服务文件位于 /lib/systemd/system/lighttpd.service
并且没有使用覆盖选项。 覆盖选项添加或修改基本服务文件。 您将使用覆盖来使用专用覆盖文件对 lighttpd 进行沙箱处理。
覆盖文件位于 /etc/systemd/system/process.service.d/override.conf
。 systemd 有一个专用的 edit
命令,它将在正确的位置创建一个覆盖文件,并在保存并退出编辑器后运行 systemctl daemon-reload
。 systemctl daemon-reload
指示 systemd 使用您编写的任何新配置。
systemd 编辑命令具有以下形式:
sudo systemctl edit process.service
当您运行此命令时,systemd 通常会选择您的默认 CLI 编辑器,但并非总是如此,您可能会发现自己处于 vi 甚至 ed 中。 您可以通过设置 SYSTEMD_EDITOR
shell 变量来配置 systemd 将使用哪个编辑器。
通过在 ~/.bashrc
文件中添加一行来设置这个 shell 变量。 使用文本编辑器打开此文件:
nano ~/.bashrc
并添加以下行:
~/.bashrc
export SYSTEMD_EDITOR=editor
将 editor
更改为您喜欢的 CLI 编辑器。 这是设置为使用 nano 编辑器的行:
~/.bashrc
export SYSTEMD_EDITOR=nano
在您注销并使用 echo
命令再次登录后确认这是设置的:
echo $SYSTEMD_EDITOR
此命令将打印您设置的编辑器的名称。
SYSTEMD_EDITOR
shell 变量仅设置在用户的 shell 中,而不是由 sudo
打开的 root 的 shell。 要将此变量传递给 root 的 shell,请使用 sudo -E
调用 systemctl edit
:
sudo -E systemctl edit process.service
最后的建议将通过向您显示您的更改导致的任何错误,使您的沙盒调试更容易。 这些错误将被系统日志记录下来,通过 journalctl 命令访问。
在沙盒化期间,您将进行许多更改,这些更改会破坏您尝试沙盒化的过程。 出于这个原因,最好打开第二个终端并将其专门用于跟踪系统日志。 这将节省重新打开系统日志的时间。
通过运行以下命令在第二个终端中跟踪系统日志:
sudo journalctl -f -u process.service
-f
:跟随或尾随系统日志,以便立即显示新行。-u process.service
:仅显示您正在沙盒的process
的日志行。
以下是您需要运行以仅打印 lighttpd 的错误:
sudo journalctl -f -u lighttpd.service
接下来,您将开始编辑 override.conf
文件并开始对 lighttpd 进行沙盒处理。
第 3 步 — 强制执行用户和组
在此步骤中,您将设置运行 lighttpd 的非 root 用户。
在其默认配置中,lighttpd 以 root 用户身份开始运行,然后更改为 www-data 用户和组。 这是一个问题,因为当 lighttpd 作为 root 运行时,它可以做任何 root 可以做的事情——这就是任何事情。
systemd 提供了以非 root 用户身份启动和运行进程的能力,从而避免了这个问题。
返回到您的第一个终端会话并通过运行以下命令开始编辑覆盖文件:
sudo -E systemctl edit lighttpd.service
现在,添加以下行:
lighttpd 覆盖文件
[Service] User=www-data Group=www-data
[Service]
:告诉 systemd 以下选项应该应用于[Service]
部分。User=www-data
:定义启动进程的用户。Group=www-data
:定义启动进程的组。
接下来,保存并退出编辑器并使用以下命令重新启动 lighttpd:
sudo systemctl restart lighttpd.service
lighttpd 将无法启动,因为它使用 root
权限将 PID 文件写入 root 拥有的位置。 www-data 用户无法写入 root 拥有的目录。 此问题会在您的第二个终端会话中出现的系统日志中指出:
journalctl error messageAug 29 11:37:35 systemd lighttpd[7097]: 2020-08-29 11:37:35: (server.c.1233) opening pid-file failed: /run/lighttpd.pid Permission denied
解决此问题遵循沙盒过程,即:
- 实施沙盒限制。
- 重新启动进程并检查错误。
- 修复任何错误。
接下来,您将解决 PID 文件问题,同时仍然强制执行您在本节中设置的用户和组限制。
第 4 步 — 管理 PID 文件
PID 文件是包含正在运行的进程的 PID 或进程标识号的文件。 像 lighttpd 这样的长期运行程序使用它们来管理自己的进程。 您在上一节中遇到的问题是 lighttpd 无法将其 PID 文件写入 /run/lighttpd.pid
,因为 /run/
由 root 拥有。
systemd 有针对这个问题的 RuntimeDirectory
选项,您将使用它为 lighttpd 提供一个可以将其 PID 文件写入的位置。
RuntimeDirectory
选项允许您在 /run/
下指定一个目录,当 systemd 启动 lighttpd 时,该目录将使用您在 Step 3 中设置的用户和组创建。 lighttpd 无需 root 的 权限即可将其 PID 写入此目录。
首先,使用您在 Step 3 中使用的相同命令打开并编辑覆盖文件:
sudo -E systemctl edit lighttpd.service
接下来,在您已添加到覆盖文件的两行下添加以下行:
lighttpd 覆盖文件
RuntimeDirectory=lighttpd
保存并退出编辑器。
您不要添加带有 RuntimeDirectory
的目录的完整路径,只需添加 /run/
下的目录名称。 在这种情况下,systemd 将创建的目录是 /run/lighttpd/
。
您现在需要配置 lighttpd 以将其 PID 文件写入新目录 /run/lighttpd/
而不是 /run/
。
使用文本编辑器打开 lighttpd 的配置文件:
sudo nano /etc/lighttpd/lighttpd.conf
更改以下行:
/etc/lighttpd/lighttpd.conf
server.pid-file = "/run/lighttpd.pid"
到:
/etc/lighttpd/lighttpd.conf
server.pid-file = "/run/lighttpd/lighttpd.pid"
保存并退出编辑器。
现在,重新启动 lighttpd:
sudo systemctl restart lighttpd.service
它不会启动,因为它无法执行需要 root 的 功能之一的操作。 接下来,您将解决这个新问题。
第五步——借用根的能力
系统日志中的以下行解释了停止 lighttpd 启动的问题:
journalctl error messageAug 29 12:07:22 systemd lighttpd[7220]: 2020-08-29 12:07:22: (network.c.311) can't bind to socket: 0.0.0.0:80 Permission denied
只有root才能打开1024
号以下的网口。 lighttpd 正在尝试打开 HTTP 端口 80
,但它被拒绝,因为 www-data 用户不能这样做。
这个问题通过给 lighttpd 进程提供一小部分 root 的 权力来解决——也就是说,打开 1024
下面的端口。
root的“能力”分为能力,称为能力。 root 用户拥有所有能力,因此可以做任何事情。 将 root 的 能力分解为能力意味着它们可以单独提供给非 root 进程。 这允许该进程执行需要完整 root 用户的操作,但普通用户现在可以使用 root 的 功能之一。
为进程提供 root 的 功能中的一项或多项的 systemd 选项是 AmbientCapabilities
选项。
打开覆盖文件:
sudo -E systemctl edit lighttpd.service
然后在您已经添加的行下添加以下行:
lighttpd 覆盖文件
AmbientCapabilities=CAP_NET_BIND_SERVICE
CAP_NET_BIND_SERVICE
能力允许进程打开 1024
下的端口。
保存并退出文件。
lighttpd 现在可以启动了。
您现在拥有一个工作的 lighttpd Web 服务器,您已将其设置为比其默认配置更安全。 systemd 提供了更多沙盒选项,您可以使用这些选项使您的目标进程更加安全。 我们将在以下部分中探讨其中的一些内容。
在下一步中,您将限制 lighttpd 在文件系统中可以访问的内容。
第 6 步 — 锁定文件系统
lighttpd 进程以 www-data 用户身份运行,因此可以访问系统上 www-data 有权读取和写入的任何文件。 在 www-data 的情况下,这不是很多,但仍然超过了 lighttpd 的需要。
第一个也是最简单的沙盒设置是 ProtectHome
选项。 此选项停止进程读取或写入 /home/
下的任何内容。 lighttpd 不需要访问 /home/
下的任何内容,因此实施这将保护您的所有私人文件而不影响 lighttpd。
打开覆盖文件:
sudo -E systemctl edit lighttpd.service
然后在文件底部添加以下行:
lighttpd 覆盖文件
ProtectHome=true
保存并退出编辑器,然后重新启动 lighttpd 以使用以下命令检查它是否按预期工作:
sudo systemctl restart lighttpd.service
您已经保护了 /home/
,但仍然保留了文件系统的其余部分。 这可以通过 ProtectSystem
选项来处理,该选项会阻止进程写入文件系统的某些部分。
ProtectSystem
选项具有三个设置,可提供更高级别的保护。 它们如下:
true
:将以下目录设置为只读:
full
:将以下目录设置为只读:
strict
:将以下目录设置为只读:
更高级别的保护更安全,因此通过在覆盖文件中添加以下行将 ProtectSystem
选项设置为 strict
:
lighttpd 覆盖文件
ProtectSystem=strict
保存并退出编辑器并使用以下命令重新启动 lighttpd:
sudo systemctl restart lighttpd.service
lighttpd 将无法启动,因为它需要将其日志文件写入 /var/log/lighttpd/
并且 strict
设置禁止这样做。 系统日志中的以下行显示了问题:
journalctl error messageAug 29 12:44:41 systemd lighttpd[7417]: 2020-08-29 12:44:41: (server.c.752) opening errorlog '/var/log/lighttpd/error.log' failed: Read-only file system
这个问题是 systemd 使用 LogsDirectory
选项预料到的。 它采用 /var/log/
下允许进程将其日志写入的目录的名称。
在您的第一个终端会话中再次打开覆盖文件:
sudo -E systemctl edit lighttpd.service
lighttpd 日志目录是 /var/log/lighttpd/
所以将以下行添加到覆盖文件的底部:
lighttpd 覆盖文件
LogsDirectory=lighttpd
保存并退出编辑器并重新启动 lighttpd:
sudo systemctl restart lighttpd.service
lighttpd 现在将能够启动和运行。
注意: 如果您正在沙箱化除 lighttpd 以外的进程,并希望允许您的进程对 /var/log/
之外的特定目录进行写访问,请使用 ReadWritePaths 选项。
在下一步中,您将通过限制允许进行的系统调用来限制 lighttpd 进程与系统其余部分的交互方式。
第 7 步 — 限制系统调用
系统调用 是程序向内核请求某些内容的方式。 系统调用的数量非常多,包括读取、写入和删除文件等操作,以及挂载文件系统、生成进程、重新启动等硬件相关任务。
systemd 创建了一组系统调用,这些系统调用(如 lighttpd)通常使用,并且排除了它们不使用的调用。 阻塞的系统调用是诸如挂载文件系统和重新启动系统之类的事情,而 lighttpd 永远不需要这样做。
首先,打开覆盖文件:
sudo -E systemctl edit lighttpd.service
将以下行添加到文件底部以使用 SystemCallFilter
选项设置 @system-service
组:
lighttpd 覆盖文件
SystemCallFilter=@system-service
保存并退出编辑器并重新启动 lighttpd:
sudo systemctl restart lighttpd.service
在下一部分中,您将应用其余推荐的沙盒选项。
第 8 步 - 实施更多选项
systemd 文档建议为 lighttpd 等长时间运行的联网进程启用以下选项。 这些设置都是可选的,但每一个都使您的沙盒过程更加安全,如果可以的话,应该使用。
您应该一次启用这些选项,并在每个选项之后重新启动您的进程。 如果您一次将它们全部添加,则调试问题将更加困难。
以下推荐的选项附有对它们作用的简要说明。 将这些行添加到您已经添加的行下的覆盖文件中:
lighttpd 覆盖文件
NoNewPrivileges=true
此选项会阻止沙盒进程及其任何子进程获取新权限。
lighttpd 覆盖文件
ProtectKernelTunables=true
此选项阻止进程更改任何内核变量。
lighttpd 覆盖文件
ProtectKernelModules=true
此选项停止加载或卸载内核模块的进程。
lighttpd 覆盖文件
ProtectKernelLogs=true
此选项阻止进程直接读取和写入内核日志。 它必须使用系统日志应用程序来记录任何日志消息。
lighttpd 覆盖文件
ProtectControlGroups=true
此选项会阻止进程修改系统控制组。
lighttpd 覆盖文件
MemoryDenyWriteExecute=true
此选项阻止进程修改系统内存中运行的任何代码。
lighttpd 覆盖文件
RestrictSUIDSGID=true
此选项停止进程在文件或目录上设置 set-user-ID (SUID) 或 set-group-ID (SGID)。 这种能力可以被滥用来提升特权。
lighttpd 覆盖文件
KeyringMode=private
此选项阻止进程访问以同一用户身份运行的其他进程的内核密钥环。
lighttpd 覆盖文件
ProtectClock=true
此选项阻止进程更改硬件和软件系统时钟。
lighttpd 覆盖文件
RestrictRealtime=true
此选项阻止进程启用可被滥用以使 CPU 过载的实时调度。
lighttpd 覆盖文件
PrivateDevices=true
此选项阻止进程访问连接到系统的物理设备,例如存储设备或 USB 设备。
lighttpd 覆盖文件
PrivateTmp=true
此选项强制进程使用私有 /tmp/
和 /var/tmp/
目录。 这会阻止进程读取存储在这些共享系统目录中的其他程序的临时文件。
lighttpd 覆盖文件
ProtectHostname=true
此选项阻止进程更改系统的主机名。
您已沙盒化的进程现在比其默认配置安全得多。 您现在可以采用这些技术并将它们用于您需要在 Linux 系统上保护的任何其他进程。
结论
在本文中,您使用 systemd 沙盒选项使 lighttpd 程序更加安全。 您可以将这些技术与 systemd 管理的任何进程一起使用,从而继续提高系统的安全性。
沙盒和其他安全选项的完整列表可以在 systemd 的 在线文档 中找到。 此外,在 DigitalOcean 社区上查看更多 安全主题。