WechatOnCloud/doc/安全加固.md
Kris e35f1d64cf feat: 初始化项目,添加完整的微信云部署方案与面板实现
包含以下核心内容:
1.  完整的Docker容器化部署方案,支持多架构amd64/arm64
2.  自研Web管理面板,包含登录认证、实例管理、权限控制
3.  微信实例容器镜像,内置中文字体与输入法修复
4.  飞牛OS应用打包适配
5.  完整的文档与运维指南
2026-07-05 03:50:30 +08:00

167 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# WechatOnCloud 安全加固指南
本文介绍两项可选的安全加固措施:**Docker Socket 代理**和**镜像 Digest 固定**。两者均为自愿启用,不影响默认部署行为。
---
## 一、Docker Socket 代理
### 1.1 威胁面分析
默认配置中,面板容器直接挂载了宿主的 `/var/run/docker.sock`
```yaml
volumes:
- /var/run/docker.sock:/var/run/docker.sock
```
对任意能访问这个 socket 的进程而言,这等同于**宿主 root 权限**
- 可以创建任意容器并挂载宿主任意目录(如 `/`、`/etc`、`/home`
- 可以以 `--privileged` 模式启动容器逃逸到宿主
- 可以停止、删除宿主上所有现有容器
- 可以通过 `exec` 向任意已运行容器注入命令
攻击路径示例:面板存在 Web 漏洞(如 RCE、SSRF、依赖链投毒→ 攻击者控制面板进程 → 通过裸 socket 调用 `POST /containers/create`(挂载 `/ ` 并运行 `chroot` shell→ 获得宿主 root Shell。
> **关键点**:这不是面板代码本身是否安全的问题,而是一旦面板被攻陷,攻击者能利用 socket 做什么的问题。最小化 socket 暴露面是纵深防御的标准做法。
### 1.2 缓解方案docker-socket-proxy
`docker-compose.secure.yml` overlay 在面板与宿主 socket 之间插入一个 **[docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy)**[Tecnativa](https://github.com/Tecnativa)
```
面板 ──TCP 2375──▶ docker-socket-proxy ──Unix socket──▶ /var/run/docker.sock
(允许列表过滤) (只读挂载)
```
代理仅透传面板实际需要的端点,拒绝其余所有 Docker API 调用:
| 权限 | 状态 | 原因 |
|------|------|------|
| `CONTAINERS` | ✅ 开启 | 容器生命周期 + 日志 + 统计 + 文件存取 |
| `EXEC` | ✅ 开启 | 面板通过 exec 触发应用安装、文件管理、中文输入、设备 ID 重置 |
| `IMAGES` | ✅ 开启 | 实例镜像拉取与检查 |
| `VOLUMES` | ✅ 开启 | 孤儿卷列举与清理 |
| `POST` | ✅ 开启 | 所有写操作create/start/exec-start/pull均需 POST |
| `INFO` | ✅ 开启 | 诊断包展示 Docker 版本与资源概况 |
| `BUILD / COMMIT / SWARM / SERVICES / SECRETS / PLUGINS / NODES / NETWORKS / ...` | ❌ 关闭 | 面板不需要,一律拒绝 |
同时overlay 将面板对裸 socket 的直接挂载替换为 `/dev/null`,从文件系统层面阻断直接访问。
### 1.3 残余风险
代理按**端点**过滤,而非按**请求体内容**过滤。这意味着:
- 代理无法阻止面板通过 `POST /containers/create` 创建带 `--privileged` 或任意目录挂载的容器
- `EXEC=1` 意味着可向 woc-wx-* 容器注入任意命令(这些容器本身已是不可信的用户进程,风险相对可接受;但若代理本身被绕过则影响扩大)
- 代理容器自身持有只读 socket若代理镜像被供应链攻击保护失效
因此,本方案将攻击者在面板被攻陷后能做的事情从"获得宿主 root"降低到"有限制地操作 Docker 资源",但并非完全消除风险。
### 1.4 使用方法
```bash
# 启动overlay 叠加在默认 compose 之上)
docker compose -f docker-compose.yml -f docker-compose.secure.yml up -d
# 更新
docker compose -f docker-compose.yml -f docker-compose.secure.yml pull
docker compose -f docker-compose.yml -f docker-compose.secure.yml up -d
# 停止
docker compose -f docker-compose.yml -f docker-compose.secure.yml down
```
验证代理是否生效(面板应通过代理访问 Docker而非裸 socket
```bash
# 代理容器日志中能看到面板的 API 请求
docker logs woc-socket-proxy --follow
# 确认面板容器内 /var/run/docker.sock 已被替换为 /dev/null非 socket 文件)
docker exec woc-panel file /var/run/docker.sock
# 预期输出:/var/run/docker.sock: character special (1/3)
```
---
## 二、镜像 Digest 固定
### 2.1 为什么要固定 Digest
使用 `:latest` 或具体版本标签(如 `:v1.2.3`)时,存在以下风险:
- **标签是可变的**:镜像仓库维护者可以将同一个标签重新指向不同的镜像层
- **供应链攻击**:若 GHCR/Docker Hub 账号被盗,攻击者可以推送携带恶意代码的同名镜像
- **浮动标签**`latest` 每次 `docker pull` 都可能拉取到不同内容
**Digest`sha256:...`)是镜像内容的加密哈希,固定后任何修改都会导致 digest 不匹配,拉取失败。**
### 2.2 固定方法
**第一步:查看当前镜像的 digest**
```bash
# 拉取后查看本地 digest
docker pull ghcr.io/gloridust/woc-panel:latest
docker inspect ghcr.io/gloridust/woc-panel:latest \
--format '{{index .RepoDigests 0}}'
# 或从仓库直接查询(无需拉取)
docker buildx imagetools inspect ghcr.io/gloridust/woc-panel:latest \
| grep Digest
```
**第二步:在 `.env` 中固定版本**
```bash
# .env
WOC_VERSION=latest # 改为具体 digest
# 推荐写法(在 docker-compose.yml 里 image 字段支持 @digest 语法)
# WOC_PANEL_IMAGE=ghcr.io/gloridust/woc-panel@sha256:abcdef1234...
# WOC_WECHAT_IMAGE=ghcr.io/gloridust/wechat-on-cloud@sha256:abcdef5678...
```
**第三步:固定 digest 的 compose 示例**
```yaml
services:
panel:
image: ghcr.io/gloridust/woc-panel@sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
```
或在 `docker-compose.yml` 同目录维护一个 `digests.env`
```bash
# digests.env加入 .gitignore每次发版后手动更新
WOC_PANEL_DIGEST=sha256:aaaa...
WOC_WECHAT_DIGEST=sha256:bbbb...
```
### 2.3 更新流程建议
固定 digest 后,更新时需要三步:
```bash
# 1. 查看新版 digest
docker buildx imagetools inspect ghcr.io/gloridust/woc-panel:latest | grep Digest
# 2. 更新 digests.env 或 docker-compose.yml 中的 digest 值
# 3. 验证并拉起
docker compose pull # 拉取时 docker 会验证 digest
docker compose up -d
```
> **提示**:可以在 GitHub 上 Watch 本仓库的 Release在收到新版通知后再更新 digest避免被动跟随 `latest`。
---
## 参考
- [Tecnativa/docker-socket-proxy](https://github.com/Tecnativa/docker-socket-proxy) — Docker Socket 代理实现
- [Docker Engine API](https://docs.docker.com/engine/api/) — 端点权限参考
- [Docker Content Trust](https://docs.docker.com/engine/security/trust/) — Docker 官方镜像签名方案(进阶)