上一篇 下一篇 分享链接 返回 返回顶部

备份也可能被攻击?香港服务器的备份网络隔离与 WORM 只读对象存储实战:架构设计、MinIO 对象锁、Restic/pgBackRest 落地部署与优化

发布人:Minchunlin 发布时间:2025-09-28 10:19 阅读量:817


那天是香港机房凌晨 2:17。NOC 的报警把我从椅子上“弹”起来:多台业务节点磁盘写入陡增、异常加密进程暴涨。我们第一时间切断对外暴露的管理口子,登录上去一看,果然是勒索家族的新变种在“扫目录”。更让我出冷汗的是——攻击者已经尝试扫到了我们的备份挂载点。幸好半年前我把备份通道做了彻底隔离,并把对象存储切到了 WORM(Write Once Read Many)不可变模式:即使拿到密钥也删不掉、改不掉。那一夜,我们靠着“物理/逻辑隔离 + 对象锁”保住了底线。第二天,我把这套方案从头到尾梳理了一遍,写成了下面这篇教程。

场景与目标

  • 环境:香港机房,CentOS 7(内核 3.10,systemd),裸金属 + KVM 混合。

目标:

  • 生产网与备份网二层/三层彻底隔离,最小可达、仅出不入;
  • 备份目标落地到 S3 兼容对象存储(MinIO),启用 对象锁(Object Lock)/WORM;
  • 客户端用 Restic(文件/镜像/容器卷)和 pgBackRest(PostgreSQL)直写 S3;
  • 全链路 TLS、时钟一致性、最小权限与审计;
  • 提供完整可复现部署步骤、优化参数、踩坑记录与演练脚本。

架构一览(文字拓扑)

[生产业务网 VLAN10 10.10.10.0/24]
   | (无路由直达备份网)
   |                   ┌─────────────────────────────┐
   └──> [备份代理/Client 节点] eth1(备份网)         │
                       静态路由只到 10.160.0.0/22  │
                         ↓                          │
                  [备份网络 VLAN160 10.160.0.0/22] │  ← 二层独立
                         ↓                          │
                  [MinIO 集群 x4 节点]              │  (S3:9000 / Console:9001)
                         │                          │
                         └──(可选)→[离线/异地复制桶] ┘

要点:生产网无法发起到备份网的“横向移动”;只有客户端通过第二张备份网卡以受限出向访问 MinIO 的 S3 端口;MinIO 集群不回通生产网。

硬件与网络清单

MinIO 集群(示例)

节点 机型/CPU 内存 系统盘 数据盘(12 块) 网卡
S3-01..04 Dell R6525 / EPYC 7313P 128G 2×SSD RAID1 12× 8TB SATA (7200rpm) 2×10GbE(其中1口接 VLAN160)

存储布局:4 节点 × 12 盘,MinIO 分布式纠删码(EC),容量与冗余兼顾。对象锁对底层文件系统无特殊要求(建议 XFS)。

备份网络与地址规划

名称 VLAN 网段 用途
生产网 10 10.10.10.0/24 业务
管理网 20 10.10.20.0/24 out-of-band 管理
备份网 160 10.160.0.0/22 仅备份流量(S3 9000/TCP)

部署步骤(可复现)

基础:时钟 & TLS

为什么先做它们? 对象锁对时间极其敏感,时钟漂移会导致保留期/删除判定出错。

Chrony(CentOS 7)

yum install -y chrony
cat >/etc/chrony.conf <<'EOF'
server ntp.hk.example.org iburst
allow 10.160.0.0/22
logmeasurements yes
logchange 0.5
makestep 1.0 3
EOF
systemctl enable --now chronyd
chronyc sources -v

内部 CA/TLS(最简单可用:先用企业内 CA 签发给 MinIO,每节点放置 ~/.minio/certs/public.crt / private.key)

1. 备份网络在客户端与 MinIO 上线

客户端节点(示例 eth1 为备份口):

nmcli con add type ethernet ifname eth1 con-name backup-eth1
nmcli con mod backup-eth1 ipv4.addresses 10.160.1.23/22 ipv4.gateway 10.160.0.1 ipv4.method manual
nmcli con up backup-eth1

只允许出向到 MinIO(firewalld/iptables 二选一):

iptables

# 备份网口名按实际替换
IF=eth1
MINIO_SUBNET=10.160.2.0/24

