systemd 服务沙箱加固实战:进程权限最小化配置

适用场景

服务器上同时运行 Nginx、Redis、Python 应用等多个服务时,systemd 服务加固的目标是:假设某个服务被漏洞攻破,将攻击者的活动范围锁死在该服务的沙箱内,无法读取 /etc/passwd 以外的系统配置、无法篡改系统目录、无法提权。相比 AppArmor、SELinux 等强制访问控制方案,systemd 沙箱无需额外软件、按服务粒度逐个启用,是 Linux 默认自带的最实用的纵深防御手段。

前置条件

  • 系统使用 systemd 管理(CentOS 7+/Rocky/AlmaLinux、Ubuntu 16.04+、Debian 8+)
  • 目标服务以 systemd unit 方式启动(Nginx、Redis 等多数发行版默认如此)
  • systemd 版本 232 及以上(支持本文全部指令;CentOS 7 为 219,部分指令无效,文末 FAQ 说明)
  • root 或 sudo 权限

原理说明

systemd 服务加固利用内核的 mount namespace、capabilities、seccomp 等机制,在服务启动瞬间对进程做收权:文件系统只读挂载、/tmp 私有化、丢弃 root 特权能力、限制可用系统调用。这些指令写在 unit 文件中,由 PID 1 在 fork/exec 时直接应用,攻击者获得进程控制权后处于已被限制的上下文内,逃逸需要额外的内核漏洞。官方提供 systemd-analyze security 工具为每个服务输出安全评分,可量化加固效果。

操作步骤

第一步:查看当前安全评分

systemd-analyze security nginx.service | tail -20

输出末尾给出 0-10 的总评分,10 最安全。未加固的 Nginx 通常在 4-6 分之间,每条 EXPOSE 项都对应一条可收紧的指令。

第二步:创建 override 配置

不要直接改发行版的 unit 文件(升级会被覆盖),使用 systemctl edit 生成 override:

systemctl edit nginx.service

在打开的编辑器中写入 [Service] 段(以 Nginx 为例):

[Service]
# 整个文件系统只读,白名单目录除外
ProtectSystem=strict
ReadWritePaths=/var/log/nginx /var/cache/nginx /run

# 私有 /tmp,与其他进程隔离
PrivateTmp=true

# 禁止提权(setuid/setcap 均失效)
NoNewPrivileges=true

# /home 目录不可见
ProtectHome=true

# 禁止修改内核参数与设备
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true

# 限制网络地址族(只允许 IPv4/IPv6/Unix socket)
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# 收窄能力集合(Nginx 只需绑定端口的能力)
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

# 禁止获取新特权的能力
RestrictSUIDSGID=true

关键参数说明:ProtectSystem=strict 将整个文件系统树只读挂载,只有 ReadWritePaths 列出的路径可写;PrivateTmp=true 给服务分配独立的 /tmp(配合此前 /tmp noexec 加固形成双层防护);NoNewPrivileges=true 阻断通过 setuid 二进制提权到 root 的经典路径。写盘路径必须逐项加入 ReadWritePaths,否则服务启动即报 Permission denied。

第三步:应用并验证

# 重载 unit 定义并重启服务
systemctl daemon-reload
systemctl restart nginx

# 确认沙箱指令已生效
systemctl show nginx -p ProtectSystem -p PrivateTmp -p NoNewPrivileges

# 再次评分,对比加固前后差异
systemd-analyze security nginx.service | tail -3

按同样方法为 Redis(额外加 ProtectClock=trueRestrictNamespaces=true)与自研服务逐个收紧,目标是将所有对外服务评分提升到 7 分以上。

配置验证

进入服务的沙箱上下文实测写权限:

# 在 nginx 服务的 namespace 内尝试写 /etc(应被拒绝)
systemd-run --scope -p ProtectSystem=strict sh -c 'touch /etc/test && echo 写入成功 || echo 写入被拦截'

# 确认业务正常
curl -sI http://127.0.0.1/ | head -1

# 日志确认无权限类报错
journalctl -u nginx --since "5 minutes ago" | grep -i denied

常见问题

FAQ 1:服务重启后报 Permission denied 无法启动

最常见原因是运行期写盘路径未加入 ReadWritePaths。定位方法:journalctl -u 服务名 -p err --since today 查看被拒绝的具体路径,将其追加到 ReadWritePaths 后 systemctl daemon-reload && systemctl restart。注意 Nginx reload 时若 PID 文件目录不可写也会失败,/run 需保留可写。

FAQ 2:CentOS 7 部分指令报 Unknown key 或不生效

CentOS 7 内置 systemd 219,不支持 RestrictAddressFamilies、ProtectKernelModules 等新指令,systemd 会打印 warning 但不影响启动,仅生效旧指令。建议以 systemd-analyze security 的实际输出为准逐条验证;条件允许优先升级系统,沙箱指令在 systemd 232 之后才完整可用。

总结

systemd 服务加固以接近零成本为每个服务建立独立沙箱:文件系统只读、私有 /tmp、禁提权、收窄能力集合,配合评分工具可持续量化每个服务的暴露面。与 WAF、防火墙等边界防护互补——边界防的是进来,沙箱防的是进来之后的横向移动。建议从对外暴露的 Nginx、Redis 开始逐服务收紧,所有变更通过 override.d 管理,系统升级不丢失。