Files
MeshSate/deploy/OPERATIONS.md
zhaolei 16f134e328 feat: MeshSafe 文件摆渡安全卫士 v1.0 初始提交
核心功能:
- 6 层检测管线: L0 结构分析 → L1 ClamAV → L2 文档深度检测 → L3 关键词匹配 → L4 YARA → L5 PE/二进制
- FastAPI + PostgreSQL + Redis + Docker Compose 架构
- 客户端 HMAC-SHA256 签名鉴权 + 限流 + 幂等性
- 控制台 UI (登录/Dashboard/诊断中心/策略管理/任务列表/测试中心/产品介绍页)
- 三层诊断定时器 (error_scan 30s / db_aggregate 30s / container_check 5min)

并发与稳定性修复:
- 诊断定时器 Redis 时间槽位锁 (多 worker 同一槽位只有一个执行)
- JSONL 存储 fcntl 跨进程排他锁 + 容错解码 + 原子重写
- 移除 SQLite fallback, 统一 PostgreSQL
- API_CLIENTS 脱敏为环境变量, .env 已 .gitignore
2026-10-28 19:07:11 +08:00

14 KiB
Raw Permalink Blame History

MeshSafe 部署操作手册

本文档是给运维/实施工程师直接照着干的。不讲原理,只讲"改什么、改成什么、敲什么命令"。仓库根目录在服务器上假设为 /opt/meshsafe(Windows 上就是 d:\project\MeshSafe)。下面统一用 /opt/meshsafe 指代。


第零步:先填这 5 个决策项

动手改配置之前先把这 5 个值定下来,不确定的问甲方。

# 决策项 填什么 怎么查
① 服务器 CPU 物理核数 一个整数,比如 8 Linux: `lscpu
② 有几个外部系统要对接扫描 API 比如 3 个:采购系统、档案系统、摆渡客户端 问甲方"哪些系统会调你家扫描接口"
③ 预计峰值并发(同时在扫的文件数) 比如 50 问甲方"忙的时候一般多少个文件同时扫"
④ Postgres 密码定什么 自己定一个 ≥ 16 位的随机串 自己想,不要用仓库默认的 meshsafe_db_pw_2026
⑤ 服务器内网 IP 比如 192.168.50.200 ip addr / ipconfig 查

例:某客户服务器 8 核,3 个对接系统,峰值并发 50,Postgres 密码 abc123xyz789qwe,内网 IP 192.168.50.200


第一步:生成密钥(3 条命令,复制就能跑)

在服务器上(或本地)跑:

# 1. MASTER_KEY(主密钥,所有 API 客户端密钥从它派生)
python -c "import base64, os; print(base64.b64encode(os.urandom(32)).decode())"
# 输出示例:F0CcBAXSk050fCbtf4jYwyaPW8dCkiUnsJZ7H2WPgFk=

# 2. ADMIN_PASSWORD(控制台默认管理员密码,16 位随机)
python -c "import base64, os; print(base64.b64encode(os.urandom(12)).decode())"
# 输出示例:SGVsbG9Xb3JsZDEyMzQ1Njc4

# 3. Postgres 密码(如果不想自己想,也可以生成)
python -c "import base64, os; print(base64.b64encode(os.urandom(16)).decode())"
# 输出示例:YWJjZGVmZ2hpamtsbW5vcHFyc3Q=

生成好后把这 3 个值记下来。MASTER_KEY 要离线备份(让两位管理员各自存一份,密码管理器 / 纸质都行)。


第二步:逐文件改

文件 1:.env(项目根目录下)

打开 /opt/meshsafe/.env,改下面几行:

行 当前内容 改成
11 MESHSAFE_MASTER_KEY=F0CcBAXSk050fCbtf4jYwyaPW8dCkiUnsJZ7H2WPgFk= 第一步生成的 MASTER_KEY
14 MESHSAFE_DB_PASSWORD=meshsafe_db_pw_2026 第四步的 Postgres 密码
19 MESHSAFE_DB_URL=postgresql+psycopg2://meshsafe:meshsafe_db_pw_2026@postgres:5432/meshsafe ⚠️ 密码要和第 14 行一致。把 URL 里的密码部分(meshsafe_db_pw_2026)替换成你实际的 Postgres 密码。完整形式:postgresql+psycopg2://meshsafe:你的Postgres密码@postgres:5432/meshsafe
23 MESHSAFE_ADMIN_PASSWORD=admin123 第一步生成的 ADMIN_PASSWORD
26 UVICORN_WORKERS=15 第一步的 CPU 物理核数
27 DB_POOL_SIZE=30 计算:物理核数 × 3。8 核填 24,16 核填 45
28 DB_MAX_OVERFLOW=50 计算:物理核数 × 5。8 核填 40,16 核填 80

上面示例(8 核服务器、密码 abc123xyz789qwe)的 .env 相关行:``` MESHSAFE_MASTER_KEY=你生成的key MESHSAFE_DB_PASSWORD=abc123xyz789qwe MESHSAFE_DB_URL=postgresql+psycopg2://meshsafe:abc123xyz789qwe@postgres:5432/meshsafe MESHSAFE_ADMIN_PASSWORD=你生成的admin密码 UVICORN_WORKERS=8 DB_POOL_SIZE=24 DB_MAX_OVERFLOW=40


