systemd 服务加固实战:让服务不 root、不裸跑、破坏面最小


用 `systemctl enable --now` 把服务跑起来很容易,但"让服务按你想要的隔离与安全方式跑"常被忽略:默认 unit 进程以 root 跑、能读写全盘、能访问网络与主机资源——一旦这个服务被攻破就是一台机器的沦陷。systemd 自带一套"服务级安全加固"开关,几乎不写代码就能大幅收窄服务的攻击面。这篇讲清 systemd 加固解决什么、最常用的一批加固项怎么用、怎么验证"真的收窄了",以及"过度加固导致服务起不来"的坑与边界。

一、systemd 加固解决什么:把"以 root 全权限跑"收成"最小够用"的系统级隔离

默认服务进程等同"以 root 跑、能读全盘、能访问任意网络端口、能触及主机共享资源"。加固的目标是"按最小权限给它一个被装进笼子里的运行环境":不让它 root 就跑、不共享一堆内核/文件系统能力、把写盘和网络/挂载收窄、限制可打开文件数与进程数。systemd 把这些抽象成一组几乎零代码的 unit 开关,能做成"即使进程被 RCE,能造成的破坏也有限"。它跟前文的容器加固、SELinux 是同一条主线:都是"运行时的最小权限"——systemd 是用系统自带能力做的轻量隔离。理解它先抓住"systemd 能在一个 unit 上声明 N 个隔离与降权约束"这一点,比默认裸跑稳一个量级。

二、最常用的一批项:进程降权、文件系统只读、网络与能力收窄

给一个 unit 加固,编排顺序是"能降权就降权 + 能收窄就收窄 + 能只读就只读"。最常用的一批:`User=/Group=`(用普通用户/组跑,别 root)、`PrivateTmp=`(隔离的 /tmp)、`ProtectSystem=strict/full`(根文件系统只读,仅显式 `ReadWritePaths=` 可写)、`ProtectHome=`(隐藏/只读家目录)、`NoNewPrivileges=`(禁止提权,配合降权);网络与能力类 `CapabilityBoundingSet=`(裁剪 capability,如去掉 NET_RAW/SYS_ADMIN)、`PrivateDevices=`(阻塞设备)、`RestrictAddressFamilies=`(只允许想用的网络栈);资源类 `LimitNOFILE=`(文件描述符上限)、`TasksMax=`(进程上限)。把这些叠上去,一个 root 裸跑服务通常能收成一个"受限普通用户 + 只读 fs + 去掉多余能力的进程"。

# /etc/systemd/system/my-app.service 加固片段
[Service]
User=appuser              # 别 root 跑,用专用低权用户
Group=appgroup
NoNewPrivileges=true      # 关闭 setuid 提权
ProtectSystem=strict      # 根文件系统只读
ReadWritePaths=/var/lib/my-app   # 只显式放行这一个写目录
ProtectHome=true          # 家目录只读/不可见
PrivateTmp=true           # 独立的临时目录
PrivateDevices=true       # 不共享宿主设备
CapabilityBoundingSet=    # 清空 capability(或按需列,别给 NET_RAW/SYS_ADMIN 等)
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6  # 只允许需要的网络栈
LimitNOFILE=4096          # 限制可打开文件数
TasksMax=100              # 限制 fork 出来的进程数

# 应用后:重载并重启,再用 systemd-analyze 看实际该 unit 的隔离
systemctl daemon-reload && systemctl restart my-app
systemd-analyze security my-app

编排时的两个把关:一是"先降权再裁剪"(先 User= 再 #Capability/devices 才有意义);二是"写目录显式放行"(ProtectSystem=strict 后不配 ReadWritePaths=,需要落盘的服务会写不进去直接报错)。每次改完务必用 `systemd-analyze security` 看"暴露面分值和具体没收紧的开关",别只是"加了几行却不验证"。

三、怎么验证真的收窄:systemd-analyze security 与实测

收窄要"可证明",而不是"加了好几个开关就当安全了"。用 `systemd-analyze security my-app` 会给出该 unit 的暴露面评估(一个分值与一堆说明),能看到它确切启用了哪些隔离、哪些还没启用。更实的验证是实测:以被加固的用户角度看一眼"它是谁在跑(ps)、它能不能写根目录、能不能开能力"——比如 `systemctl show` 查看生效的属性、`cat /proc//status'` 看 CapEff/CapBnd 是否为 0(真正降权到无能力)。这两样结合,才不是"加开关自嗨"。

# 看该 unit 实际生效的安全属性(CapabilityBoundingSet 是否真空)
systemctl show my-app -p User -p CapabilityBoundingSet -p ProtectSystem -p NoNewPrivileges
# 或直接看进程:CapEff/CapBnd 是 0 说明确无 capabilities
procs=$(pgrep -f my-app); cat /proc/$procs/status | grep -E '^(CapEff|CapBnd|Uid)'

四、边界与坑:加固是"收窄",别把必要能力也给误剪

systemd 加固的正确打开方式是"把能安全收的都收 + 明确给业务必需的放行"——用前先确认业务到底需要哪些能力/路径:需要监听 80/443 的就别把 NET 全裁、需要落盘到某目录就放行 ReadWritePaths、需要跑子进程的就别把能力裁到它会 fork 不了。过度加固(把启动必需的资源也剪掉)会让服务起不来,报的错通常是"权限不足/写不进/无能力",这时对照 systemd-analyze 回退放行必需项即可。边界要认清:它是"系统级进程加固",能显著缩小被 RCE 方的破坏面,但不替代"跑非 root 还是可能被提权"这类更深层防护、也不替代上游漏洞打补丁。把 'User= + NoNewPrivileges + ProtectSystem + 裁剪能力'这几张牌打扎实、并常态跑到 systemd-analyze 验证,一台机器的服务安全基线就上了一个台阶。


手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

Grafana 进阶实战:多数据源、变量联动与告警回落看板

Linux 网络命名空间与 Overlay 实战:把容器网络刨到底

评 论
更换验证码