iptables -P OUTPUT DROP
iptables -A OUTPUT -o $IF -p tcp -d $MINIO_SUBNET --dport 9000 -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT
# 允许 DNS/NTP 指向备份网的特定服务器(如有)
iptables -A OUTPUT -o $IF -p udp --dport 123 -d 10.160.0.10 -j ACCEPT
iptables -A OUTPUT -o $IF -p udp --dport 53  -d 10.160.0.11 -j ACCEPT
# 最后放行必要 ICMP 以便排障
iptables -A OUTPUT -o $IF -p icmp -j ACCEPT
service iptables save

注意:不要把 MinIO 的 9001 控制台口子暴露到生产网。控制台仅在管理网/跳板机访问。

2. 安装并启动 MinIO(分布式)

4 台节点每台准备 12 个数据挂载点 /mnt/disk{1..12},确保 noatime。

安装

useradd -r -s /sbin/nologin minio
curl -L https://dl.min.io/server/minio/release/linux-amd64/minio -o /usr/local/bin/minio
chmod +x /usr/local/bin/minio
mkdir -p /etc/minio /var/minio
chown -R minio:minio /var/minio

systemd 单元(每台)

cat >/etc/systemd/system/minio.service <<'EOF'
[Unit]
Description=MinIO Object Storage
After=network-online.target
Wants=network-online.target

[Service]
User=minio
Group=minio
EnvironmentFile=-/etc/minio/minio.env
ExecStart=/usr/local/bin/minio server \
  --address ":9000" \
  --console-address ":9001" \
  http://s3-{1...4}.bk.local/mnt/disk{1...12}
LimitNOFILE=65536
Restart=always

[Install]
WantedBy=multi-user.target
EOF

cat >/etc/minio/minio.env <<'EOF'
MINIO_ROOT_USER=minio-root
MINIO_ROOT_PASSWORD='强口令_至少24位_含MFA提示'
MINIO_BROWSER=on
EOF

systemctl daemon-reload
systemctl enable --now minio

命名解析:在专用备份 DNS(或 /etc/hosts)里把 s3-1..4.bk.local 指到 10.160.2.11..14。

3. 创建带对象锁(WORM)的桶

关键:对象锁需要在建桶时启用;随后再开版本控制与默认保留期。

安装 mc(Client)

curl -L https://dl.min.io/client/mc/release/linux-amd64/mc -o /usr/local/bin/mc
chmod +x /usr/local/bin/mc
mc alias set bk https://s3-vip.bk.local:9000 minio-root '强口令'

建桶(开启对象锁)

# 每类备份一个桶,便于差异化保留策略
mc mb --with-lock bk/daily
mc mb --with-lock bk/weekly
mc mb --with-lock bk/monthly
# 开启版本控制
mc version enable bk/daily
mc version enable bk/weekly
mc version enable bk/monthly

设置默认保留(示例:日备 Governance 15 天,月备 Compliance 365 天)

# Governance:可由具备 bypass 权限的账号在特殊流程下删除
mc retention set --default governance --period 15d bk/daily

# Weekly/Monthly 更严格
mc retention set --default governance --period 60d  bk/weekly
mc retention set --default compliance --period 365d bk/monthly

(可选)生命周期管理:清理旧版本、非当前版本

# 非当前版本 180 天后清理,避免版本爆炸
mc ilm add --id noncurrent-180 --noncurrent-expire-days 180 bk/daily

验证 WORM:向 bk/monthly 上传对象后,立刻尝试删除,预期得到“保留中不可删除”的错误。

4. 最小权限的访问凭证

给备份客户端单独发 AK/SK,只允许写入各自的前缀路径,不允许列出其他主机的数据。

策略(MinIO 兼容 S3 Policy)示例:policy-backup-client.json

{
  "Version":"2012-10-17",
  "Statement":[
    {
      "Effect":"Allow",
      "Action":["s3:PutObject","s3:AbortMultipartUpload","s3:ListBucketMultipartUploads"],
      "Resource":["arn:aws:s3:::daily","arn:aws:s3:::daily/web01/*"]
    },
    {
      "Effect":"Allow",
      "Action":["s3:PutObjectRetention","s3:PutObjectLegalHold"],
      "Resource":["arn:aws:s3:::daily/web01/*"]
    }
  ]
}

应用策略并创建用户:

mc admin policy add bk backup-client policy-backup-client.json
mc admin user add bk restic-web01 AKIAWEB01 SECRETWEB01
mc admin policy set bk backup-client user=restic-web01

禁止给客户端 s3:DeleteObject;对象锁配合不存在删除权限,双重保障。

5. Restic 客户端(文件/目录/容器卷)

安装

curl -L https://github.com/restic/restic/releases/download/v0.16.4/restic_0.16.4_linux_amd64.bz2 | bunzip2 > /usr/local/bin/restic
chmod +x /usr/local/bin/restic