文件 2:docker-compose.yml(项目根目录下)

打开 /opt/meshsafe/docker-compose.yml,改 1 行(api 服务的 environment 里,大概第 106 行):

当前:  - MESHSAFE_API_CLIENTS=test-client:test-secret,duandao-system-a:real-secret

改成:把"②有几个外部系统"里的每个系统,分配一对 client_id:client_secret,逗号隔开。

给每个对接系统生成 client_secret:

python -c "import base64, os; print(base64.b64encode(os.urandom(24)).decode())"

示例(3 个对接系统):

- MESHSAFE_API_CLIENTS=system-caigou:aBc123xyz789...,system-dangoaN:DeF456ghi012...,system-bodao:GhI789jkl345...

格式说明:client_id 用英文代号(不要中文、不要空格),client_secret 是上条命令生成的随机串,一对一对用英文逗号隔开。

其他都不要改! compose 里的 UVICORN_WORKERS、DB_POOL_SIZE、DB_MAX_OVERFLOW 这三行是从 .env 读取的,改 .env 就自动生效了。


文件 3:deploy/nginx/default.conf

打开 /opt/meshsafe/deploy/nginx/default.conf,可选改 IP 白名单。

不改也行(内网光闸场景)。改的话,在每个 location 块里加 allow 和 deny 行,格式:

location /console/ {
    allow 运维网段/掩码;      # 例如 allow 192.168.1.0/24;
    deny  all;                # 其他都拒绝
    proxy_pass http://api:8443;
    ... 其余不动 ...
}

location /api/ {
    allow 摆渡客户端固定IP;   # 例如 allow 192.168.50.100;
    deny  all;
    proxy_pass http://api:8443;
    ... 其余不动 ...
}

如果不知道有哪些 IP 要放行,先跳过这步——等测试通了再回来加。


第三步:启动

按顺序执行,每一步看状态对了再走下一步:

cd /opt/meshsafe

# 1. 建磁盘目录
mkdir -p data/postgres data/redis data/clamav data/logs data/temp data/diagnostics

# 2. 起基础设施(Postgres + Redis)
docker compose up -d postgres redis

# 3. 等它们 healthy
docker compose ps postgres redis
# 看到 "healthy" 再往下,否则等 10 秒再看

# 4. 起 ClamAV(病毒引擎)
# 注意:如果是光闸内网,ClamAV 签名库要先摆渡到 data/clamav/ 目录
# 首次启动如果 data/clamav 是空的会自动 freshclam(需要外网)
docker compose up -d clamav
# 等 2~5 分钟,因为 ClamAV 启动慢

# 5. 构建并起 API
docker compose up -d --build api

# 6. 初始化数据库(建表 + admin 账号 + 初始策略)
docker compose exec api python scripts/init_db.py
# 应该看到 "=== 初始化完成 ==="

# 7. 起 Nginx(最后)
docker compose up -d nginx

全绿检查:

docker compose ps
# 所有 service 状态应该是 Up (healthy) 或 Up

第四步:验证

# 1. 健康检查
curl http://服务器IP:44380/health
# 应该返回 {"status":"ok", ...}

# 2. 控制台(浏览器打开)
# http://服务器IP:44380/console/login
# 用 .env 里的 MESHSAFE_ADMIN_PASSWORD 登录(首次登录会强制改密码)

第五步:给对接方交付的东西

对 MESHSAFE_API_CLIENTS 里配的每一个 client_id:

