未来星球 未来星球
  • 首页
  • 新闻资讯
  • 技术教程
  • 软件工具
  • 知识百科
  • 生活实用
  • 个人随笔
  • 生命外星人
首页 › 技术教程 › 用systemd硬化一个备份服务:5项配置,和4个必然踩到的坑
  • 0
  • 0

用systemd硬化一个备份服务:5项配置,和4个必然踩到的坑

admin
2 天前
11 views
用systemd硬化一个备份服务:5项配置,和4个必然踩到的坑-未来星球

先看一行输出。在一台刚装好的 Ubuntu 24.04 上,给自写的备份脚本配好 systemd 服务,运行 systemd-analyze security,它给出 9.6,评级 UNSAFE。9.6 的意思是「几乎不做任何限制」,分数越低越安全。

大多数自建服务都停在这个位置,不是因为不管,是因为默认值就是这么松。下面这份配置把它压到 2 附近,用到的全部是 systemd 自带的开关,不需要容器,不需要改代码。硬化要解决的不是「会不会被打」,是「被打中之后能走多远」。

本文环境:Ubuntu 24.04 LTS,systemd 255,源目录 /srv/data,落地目录 /srv/backup,服务名 bk.service,运行用户 backup。下面每一步都对应一个具体的开关,可以只挑其中几项用。

/srv/
├── data/                  # 只读源
│   ├── app/
│   └── uploads/
├── backup/                # 可写目标
│   └── 2026-11/
/etc/systemd/system/
└── bk.service
/usr/local/bin/
└── backup.sh

1. 把根文件系统设成只读

ProtectSystem=strict 会把整个文件系统挂成只读,包括 /usr、/etc、/boot,连 /var 也一起。写完这一行,服务立刻起不来——因为它连日志都写不进去。这不是配置失误,是硬化本身的性质:先全部关掉,再一个一个开回来。

开回来的方式有三种,按精细程度递增:ReadWritePaths 给具体路径,LogsDirectory 和 StateDirectory 让 systemd 建目录并授权。后者比在脚本里 mkdir 再 chown 可靠,因为目录在服务启动之前就已经存在,属主也由 systemd 维护。启动顺序上的确定性,比权限本身更值钱。

ProtectSystem=strict
ReadWritePaths=/srv/backup
LogsDirectory=backup
StateDirectory=backup

2. 私有临时目录

PrivateTmp=yes 给服务一个独立的 /tmp 和 /var/tmp,其他进程看不见,服务自己也看不见别人的。这一项几乎没有副作用,唯一要注意的是互斥锁:如果你在脚本里写死了 /tmp/backup.lock 去做单实例,硬化之后这个锁只对本服务有效,手工跑的同一脚本看不到它。互斥要用 flock 加在真实路径上,不要指望 /tmp 里的文件当锁。

3. 关掉家目录

ProtectHome=yes 让 /home、/root、/run/user 对服务不可见。备份脚本经常顺手读 ~/.ssh 里的密钥去做远端同步,这一行会让它当场失败。失败是好事,因为它暴露了一个事实:密钥一直放在只有人能读的地方。

正确做法是把密钥移到 /etc/bk/id_ed25519,属主 root、权限 600,再用 ReadOnlyPaths 挂进去。密钥跟着服务走,不跟着人走,这本身就是一次权限收敛。顺带的好处是:人离职时不用再想起来删一遍密钥。

4. 只留需要的协议族

RestrictAddressFamilies 限制服务能创建哪些 socket,备份只连远端 SSH 的话,AF_INET 和 AF_INET6 就够。但漏掉 AF_UNIX 会出一个很难查的错:某些库在初始化时会去连 /run/dbus/system_bus_socket,拿不到就打印一行含糊的 warning 然后继续。你只会觉得「怎么慢了两秒」,不会想到是权限问题。

RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

5. 收掉能力位与系统调用

