不可变备份是抵御勒索软件的最后一道防线。多数勒索事件中,攻击者会先枚举并删除备份,再加密生产数据;只要备份可以被删改,恢复能力就形同虚设。不可变备份通过 WORM(Write Once Read Many)机制,在保留期内禁止任何账号删除或覆盖备份对象,即便管理员凭据泄露也无法绕过。本文给出基于 S3 Object Lock 与本地只追加保护的完整配置方案与验证命令。
适用场景
- 勒索软件风险较高,需要确保备份在保留期内不可删除、不可篡改
- 数据库、代码仓库、文件服务器等关键数据的离线保护
- 需要满足等保或行业审计对备份不可否认性的要求
- 运维账号权限较大,需要技术手段约束人为误删
- 已有常规备份,但担心备份服务器与生产环境同域沦陷
前置条件
- 支持 Object Lock 的对象存储(MinIO、AWS S3、阿里云 OSS、腾讯云 COS 等)
- Object Lock 只能在创建桶时开启,已存在的桶无法事后启用
- 独立的备份专用账号与访问密钥,权限与生产账号分离
- 对象存储客户端:mc(MinIO Client)、awscli 或 rclone
- 备份主机与生产主机网络隔离,备份数据流向为单向
- 已明确保留期要求(如 30 天、90 天、1 年)与合规模式选择
# 安装客户端
curl -O https://dl.min.io/client/mc/release/linux-amd64/mc
chmod +x mc && mv mc /usr/local/bin/
apt-get install -y awscli rclone # Debian/Ubuntu
# 配置别名
mc alias set backup https://oss.example.com BACKUP_AK BACKUP_SK
原理说明
不可变备份依赖三个机制叠加,缺一不可:
- 版本控制(Versioning):对象的每次写入生成新版本,删除操作只是新增一个删除标记,历史版本仍然可读。没有版本控制,Object Lock 无法生效。
- 对象锁(Object Lock):为对象或桶设置保留期,在
retain-until-date之前,该对象版本无法被覆盖或删除。 - 权限收敛:备份写入账号被策略显式拒绝 Delete 类操作,避免保留期设置被绕过。
对象锁提供两种模式,区别在能否提前解除:
- GOVERNANCE 模式:具有
s3:BypassGovernanceRetention权限的账号可提前解除保留,适合内部治理,灵活性高但存在被高权限账号绕过的可能。 - COMPLIANCE 模式:任何账号(含 root)在保留期内都无法删除或缩短保留期,合规要求场景必须使用此模式。选择前务必确认保留期,设错无法回退。
配合 3-2-1-1-0 原则使用效果最佳:3 份副本、2 种介质、1 份异地、1 份不可变或离线、0 次验证失败。
操作步骤
步骤 1:创建启用对象锁的桶
# 使用 mc 创建桶并启用版本控制与对象锁
mc mb --with-lock backup/backup-immutable
mc version enable backup/backup-immutable
# 或使用 awscli(PutBucketObjectLockConfiguration 需在桶创建时生效)
aws --endpoint-url https://oss.example.com s3api create-bucket \
--bucket backup-immutable --object-lock-enabled-for-bucket
aws --endpoint-url https://oss.example.com s3api put-bucket-versioning \
--bucket backup-immutable --versioning-configuration Status=Enabled
创建后确认输出包含 ObjectLockEnabled: Enabled,若缺失说明桶未正确启用对象锁,需删除重建。
步骤 2:设置桶级默认保留策略
# mc 方式:设置桶默认保留 90 天,COMPLIANCE 模式
mc retention set --default COMPLIANCE "90d" backup/backup-immutable
# awscli 方式:等价配置
cat > /tmp/lock.json <<'EOF'
{
"ObjectLockEnabled": "Enabled",
"Rule": {
"DefaultRetention": { "Mode": "COMPLIANCE", "Days": 90 }
}
}
EOF
aws --endpoint-url https://oss.example.com s3api put-object-lock-configuration \
--bucket backup-immutable --object-lock-configuration file:///tmp/lock.json
桶级默认策略对之后写入的新对象自动生效,无需在每次上传时单独指定,是降低运维遗漏的关键配置。存量对象需单独补设。
步骤 3:写入备份并核对保留状态
# 生成数据库备份
mysqldump --single-transaction --routines --triggers app \
| gzip > /backup/app_$(date +%F).sql.gz
# 上传(桶默认保留策略自动生效)
mc cp /backup/app_$(date +%F).sql.gz backup/backup-immutable/db/
# 显式指定保留期(覆盖默认值,示例保留至 2027-01-02)
aws --endpoint-url https://oss.example.com s3api put-object \
--bucket backup-immutable --key db/app_20261004.sql.gz \
--body /backup/app_20261004.sql.gz \
--object-lock-mode COMPLIANCE \
--object-lock-retain-until-date 2027-01-02T00:00:00Z
# 核对对象保留状态
aws --endpoint-url https://oss.example.com s3api get-object-retention \
--bucket backup-immutable --key db/app_20261004.sql.gz
mc retention info backup/backup-immutable/db/app_20261004.sql.gz
返回结果应包含 Mode: COMPLIANCE 与正确的 RetainUntilDate。若返回 NoSuchObjectLockConfiguration,说明对象是在桶默认策略生效前写入的。
步骤 4:收紧备份账号权限
仅依赖对象锁不够,还需防止权限被滥用去修改桶策略。为备份写入账号附加显式拒绝删除的策略:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DenyDeleteAndLockRemoval",
"Effect": "Deny",
"Action": [
"s3:DeleteObject",
"s3:DeleteObjectVersion",
"s3:PutBucketObjectLockConfiguration",
"s3:PutLifecycleConfiguration"
],
"Resource": [
"arn:aws:s3:::backup-immutable",
"arn:aws:s3:::backup-immutable/*"
]
}
]
}
写入账号与读取/恢复账号必须分离:写入账号只有 PutObject 权限,恢复账号只有 GetObject 权限,两侧都不具备删除能力。
步骤 5:本地备份目录的只追加保护
对象存储之外的本地副本可用文件属性锁定,注意 +i 属性需 root 才能设置,且在保留期内必须由流程约束不得解除:
# 锁定归档文件,禁止删除、重命名与写入
chattr +i /backup/archive/app_20261004.sql.gz
lsattr /backup/archive/app_20261004.sql.gz # 期望输出含 ----i---------
# 目录级锁定(新文件需先解锁目录再写入)
chattr +i /backup/archive/
# ZFS 快照加 hold,防止被自动清理策略删除
zfs snapshot tank/backup@20261004
zfs hold keep tank/backup@20261004
zfs holds tank/backup
使用 rclone 同步到远端不可变桶时,加 --immutable 参数可拒绝任何覆盖已有对象的操作:
rclone sync /backup remote:backup-immutable \
--immutable --transfers 4 --checkers 8 --log-file /var/log/rclone-backup.log
步骤 6:恢复演练与完整性校验
# 每月至少一次恢复演练:下载并校验
mc cp backup/backup-immutable/db/app_20261004.sql.gz /restore/
gunzip -c /restore/app_20261004.sql.gz | head -20
# 校验备份与源库一致性:对比表数量与行数
mysql -e "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='app';"
gunzip -c /restore/app_20261004.sql.gz | grep -c "CREATE TABLE"
# 生成恢复演练记录,保留时间、耗时、结果
echo "$(date +%F' '%T) restore-ok app_20261004.sql.gz 4m12s" >> /var/log/restore-drill.log
配置验证
# 1. 桶对象锁状态
aws --endpoint-url https://oss.example.com s3api get-object-lock-configuration \
--bucket backup-immutable
# 2. 版本控制状态
mc version info backup/backup-immutable
# 3. 删除测试(预期被拒绝,返回 AccessDenied)
aws --endpoint-url https://oss.example.com s3api delete-object \
--bucket backup-immutable --key db/app_20261004.sql.gz --version-id <VID>
# 4. 本地不可变属性
lsattr /backup/archive/app_20261004.sql.gz
# 5. 备份任务与恢复演练记录是否连续
tail -5 /var/log/rclone-backup.log
tail -5 /var/log/restore-drill.log
第 3 项验证是核心:删除请求必须失败。若返回 204 说明保留期未生效或使用了 GOVERNANCE 模式且有绕过权限,需立即排查。建议将此验证脚本纳入定期巡检。
常见问题
FAQ 1:桶已存在,能否事后开启 Object Lock?
不能。Object Lock 必须在创建桶时通过 --object-lock-enabled-for-bucket 或 --with-lock 开启,已存在的桶无法追加该配置。正确做法是新建一个启用对象锁的桶,用 rclone 或 mc 将历史备份迁移过去,并对迁移后的对象补设保留策略。迁移期间保留原桶,确认新桶验证通过后再下线旧桶。
FAQ 2:COMPLIANCE 模式选错保留期,能提前释放吗?
不能。COMPLIANCE 模式下,保留期内任何账号(包括超级管理员)都无法删除对象或缩短保留期,这是该模式的设计目的,也是它适合合规场景的原因。选错保留期只能等待自然到期。因此建议:日常备份先用 GOVERNANCE 模式验证流程,确认无误后再对关键数据使用 COMPLIANCE,并按最小必要原则设置保留天数。
FAQ 3:不可变备份能否替代离线(air gap)备份?
两者互补,不能完全互相替代。不可变备份的存储系统仍需在线、需凭据访问,若存储平台自身被攻破或凭据与保留期配置同时被篡改,仍存在风险;离线备份(磁带、断电异机、定期拔盘)在网络层面彻底不可达。推荐组合为:本地快照 + 不可变对象存储 + 周期性离线副本,三者保留期错开,形成纵深。
FAQ 4:备份文件上传后体积异常或上传中断怎么办?
上传中断会在桶中留下不完整对象,配合对象锁后同样无法删除。建议上传前启用分片上传并在客户端做完整性校验,或在对象元数据中记录源文件 SHA256,恢复演练时比对。若已产生垃圾对象,只能等待保留期结束后清理,这也是需要提前设定合理默认保留天数的原因之一。
总结
不可变备份的落地要点是三个机制叠加:版本控制 + 对象锁 + 权限显式拒绝,任一缺失都会留下删除路径。实施顺序建议为:确认保留期 → 新建启用锁的桶 → 设置桶级默认保留 → 分离读写账号 → 配置本地只追加保护 → 定期演练与删除验证。合规场景使用 COMPLIANCE 模式并做好保留期评估,内部治理场景可用 GOVERNANCE 模式换取灵活性。备份体系的价值不在于备份动作本身,而在于勒索发生后能否在规定时间内完成一次可验证的恢复,因此删除验证与恢复演练必须纳入常态化巡检。