DockerSwarm集群取证

当我们成功仿真出 Docker Swarm 集群的各节点时,往往需要重新配置节点 IP 并恢复 Raft 仲裁,才能让集群重新跑起来,进而提取 service、secret、镜像等关键信息。与 PVE 不同,Swarm 的全部集群状态(节点、服务、密钥、网络)都保存在 manager 节点的 /var/lib/docker/swarm/ 下,拿到 manager 就等于拿到整个集群。

Docker Swarm 基础知识

什么是 Docker Swarm

Docker Swarm 是 Docker 内置的容器编排集群模式(SwarmKit),由 manager 和 worker 两类节点组成:

  • manager:负责集群管理与调度,彼此之间通过 Raft 一致性协议 同步集群状态(service 定义、secret、config、网络等)。Raft 状态落盘在 /var/lib/docker/swarm/raft/,只有 manager 才有。通常部署奇数个 manager(3/5 个)以保证仲裁。
  • worker:只负责运行容器,本地保存任务状态在 /var/lib/docker/swarm/worker/,不持有 Raft 数据。
  • 节点间通信使用 双向 TLS(mTLS),每个节点持有由集群根 CA 签发的证书(/var/lib/docker/swarm/certificates/),证书默认 90 天轮换一次。
  • 容器间跨节点通信走 overlay 网络(VXLAN,UDP 4789),可选 IPsec 加密。

取证要点速览

类别关键位置
集群总状态/var/lib/docker/swarm/(manager 含 raft/,worker 仅 worker/)
Raft 日志(WAL)/var/lib/docker/swarm/raft/wal-v3-encrypted/ 或 raft/wal/
Raft 快照/var/lib/docker/swarm/raft/snap-v3-encrypted/ 或 raft/snap/
Raft 状态/var/lib/docker/swarm/raft/raft-state.json
节点本地状态/var/lib/docker/swarm/docker-state.json、state.json
节点证书/var/lib/docker/swarm/certificates/(swarm-root-ca.crt、swarm-node.crt、swarm-node.key)
worker 任务库/var/lib/docker/swarm/worker/tasks.db(BoltDB)
Docker 主配置/etc/docker/daemon.json
镜像与容器/var/lib/docker/image/、/var/lib/docker/containers/
数据卷/var/lib/docker/volumes/<卷名>/_data/
容器日志/var/lib/docker/containers/<id>/<id>-json.log
daemon 日志journalctl -u docker.service(或 /var/log/syslog)

离线取证技巧:Raft 数据与 autolock

Raft 日志(WAL)与快照中的记录是 protobuf 编码 的,不能直接 cat 阅读,但可以先用 strings 粗筛出 service 名、镜像名、网络名等明文字段:

strings /var/lib/docker/swarm/raft/wal-v3-encrypted/*.log | less    # 未加密时是 wal/*.log
strings /var/lib/docker/swarm/raft/snap-v3-encrypted/* | less

manager 在线时,SwarmKit 自带的控制套接字 /var/run/docker/swarm/control.sock 可被 swarmctl(docker/swarmkit 项目)连接,用于导出节点/服务/密钥清单。

autolock(集群锁)是离线取证的最大障碍:若开启 autolock,Raft 日志与快照会被加密(目录名即 *-encrypted),manager 重启后必须输入 解锁密钥(unlock key) 才能载入集群状态。解锁密钥不落盘,只在开启时通过 docker swarm unlock-key 显示一次。取证时注意:

  • 优先在内存镜像、shell 历史、运维文档、密码管理器中搜索该密钥
  • 在线仿真时若 daemon 停在等待解锁状态,执行 docker swarm unlock 并粘贴密钥
  • 密钥丢失则该 manager 的 Raft 数据无法解密,只能依靠其他在线 manager 或 worker 的任务残留重建现场
docker swarm unlock          # 粘贴解锁密钥(仿真恢复时第一步)
docker swarm unlock-key      # 在线查看/确认当前解锁密钥

仿真环境网络配置

启动网卡并配置 IP

仿真出的节点网卡可能未启用,且 IP 往往与原集群不一致,需手动配置:

ip link set ens33 up
dhclient ens33                          # 或按原集群规划手动指定:
ip addr add 192.168.100.11/24 dev ens33

Swarm 节点间必须互通以下端口,防火墙/安全组需放行:

  • 2377/tcp —— 集群管理(Raft 通信)
  • 7946/tcp、7946/udp —— 节点发现(gossip)
  • 4789/udp —— overlay 网络数据面(VXLAN)
  • IP protocol 50 (ESP) —— 仅 overlay 开启加密时需要

恢复集群运行

仿真题中常见的情形是只有一台 manager 的镜像被导出,或多数 manager 失效导致 Raft 无法选主,集群 API(docker node ls 等)全部报 The swarm does not have a leader。在幸存的 manager 上用 force-new-cluster 强制以本地 Raft 数据重建单节点集群,原有 service/secret/config 定义会保留:

systemctl start docker
docker swarm init --force-new-cluster --advertise-addr 192.168.100.11    # 保留 Raft 状态重建集群
docker node ls                                                           # 验证自己成为 Leader
docker swarm join-token worker                                           # 重新生成 worker 加入令牌

其他 worker 节点 IP 变化后,用新令牌重新加入:

docker swarm leave                # 在 worker 上先脱离失效的旧集群
docker swarm join --token <新令牌> 192.168.100.11:2377

若节点证书因仿真日期漂移而过期(证书默认有效期 90 天),daemon 日志会出现 certificate has expired,此时需校准时间或轮换 CA(见时钟偏差一节)。

系统取证

基本系统信息

Docker 与 Swarm 版本

docker version             # client/server 版本、API 版本
docker info                # 最重要:Swarm 段给出 NodeID、是否 manager、Raft 参数、
                           # Managers/Nodes 数量、CA 轮换计数、autolock 状态
cat /etc/os-release
uname -a

docker info 输出中 Swarm 段的关键字段:

  • Is Manager: true + Managers: 3 —— 确认本机角色与管理面规模
  • Raft: Snapshot Interval / Heartbeat Tick / Election Tick —— Raft 调参痕迹
  • CA Config: Expiry Duration / Force Rotate —— 证书有效期与 CA 被强制轮换的次数
  • Encrypted Raft Log: true —— 即开启了 autolock

主机名与节点标识

hostnamectl
cat /etc/hostname
cat /etc/hosts                       # 常记录各节点 IP/主机名映射
cat /var/lib/docker/swarm/docker-state.json    # 本机 NodeID、JoinToken 等

时间服务器地址

cat /etc/chrony.conf                 # 或 /etc/chrony/chrony.conf,server/pool 行即时间源
cat /etc/systemd/timesyncd.conf      # systemd-timesyncd 环境
timedatectl                          # 时区与同步状态
chronyc sources                      # chrony 在线状态

时钟偏差

Swarm 对时钟敏感:Raft 选主依赖心跳超时,节点证书有明确的 NotBefore/NotAfter。仿真出来的多台节点时钟不一致,会导致选主失败或证书校验报错。处理流程:

# 1. 关闭自动同步,防止 NTP 与手动设置打架
timedatectl set-ntp false
systemctl stop chronyd 2>/dev/null; systemctl stop systemd-timesyncd 2>/dev/null

# 2. 手动统一时间(每台节点执行,保持一致)
timedatectl set-timezone Asia/Shanghai
timedatectl set-time "2024-01-01 12:00:00"
hwclock --systohc                    # 写入硬件时钟

# 3. 逐台确认时间一致
date; timedatectl

# 4. 检查节点证书有效期是否覆盖当前时间
openssl x509 -in /var/lib/docker/swarm/certificates/swarm-node.crt -noout -dates -subject
openssl x509 -in /var/lib/docker/swarm/certificates/swarm-root-ca.crt -noout -dates

# 5. 时间一致后重启 docker 恢复集群
systemctl restart docker

证书因时间漂移被判过期且无法回拨时间时,可在 Leader 上轮换 CA 重新签发:docker swarm ca --rotate(会触发全集群证书重签)。

集群信息

节点列表与角色

docker node ls                       # 节点 ID、主机名、Status、Availability、Manager Status(Leader/Reachable)
docker node inspect <节点> --pretty   # 节点详情:IP、标签、平台、加入时间
docker node inspect <节点> | jq '.[0].ManagerStatus'    # 是否 Leader、Raft 可达性

Raft 状态

docker node inspect self --pretty | grep -A3 "Manager Status"
cat /var/lib/docker/swarm/raft/raft-state.json    # 本地 Raft 硬状态(任期/投票,JSON)
ls -lh /var/lib/docker/swarm/raft/                # wal/ 或 wal-v3-encrypted/ 日志目录

raft-state.json 是明文 JSON,离线也能读;WAL 与快照为 protobuf(autolock 时加密),在线状态用 docker node ls/docker info 反映,离线可用 strings 粗筛内容。

集群证书与身份

ls /var/lib/docker/swarm/certificates/
# swarm-root-ca.crt  集群根 CA(所有节点互信的基础)
# swarm-node.crt     本节点证书:CN=NodeID,OU=swarm-manager 或 swarm-worker,O=Swarm 集群 ID
# swarm-node.key     本节点私钥(高价值,拿到可伪造节点身份)

openssl x509 -in /var/lib/docker/swarm/certificates/swarm-node.crt -noout -subject -issuer -dates
docker swarm join-token worker      # worker 加入令牌(含 CA 指纹与 manager 地址)
docker swarm join-token manager     # manager 加入令牌(泄露后可伪造 manager,高敏感)

工作负载取证

Service 与 Stack

docker service ls                        # 全部 service:副本数、镜像、端口发布
docker service inspect <svc> --pretty    # 完整定义:镜像、环境变量、挂载、网络、约束
docker service ps <svc>                  # 各 task 落在哪个节点、当前/期望状态、失败原因
docker stack ls                          # stack 列表
docker stack services <stack>            # stack 内 service 列表
docker stack ps <stack>                  # stack 全部 task 分布
docker stack config <stack>              # 还原出部署时的 compose 定义(需在 manager 在线时)

离线时 service 定义只能从 Raft 数据中提取(strings 粗筛或 swarmctl),因此仿真恢复 manager 是第一优先级。

容器取证

docker ps -a                             # 本节点全部容器(含已退出,task 完成后容器默认保留)
docker inspect <容器>                     # 挂载、环境变量、启动命令、网络、退出码
docker diff <容器>                        # 容器内文件系统相对镜像的增删改(快速定位落地文件)
docker logs <容器>                        # 单容器标准输出日志
docker export <容器> -o /导出目录/容器.tar  # 导出容器文件系统做静态分析

Secret 与 Config

docker secret ls                         # 全部 secret 名称与创建时间
docker secret inspect <secret>           # 只有元数据,value 永不回显(设计如此)
docker config ls
docker config inspect <config>           # config 未加密,Spec.Data 字段为 base64 明文,可直接解码
  • secret:密文保存在加密的 Raft 日志中,运行时以 tmpfs 形式注入容器 /run/secrets/<名称>。提取途径:在运行中的容器内 cat /run/secrets/<名称>,或 docker exec <容器> cat /run/secrets/<名称>;容器已退出则导出文件系统后查看。
  • config:明文存储,docker config inspect <config> --format '{{.Spec.Data}}' | base64 -d 直接还原。

镜像导出

docker image ls                          # 本节点全部镜像(含来源仓库与 tag)
docker save -o /导出目录/<镜像名>.tar <镜像:tag>    # 导出镜像做离线漏洞/恶意文件扫描
docker history <镜像:tag>                 # 构建层历史,常泄露构建期执行的命令

存储取证

/var/lib/docker 目录结构

路径内容
/var/lib/docker/swarm/Swarm 集群状态(见速览表)
/var/lib/docker/image/overlay2/镜像元数据(imagedb/、layerdb/)
/var/lib/docker/overlay2/镜像与容器的分层文件系统
/var/lib/docker/containers/<id>/容器配置(config.v2.json、hostconfig.json)与 json 日志
/var/lib/docker/volumes/<卷名>/_data/数据卷真实内容(重点取证对象)
/var/lib/docker/network/网络分配状态(files/local-kv.db)

数据卷取证

docker volume ls                         # 全部卷
docker volume inspect <卷名>              # 挂载点、驱动、创建时间
ls -l /var/lib/docker/volumes/<卷名>/_data/    # 直接读取卷内容(无需容器在线)
cp -a /var/lib/docker/volumes/<卷名>/_data /导出目录/    # 打包带走

overlay 网络与加密

docker network ls                        # 注意 ingress 与 docker_gwbridge 两个内置网络
docker network inspect <网络>             # 子网、网关、是否 encrypted、挂载的容器
  • docker network inspect 输出中 "com.docker.network.driver.overlay.vxlanid_list" 与各 service 的 VIP 可用于还原流量归属
  • 创建时带 --opt encrypted 的 overlay 走 IPsec 加密,密钥由 Raft/gossip 分发,抓包只能看到 ESP 密文
  • 网络侧取证命令:iptables -t nat -L -n(ingress 负载均衡规则)、bridge fdb show(VXLAN FDB)

用户与权限取证

本地访问控制

纯 Swarm 没有集群级用户体系(那是 UCP 的能力),能连上 docker socket 就等于集群管理员,取证关注本地账户:

cat /etc/group | grep docker             # docker 组成员:root 等价权限
getent passwd                            # 系统账户列表
ls -l /var/run/docker.sock               # socket 权限
cat /etc/docker/daemon.json              # 是否暴露 TCP 2375/2376(远程 API,重大风险点)
grep -r "tcp://" /etc/systemd/system/docker.service.d/ 2>/dev/null    # daemon 启动参数覆盖

节点身份与令牌

如「集群证书与身份」一节:节点身份即 swarm-node.crt 中的 NodeID 与 OU 角色;join-token 与节点私钥泄露等价于身份泄露。shell 历史中常见的敏感操作:

grep -i "swarm\|docker" /root/.bash_history
grep -ri "unlock\|join-token" /home/*/.bash_history /root/.bash_history 2>/dev/null