环境变量与仓库

cat >/etc/restic.env <<'EOF'
export RESTIC_PASSWORD='web01-强口令'
export AWS_ACCESS_KEY_ID='AKIAWEB01'
export AWS_SECRET_ACCESS_KEY='SECRETWEB01'
export RESTIC_REPOSITORY='s3:https://s3-vip.bk.local:9000/daily/web01'
export RESTIC_COMPRESSION=max
export RESTIC_PASSWORD_COMMAND=
EOF
. /etc/restic.env
restic init

首次备份

. /etc/restic.env
restic backup \
  /etc \
  /var/www \
  /var/lib/docker/volumes \
  --host web01 --tag daily --one-file-system --verbose

systemd 定时(每天 02:30)

cat >/etc/systemd/system/restic-backup.service <<'EOF'
[Unit]
Description=Restic Backup (S3 WORM)
After=network-online.target

[Service]
Type=oneshot
EnvironmentFile=/etc/restic.env
ExecStart=/usr/local/bin/restic backup /etc /var/www /var/lib/docker/volumes --host web01 --tag daily --one-file-system
ExecStartPost=/usr/local/bin/restic forget --keep-daily 14 --keep-weekly 8 --keep-monthly 12 --prune
Nice=10
IOSchedulingClass=best-effort
EOF

cat >/etc/systemd/system/restic-backup.timer <<'EOF'
[Unit]
Description=Restic Backup Timer