NoNewPrivileges=yes 禁止服务通过 setuid 提权,CapabilityBoundingSet 留空表示一个 capability 都不给,SystemCallFilter=@system-service 只放行常规服务需要的系统调用。这三项一起上,服务的「可行动空间」会缩得很小。缩小之后仍然跑得起来,才算证明了它本来就不需要那么多。

SystemCallErrorNumber=EPERM 让被拦下的调用返回「权限不足」而不是直接杀掉进程,这样脚本还能打出有意义的日志。直接 SIGSYS 会让人以为是段错误,排查时间能翻三倍。

完整的一份 unit

[Unit]
Description=Nightly backup of /srv/data
After=local-fs.target

[Service]
Type=oneshot
User=backup
Group=backup
ExecStart=/usr/local/bin/backup.sh

ProtectSystem=strict
ReadWritePaths=/srv/backup
LogsDirectory=backup
StateDirectory=backup

PrivateTmp=yes
ProtectHome=yes
PrivateDevices=yes
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes

RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX
RestrictNamespaces=yes
RestrictRealtime=yes
LockPersonality=yes

NoNewPrivileges=yes
CapabilityBoundingSet=
AmbientCapabilities=
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
MemoryDenyWriteExecute=no

[Install]
WantedBy=multi-user.target

MemoryDenyWriteExecute 单独留成 no,原因在最后一节。其余全部打开,这是一份可以直接用的起点,不是示意。照抄它至少能让你少走一遍「改一项、重启、看日志」的循环。

验收:一个分数,三条实测

改完 unit 先 daemon-reload,再逐条实测。分数只是参考,行为才是证据。评分表会因为你关掉一个用不到的开关而加分,但只有实测能证明它没把需要的也关掉。分数从 9.6 降到 2 附近,靠的主要是那十来个布尔开关,剩下的都是细节分。

别把 2 当成目标。分数到 2 之后继续压,通常要开始动 SystemCallFilter 的白名单,收益很小,而每一次内核升级都可能让它失效。把分数当成体检指标,而不是考试成绩。

systemctl daemon-reload
systemctl restart bk.service
systemd-analyze security bk.service
systemctl show bk.service -p ProtectSystem -p PrivateTmp -p NoNewPrivileges

第一条实测:在服务里故意写 /etc/bk-test,应该报 Read-only file system。第二条:读 /root/.ssh/id_rsa,应该报 No such file。第三条:往 /var/log/backup 写一个文件,应该成功。三条都符合预期,才说明「关掉的真关掉了,放开的是真放开」。

硬化最容易出的事故不是太严,是严了但没验证,等到下一次真备份时才发现写不进去。这类事故通常在凌晨三点被发现,而且第一次发现时看起来像磁盘满了。所以验收要在白天做,并且三条都要做。

为什么不直接上容器

容器能做到更彻底的隔离,代价是多一层运行时、多一份镜像维护、多一个需要更新的组件。对一个每天跑一次的备份脚本来说,这些成本换来的收益有限。systemd 的硬化不是容器的替代品,是「不值得为它上容器」时的正解。

反过来说,如果服务要跑不可信代码、要暴露端口、要装一堆依赖,那就该上容器,而不是在 unit 里堆四十行开关。判断标准是「这个服务会不会被人从外面碰到」,不是「哪个更时髦」。

四个必然踩到的坑

第一,ProtectSystem=strict 之后 /var/log 整体只读,所有自写的日志路径都要迁到 LogsDirectory 下。这个错误在 journalctl 里只显示一行「Permission denied」,看不出是挂载限制。看到这行字时,先想挂载,再想权限。

第二,ProtectHome=yes 会让 rsync 的 –exclude-from 找不到放在家目录的排除文件,脚本不报错,只是备份体积突然变大。这类失败不报错,只改结果,是最难发现的一类。备份系统的正确性,只能靠每次核对体积和文件数来保证。