日志取证

集群工作负载日志(重点)

docker service logs <svc>                # 聚合该 service 全部 task 的日志(需 manager 在线)
docker service logs --raw <svc>          # 不带 task 前缀的原始输出,便于导出
docker service logs --since 24h <svc>
docker logs <容器>                        # 单容器日志(worker 节点本地即可执行)
cat /var/lib/docker/containers/<id>/<id>-json.log    # 离线直接读容器日志文件

daemon 与系统日志

journalctl -u docker.service --no-pager -e    # docker daemon 日志:选主失败、证书错误、解锁提示都在此
journalctl -u containerd.service
cat /var/log/syslog | grep dockerd            # 非 systemd 或 syslog 环境
cat /var/log/auth.log                         # SSH/PAM 登录记录
cat /var/log/faillog 2>/dev/null; last -F     # 登录历史

daemon 日志中的高价值事件:

  • raft: ... became leader / lost leader —— 选主与脑裂时间线
  • certificate 相关错误 —— 证书过期/伪造节点尝试加入
  • Waiting for the swarm to be unlocked —— 证明开启了 autolock
  • node join / node leave —— 节点变动审计

任务执行审计

Swarm 没有独立的操作审计日志,操作痕迹主要靠三处交叉还原:

  1. docker service ps <svc> --no-trunc —— task 的创建/失败/终止时间线与报错
  2. journalctl -u docker.service —— API 层面的 service 创建/更新/删除记录
  3. /var/lib/docker/swarm/raft/ 的 WAL 时间戳 —— 集群状态每次变更的落盘时间