-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
126 lines (106 loc) · 6.84 KB
/
Copy pathDockerfile
File metadata and controls
126 lines (106 loc) · 6.84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# ===== 构建阶段 =====
# ARG 统一基础镜像引用:两阶段使用同一基础镜像,便于维护升级和 digest 固定。
# 供应链安全修复(2026-07):从可变 tag `python:3.12-slim` 改为不可变 tag
# `python:3.12.13-slim-bookworm`(锁定 Python 3.12.13 + Debian bookworm)。
# 可变 tag 可被 Docker Hub 重指向(如强制推送恶意镜像层),不可变 tag 一经
# 发布永不变化,杜绝 tag 篡改攻击。
# 生产环境最终建议:使用 `python:3.12.13-slim-bookworm@sha256:<digest>` 双重锁定,
# 通过 Renovate bot 自动跟踪 digest 更新(参考 .github/renovate.json)。
ARG PYTHON_BASE=python:3.12.13-slim-bookworm
FROM ${PYTHON_BASE} AS builder
WORKDIR /app
# 安装系统依赖
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY requirements.txt requirements.lock ./
# 过滤 Windows-only 依赖(pywin32、win32_setctime)
# 这些包是 loguru 等在 Windows 平台的传递依赖,在 Linux 容器中无法安装
# (PyPI 无 Linux wheel,报错 "Could not find a version that satisfies the
# requirement pywin32")。requirements.lock 由 CI 通过 pip-compile
# --generate-hashes 自动生成,手动修改会被下次生成覆盖,因此在 Dockerfile
# 构建期过滤更稳健。
# awk 逻辑:匹配 ^(pywin32|win32_setctime)== 起始行时,进入跳过模式,
# 持续丢弃所有以 --hash= 开头的续行,直到遇到下一个包的定义行。
# 修复:原 getline; next 仅跳过1行,多行 --hash 格式会导致残留 --hash 行。
RUN awk '/^(pywin32|win32_setctime)==/{skip=1; next} skip==1 && /^[[:space:]]*--hash=/{next} {skip=0; print}' requirements.lock > requirements.linux.lock
# 安装运行时依赖到独立目录(仅运行时依赖,不含 dev 依赖)
# dev 依赖(pytest/ruff/mypy)不应进入生产镜像
# 供应链安全修复(2026-07):使用 --require-hashes 校验每个包的 SHA256,
# 防止攻击者控制 PyPI 镜像注入恶意 wheel。
# requirements.linux.lock 是 requirements.lock 过滤 Windows-only 包后的副本,
# 仍包含所有直接依赖和传递依赖的精确版本 + hash,--require-hashes 校验不受影响。
# 原约束文件模式(-c requirements.lock)只约束版本不校验 hash,无法防供应链攻击。
RUN pip install --no-cache-dir --target=/app/deps --require-hashes -r requirements.linux.lock
# ===== 运行阶段 =====
FROM ${PYTHON_BASE}
LABEL maintainer="ConvergeLoop Team <dev@convergeloop.dev>"
LABEL description="ConvergeLoop - 通用型AI对抗性验证引擎"
WORKDIR /app
# 从构建阶段复制依赖
# 复制到 /app/deps 并通过 PYTHONPATH 加载,避免硬编码 python3.12 site-packages 路径
# (基础镜像升级到 3.13+ 时路径会失效,PYTHONPATH 方案跨版本兼容)
COPY --from=builder /app/deps /app/deps
# 安全修复(2026-07):将 /app/deps 设为只读,防止 Python 模块劫持。
# 漏洞:PYTHONPATH=/app/deps:/app 使 /app/deps 优先于标准库被搜索。
# 若攻击者通过应用漏洞(如文件写入 API、sandbox 逃逸)在 /app/deps 写入
# 恶意 site.py / os.py / sys.py,Python 启动时自动 import site 会加载
# 攻击者的 site.py,实现持久化代码执行(每次 python 启动都触发)。
# chmod -R a-w 移除写权限,root 属主 + convergeloop 用户无写权限,
# 即使应用被攻破也无法修改依赖目录中的 .py 文件。
# bin/ 下的入口脚本也移除写权限,防止篡改 CLI 入口。
# 安全:chmod 链式命令中 || true 仅限 /app/deps/bin(可能不存在),
# 用括号限定范围防止前两条 chmod 失败时也被 || true 吞掉错误。
RUN chmod -R a-w /app/deps && \
find /app/deps -type d -exec chmod a-w {} + && \
(chmod -R a-w /app/deps/bin 2>/dev/null || true)
# 运行阶段也需 git:server.py 的 /api/v1/health 和 /api/v1/ready 端点通过
# shutil.which("git") 校验 git 可用性,缺失时返回 503。docker-compose.yml
# healthcheck 调用 /api/v1/ready,git 缺失会触发无限重启循环
# (restart: unless-stopped + retries:3 + start_period:40s)。
# 这是可运行性维度仅 40 分的核心根因——容器无法健康运行。
# 层缓存优化:git 安排在 COPY 项目代码之前,避免每次代码变更都重装 git
# (apt 层变更频率低,放前面可被 Docker 缓存命中)
RUN apt-get update && apt-get install -y --no-install-recommends git \
&& rm -rf /var/lib/apt/lists/*
# 复制项目代码(不复制 tests/,生产环境不需要测试代码)
COPY convergeloop/ ./convergeloop/
COPY pyproject.toml ./
COPY start_mcp.sh ./
RUN chmod +x start_mcp.sh
# 复制 main.py 入口文件(CLI 入口 python -m convergeloop.cli 不依赖 main.py,
# 但 README 文档中的 docker run 示例使用 python main.py,缺失会导致用户按文档
# 操作时容器立即崩溃)
COPY main.py ./
# 创建非 root 用户
RUN useradd -m -u 1000 convergeloop
# 预创建会话/日志/报告目录并赋权给 convergeloop 用户。
# docker-compose.yml 将命名卷挂载到这些路径,Docker 首次创建命名卷时
# 目录属主为 root,非 root 的 convergeloop 用户无法写入,导致
# /api/v1/ready 的 session store 写探针失败 → 503 → 容器永久 unhealthy。
RUN mkdir -p /home/convergeloop/.convergeloop/sessions \
/home/convergeloop/.convergeloop/logs \
/home/convergeloop/.convergeloop/reports \
&& chown -R convergeloop:convergeloop /home/convergeloop/.convergeloop
USER convergeloop
# 设置环境变量
ENV PYTHONUNBUFFERED=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONPATH=/app/deps:/app
ENV CONVERGELOOP_CONFIG_PATH=/app/convergeloop/config
# 健康检查:默认使用 /api/v1/ready 端点(server.py 已实现该端点)。
# docker-compose.yml 的 healthcheck 会覆盖此默认值(compose 优先级更高),
# 但裸 docker run 场景也能获得基础健康监测,避免容器假死无法感知。
# start-period=40s 给服务启动留足时间,interval=30s + timeout=5s + retries=3。
# 端口 8080 对应 main.py 的 _DEFAULT_API_PORT 和 .env.example 的 CONVERGELOOP_API_PORT。
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8080/api/v1/ready', timeout=3)" || exit 1
# 声明容器监听端口(文档用途,不实际发布端口)。
# docker run -P 自动映射、docker inspect 端口发现、Kubernetes Service targetPort
# 都依赖此声明。与 CONVERGELOOP_API_PORT 默认值 8080 保持一致。
EXPOSE 8080
# 默认入口(使用 python -m 确保模块解析,避免 --target 安装的 bin/ 不在 PATH)
# 默认启动 API 服务(裸 docker run 即可启动),docker-compose.yml 的 command: ["server"] 保持一致
ENTRYPOINT ["python", "-m", "convergeloop.cli"]
CMD ["server"]