交付物 内容 传递方式
client_id 例如 system-caigou 邮件/IM 明文
client_secret 对应的那个 secret 加密传(企业邮箱加密 / 内网加密聊天,不要明文发微信)
MASTER_KEY 不要给! 只在 MeshSafe 服务端用 —
API 地址 http://服务器IP:44380 明文
Python 签名示例 见下面"附件 A" 明文

给密钥时要提醒对方:secret 泄露或人员离职,立即通知你吊销(你从 MESHSAFE_API_CLIENTS 里删掉那一对,重启 api 即生效)。


第六步:日常操作命令速查

# 日志
docker compose logs -f api --tail 200      # 实时看 api 日志
docker compose logs -f nginx --tail 200    # 看 nginx 访问日志

# 重启
docker compose restart api                 # 改 .env 后必须重启
docker compose restart nginx               # 改 default.conf 后必须重启

# 更新 API 客户端密钥(改完 .env 或 compose 里的 MESHSAFE_API_CLIENTS)
docker compose restart api

# 诊断打包(生成 tar.gz 给研发看)
docker compose exec api python -m app.diagnostic.dumper

# 强制重建 admin 账号(忘了密码时用)
docker compose exec api python -c "
from app.db import SessionLocal
from app.models import ConsoleUser
db = SessionLocal()
db.query(ConsoleUser).filter_by(username='admin').delete()
db.commit()
db.close()
"
docker compose exec api python scripts/init_db.py

# 全停
docker compose down

# 全停并删数据(⚠️ 生产不要乱执行!会清掉所有扫描记录和策略历史)
docker compose down -v

附件 A:给对接方的 Python 签名示例

对方拿到 client_id + client_secret 后,用下面这个代码发扫描请求:

import os, time, hashlib, hmac, base64
import requests

# ====== 对接方填写这三个值 ======
CLIENT_ID = "你拿到的client_id"
CLIENT_SECRET = "你拿到的client_secret"
BASE_URL = "http://MeshSafe服务器IP:44380"
# ===============================

def hkdf_sha256(secret: bytes, salt: str, info: str, length: int = 32) -> bytes:
    """从 client_secret 派生请求签名密钥。不要改这行。"""
    prk = hmac.new(salt.encode(), secret, hashlib.sha256).digest()
    okm, block, counter = b"", b"", 1
    while len(okm) < length:
        block = hmac.new(prk, block + info.encode() + bytes([counter]), hashlib.sha256).digest()
        okm += block
        counter += 1
    return okm[:length]

def sign_request(method, path, query=None, body=b""):
    ts = str(int(time.time()))
    nonce = os.urandom(16).hex()
    body_hash = hashlib.sha256(body).hexdigest()
    qs = "&".join(f"{k}={v}" for k, v in sorted((query or {}).items()))
    signing_string = "\n".join([method.upper(), path, qs, body_hash, ts, nonce, CLIENT_ID])
    signing_key = hkdf_sha256(
        CLIENT_SECRET.encode(),
        CLIENT_ID,
        "meshsafe/api/v1/request-signing",
    )
    sig = hmac.new(signing_key, signing_string.encode(), hashlib.sha256).hexdigest()
    return {
        "X-MS-Client-Id": CLIENT_ID,
        "X-MS-Timestamp": ts,
        "X-MS-Nonce": nonce,
        "X-MS-Body-SHA256": body_hash,
        "X-MS-Signature": sig,
    }

# ====== 实际使用 ======
# 场景 1:扫一个文件
with open("待扫描的文件.exe", "rb") as f:
    body = f.read()

headers = sign_request(
    "POST",
    "/api/v1/scans/submit",
    query={"channel": "caiGou系统"},   # 可选,标识来源
    body=body,
)
r = requests.post(f"{BASE_URL}/api/v1/scans/submit?channel=caiGou系统",
                  headers=headers, data=body)
print(r.status_code, r.json())
# 期望:200 + {"verdict": "malicious"/"pass", "score": ..., "action": ...}

附件 B:验签失败对照(对接方报错时查这里)

HTTP 状态 detail 内容 原因
401 缺少必要的认证请求头 5 个 X-MS-* 头少了哪个
401 认证失败 client_id 写错了?secret 错了?我方 MASTER_KEY 换了没通知?
401 请求时间戳超出允许偏差 你方服务器和 MeshSafe 服务器时间差超过 5 分钟了(NTP 对时)
401 重复请求,已拒绝 同一个 nonce 用了两次(每次必须生成新 nonce)
429 请求过于频繁 你方调用超过 120 次/分钟/客户端。降速或让我方调大限流