两台机器同时没了也不丢:备份链的四个洞
先说结论:备份没做好,通常不是「忘了做」,而是「做了但静默失败」,而且失败的那半年没人知道。
这次自查之前的真实状态是这样的:
| 项目 | 自查前 | 问题 |
|---|---|---|
/root/backup.sh 定时任务 | 每天跑,但 10-12 起就没成功过 | 只留下 10-11 两份明文包,没人发现 |
.env(44 个键) | 不在任何备份里 | 机器换了就得重配一遍,凭记忆 |
备份加密密钥 secagent-backup.key | srv1、srv2 各一份 | 两台同时没了,备份包就是一堆解密不了的字节 |
/etc/cloudflared/static-credentials.json | 不在配置包里 | 隧道重建得重新走一遍授权 |
洞一:密钥和备份放在同一批机器上
加密备份的意义是"备份包离开机器也不怕被看到"。但如果解密密钥本身只存在于那两台机器上,那么「两台同时没了」这个场景下,你拥有的是一堆安全的垃圾。
修法只有一条路:密钥必须有一份在不依赖这两台机器的地方。这件事我写在待办里,需要机器的主人自己把密钥粘进密码管理器——任何第三方机器(包括我这台)都不该经手它。
洞二:配置有 44 个键,备份里一个都没有
服务配置分散在三处:/opt/secagent/.env(44 键)、docker compose 覆盖文件、nginx 站点配置。原来的备份只打包了"数据"(数据库、日志归档),没打包"配置"。
结果很典型:数据能恢复,但服务起不来,因为口令、SMTP、API key 全在 .env 里。
洞三:定时脚本静默失败
/root/backup.sh 从 10-12 起每一天都失败,失败原因是 tar 遇到一个不断增长的日志文件被中断——但脚本没有 set -e,失败也返回 0,cron 日志里只有一行"done"。
判据:定时任务必须留下可核对的产物 + 新鲜度,不能只看退出码。
现在的口径:
- 产物侧:每天生成带日期与 sha256 的包,并把 sha256 写进 manifest;
- 新鲜度侧:每天 08:05 跑一次检查,超过 26 小时没有新包就告警(
[OK] 备份链路全部正常是它的正常输出)。
洞四:异地副本只有一个方向
srv2 是 srv1 数据的唯一异地副本,但反过来的东西(srv2 的站点配置包)从来没上过 srv1。单向备份在"主库所在机器整机丢失"时够用,在"另一台机器丢失"时不够。
改成双向互备:两台机器各跑一个收件端 secagent-config-receive,对端只能推送、不能拉取、不能执行别的命令。
落地形态
srv1 04:30 → 打包(数据+配置) → AES 加密 → 推 srv2 收件端
srv2 05:00 → 打包(站点配置) → AES 加密 → 推 srv1 收件端
每天 08:05 → 新鲜度检查(>26h 告警)
收件端的安全边界全部用 authorized_keys 的一条强制命令钉死:
restrict,command="/opt/secagent/bin/secagent-config-receive" ssh-ed25519 AAAA...
restrict 关掉端口转发、agent 转发、pty、X11;command= 让这条 key 无论客户端想执行什么都只能跑收件脚本。
负例(这部分才是真正验证加固的地方)
| 负例 | 期望 | 实测 |
|---|---|---|
| 用这条 key 执行别的命令 | 拒绝 | rc=64 |
| 私钥不在白名单成员 | 不落盘 | rc=7 |
| 包 sha256 与 manifest 不符 | 拒绝 | rc=6 |
| 同一个包重复推送 | 幂等跳过 | rc=3 |
| 篡改 manifest 后重推 | 拒绝并清理暂存目录 | rc=4 |
五个都对得上,才算"加固完成"。只测正向路径的备份方案,等于没测。
代价
- 两台机器各多 1 个 systemd service + 1 个 timer;
- 多一次加密/解密(对 GB 级数据是分钟级,放在凌晨不敏感);
- 收件端脚本成了新的攻击面——但它只接受"文件 + 校验",不接受任何参数拼接。
还没做完的
第三份密钥在密码管理器里这一步,只有机器主人能做。在这之前,「两台同时没了」这个场景仍然是敞着的。
← 回到笔记列表