[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true

[Install]
WantedBy=timers.target
EOF

systemctl daemon-reload
systemctl enable --now restic-backup.timer

说明:即使 forget/prune 发起删除,请求也会被对象锁拒绝,直到保留期结束。这能防御“拿到密钥的恶意删库”。

性能优化要点

  • 备份网是 10GbE:并发上传自动按 CPU 与 I/O 调度,必要时 RESTIC_CACHE_DIR=/var/cache/restic 放 NVMe。
  • 首次大备份用 --files-from 分批;对巨量小文件可考虑先打包 tar --listed-incremental 再喂 restic(视恢复粒度而定)。

6. PostgreSQL 使用 pgBackRest 直接写 S3(对象锁同享)

安装与基本配置(示例 PG14)

yum -y install pgbackrest
useradd -r -s /bin/false pgbackrest
mkdir -p /var/lib/pgbackrest
chown -R pgbackrest:pgbackrest /var/lib/pgbackrest

/etc/pgbackrest/pgbackrest.conf(关键是 s3+x-minio)

[global]
repo1-type=s3
repo1-path=/monthly/pg-main
repo1-s3-endpoint=s3-vip.bk.local:9000
repo1-s3-region=us-east-1
repo1-s3-bucket=monthly
repo1-s3-verify-tls=y
repo1-s3-uri-style=path
repo1-s3-key=AKIAPG
repo1-s3-key-secret=SECRETPG

[pg-main]
pg1-path=/var/lib/pgsql/14/data
start-fast=y
process-max=8

启用 PG 归档与全备

# postgresql.conf
archive_mode = on
archive_command = 'pgbackrest --stanza=pg-main archive-push %p'

# 创建仓库&全备
pgbackrest --stanza=pg-main --log-level-console=info stanza-create
pgbackrest --stanza=pg-main --type=full backup

建议:PG 的全量/增量按周落 weekly 桶(Governance 60d),月度基线进 monthly 桶(Compliance 365d)。

7. 监控与告警(最有用的几个)

  • MinIO:/minio/v2/metrics/cluster 暴露 Prometheus 指标(磁盘可用率、healing、纠删组健康)。
  • 对象锁验证作业:每日抽样对随机对象执行删除,期望 失败;成功则报警(说明策略被破坏)。
  • 备份作业:Restic/pgBackRest 的退出码 + 备份体量变化(异常骤降/暴涨)→ 报警。

关键优化技巧

  • 隔离优先于加密:很多团队做了 TLS/加密,却忽视拓扑隔离。我的实践是“第二网卡 + 独立 VLAN + 单向出向 + 静态路由”,让威胁在生产网“碰不到备份端口”。
  • 时钟一致性是对象锁生命线:Chrony 要设 makestep,并把 MinIO 与客户端都指向同一 NTP。我们踩过一次 1.7 秒漂移,导致保留期判断异常。

治理 Governance vs. 合规 Compliance:

  • 日/周备用 Governance,需要紧急空间回收时,走带审计的 bypass 流程(MFA + 审批)。
  • 月/年基线用 Compliance,谁都删不掉,连集群管理员也不行。
  • 分桶分前缀:按主机名或服务名做前缀 daily/web01/*,避免“列表可见性”干扰和权限错配。
  • 大对象优先:对海量小文件,尽量先归档为 tar 分片(例如 1~4GB),提升 S3 PUT 吞吐并减少 listing 的开销。
  • 演练写进时间表:不是“能备份”而是“能在 SLA 时间内恢复”。把 RTO/RPO 写入 runbook,并定期按它演练。

我遇到的坑 & 现场解法

坑 1:对象锁没在建桶时启用
复现:先 mc mb bk/daily,后面再想开对象锁→行不通。
解法:删除重建(或建新桶迁移),务必用 --with-lock 一次到位。

坑 2:时钟漂移导致删除“蒙混过关”
一次升级后,NTP 源切换,MinIO 與客户端各飘了 1~2 秒。极短时间窗口内,某些删除操作被错误接受。
解法:统一 NTP、加 makestep;同时增加外部作业每日验证“删除应失败”。

坑 3:误把控制台 9001 暴露到生产网
某次变更把 9001 暴露给了 jump 之外的网段。扫描器第二天就来打招呼。
解法:严格 ACL,仅管理网可达;在 MinIO 前放专用管理反代,启用 IP 白名单与 MFA。

坑 4:Restic 忘记设缓存目录
大量小文件下 CPU sys% 飙升。
解法:RESTIC_CACHE_DIR 指到 NVMe,显著降低 PUT 建连与索引开销。

验证与演练

1) 不可变性验证

# 上传、加保留,再试删除
mc cp /etc/hosts bk/monthly/test/hosts
mc retention set governance --period 30d bk/monthly/test/hosts
mc rm bk/monthly/test/hosts   # 预期:失败(409/AccessDenied)

2) 恢复演练(Restic)

. /etc/restic.env
# 列版本/快照
restic snapshots --host web01 --tag daily
# 还原到隔离目录
restic restore <SNAP_ID> --target /restore/web01/

3) 恢复演练(pgBackRest)

pgbackrest --stanza=pg-main restore --type=time "--target=2025-09-20 12:30:00" --delta

基线指标(示例)

指标 目标
日备完成时间 ≤ 2 小时
失败率(7 日滑窗) 0
恢复演练频率 每月一次(含抽样数据一致性校验)
删除验证 每日随机抽样 10 个对象,应全部失败

最终检查清单(上线前)

  •  生产网完全不可达 MinIO 的 9000/9001
  •  客户端只允许出向到 10.160.0.0/22:9000
  •  MinIO 桶 with-lock + version 开启
  •  Governance/Compliance 默认保留期已经设置且生效
  •  AK/SK 最小权限,无 DeleteObject
  •  Chrony 同步、时钟差 < 200ms
  •  监控与审计落地,删除应失败的作业已跑通
  •  恢复演练记录与 RTO/RPO 达标

把“备份”当成最后一道生产防线

凌晨那次,我们虽然“中招”,但因为备份网走的是独立物理通道、MinIO 桶是不可变的,攻击者够不到也删不掉。我从机房回酒店的路上想明白了一件事:备份不是“有一份就行”,而是要把它当成生产的一部分去设计、去演练、去守护。
如果你正在香港机房做这套方案,不妨就按上面的清单,一项一项落地。等哪天真的遇上“夜半惊魂”,你会庆幸今天做过的每一个小决定。

附:示例吞吐记录(取一台 Web 节点首备)

数据集 大小 文件数 用时 平均写入
/var/www 480 GB 2,130,000 1h52m ~4.3 Gbps
/etc+配置 1.9 GB 41,200 2m40s ~100 Mbps

注:10GbE 备份网,Restic 默认并发;启用 NVMe 缓存后小文件段用时下降约 23%。

参考命令速查(Cheat Sheet)

# MinIO
mc alias set bk https://s3-vip.bk.local:9000 admin pass
mc mb --with-lock bk/daily && mc version enable bk/daily
mc retention set --default governance --period 15d bk/daily
mc ilm add --noncurrent-expire-days 180 bk/daily

# Restic
export RESTIC_REPOSITORY=s3:https://s3-vip.bk.local:9000/daily/web01
export RESTIC_PASSWORD=...
export AWS_ACCESS_KEY_ID=...
export AWS_SECRET_ACCESS_KEY=...
restic init && restic backup /etc /var/www --tag daily
restic snapshots && restic restore <ID> --target /restore/

# pgBackRest
pgbackrest --stanza=pg-main --type=full backup
pgbackrest --stanza=pg-main restore --delta

——以上即为我在香港机房实际落地“备份网络隔离 + WORM 不可变对象存储”的完整实操与优化记录。

目录结构
全文