第三,MemoryDenyWriteExecute=yes 会让带 JIT 的运行时崩掉:Python 的某些加速库、Node、Java,还有用 mmap 生成代码的解析器都会中招,表现是启动即 signal 11。只要脚本里调用了这类工具,就必须把它关掉。

第四,PrivateDevices=yes 会隐藏 /dev 下的真实设备节点。脚本里写了 dd 到 /dev/sdb,或者要读某个串口,硬化之后设备直接不存在。限制设备访问是对的,但前提是服务真的不需要设备。

写在最后

这套开关的价值不在安全评分表上。它的价值在于,把「这个服务能做什么」从一行模糊的描述,变成一份可读的清单:只能读 /srv/data,只能写 /srv/backup,只能连 TCP 和本地 socket,不能提权,不能加载模块,不能改时钟。清单本身就是文档,而且是不会过期的那种。

写清单的过程往往比清单更有用。很多人是在配 ReadWritePaths 的时候,才发现自己的备份脚本半年以来一直在往 /tmp 里写中间文件、从来没清理过。硬化不是给服务加锁,是把「它其实能干很多事」这个事实摆到桌面上,然后一件一件拿掉。


你机器上跑着的那些自建服务,现在打多少分?有没有哪一个,你其实说不清它到底需要哪些权限?

0

A4纸为什么是210×297毫米:1786年的一封信,和一道开方算术

上一篇

给3000张照片改名:4个工具的边界,和3种选错的信号

下一篇

评论 (0)

登录后可发布~
立即登录
暂无评论

admin

当生活给你的全是不如意,那你一定要相信自己的兴趣!
249
文章
0
评论
-11
喜欢
2026 年 11 月
一 二 三 四 五 六 日
 1
2345678
9101112131415
16171819202122
23242526272829
30  
« 10 月    

近期评论

聚合文章

从图片里抠出文字:4 个 OCR 工具的边界,3 种选错的信号
当眼镜一直开着:三代人的记忆,和一个不太好接受的推论
第九行星:11年,和一组不肯聚拢的轨道
修一双鞋,鞋跟换了两次,和一块磨偏的橡胶
AI新闻日报–2026年11月14日:57%,和644亿美元
洗衣机甩干时满地跑:6 项措施,按投入时间排的一份清单
fail2ban 挡住 SSH 爆破:4 份配置,和 6 个坑
盲道为什么是两种凸起:1965年,和一条被走错的直道

猜你喜欢

用 Prometheus 搭一套单机监控:四条告警规则,和三个必然踩到的坑

用 Prometheus 搭一套单机监控:四条告警规则,和三个必然踩到的坑

admin
31 0 0
Uptime Kuma 监控五个站点:3份配置,和4个必然踩到的坑

Uptime Kuma 监控五个站点:3份配置,和4个必然踩到的坑

admin
17 0 0
Python自动化办公实战:用30行代码搞定重复工作

Python自动化办公实战:用30行代码搞定重复工作

admin
81 0 0
用Redis 8搭一主两从自动切换:4份配置,和3个必然踩到的坑

用Redis 8搭一主两从自动切换:4份配置,和3个必然踩到的坑

admin
25 0 0

关于我们

你是否对浩瀚的宇宙充满好奇?你是否渴望掌握未来科技的力量?现在,机会来了!“未来星球”将带你进入奇妙世界,开启你的科技探险之旅! 这是一个充满想象和创造力的博客!

分类导航

首页 新闻资讯 技术教程 软件工具 知识百科 生活实用 个人随笔 生命外星人

友情链接

未来星球AI工具【网络版】 未来星球 pycharm官方站【中国】
Copyright © 2026 未来星球. Designed by nicetheme. 蒙ICP备2026002569号-1
  • 请到 [后台->外观->菜单] 中设置菜单
  • AI未来
  • 生活百科
  • 档案
  • 外星人
  • 未来星球

admin

当生活给你的全是不如意,那你一定要相信自己的兴趣!
249
文章
0
评论
-11
喜欢