reservoir-sandbox/backend

GitHub: reservoir-sandbox/backend

一个用于自动化 Linux ELF 恶意软件分析的后端服务,负责样本上传、分析任务编排与 Kubernetes worker 调度,为可扩展的恶意软件分析平台提供基础。

Stars: 1 | Forks: 0

# Reservoir 用于自动化 Linux 恶意软件分析的后端服务。 Reservoir 允许用户上传 ELF 二进制文件,将样本存储在兼容 S3 的存储中,管理分析作业和任务,并提供安全的基于 JWT 的身份验证。该项目作为可扩展恶意软件分析平台的后端基础,未来的 worker 将执行静态分析、沙箱执行和基于机器学习的分类。 ## 架构概述 ``` User │ ▼ FastAPI Backend │ ├── Authentication (JWT + Redis) ├── Sample Upload (ELF validation) ├── Job / Task Orchestration ├── Worker Callback API (internal) │ ├── PostgreSQL ├── Redis └── S3 Storage │ │ (1) backend launches one one-shot Job per task ▼ Kubernetes (Flux HelmRelease → Job) ┌───────────────┬───────────────┬───────────────┐ │ static worker │ sandbox worker│ ml worker │ └───────────────┴───────────────┴───────────────┘ │ (2) pull sample from S3, analyze, push report to S3 │ (3) POST result to backend callback (X-Worker-Token) ▼ FastAPI Backend ──► updates JobTask + recomputes Job status ``` **端到端流程** 1. 用户上传样本 → 后端将其存储在 S3 中,并创建一个包含三个 `JobTask`(STATIC、SANDBOX、ML)的 `Job`。 2. 后端通过 Flux `HelmRelease` 为每个任务启动一个 Kubernetes `Job`,并传递二进制文件的 S3 指针和回调 URL。 3. 每个 worker 从 S3 拉取二进制文件并进行分析,将报告上传到 S3(或返回一个小的内联 JSON 结果),然后向 backend callback endpoint 报告。 4. backend 持久化每个任务的结果并重新计算整个 `Job` 的状态。 ## 关键特性 * 基于 JWT 的身份验证,支持 refresh token 轮换 * ELF 二进制文件上传与验证 * 基于 SHA256 的样本去重 * 兼容 S3 的对象存储集成 * Job 和 task 编排系统 * 为分析 worker 启动 Kubernetes job(Flux `HelmRelease`) * 用于接收分析结果的内部 worker 回调 API * PostgreSQL 和 Redis 集成 * 健康检查与就绪探针 * Docker 和 Docker Compose 部署 * 包含 Black、Ruff、Mypy 和 Pytest 的 CI pipeline * 完全基于 FastAPI 和 SQLAlchemy 2.x 的异步架构 ## 已实现功能 - 带有版本化 API 前缀 `/api/v1` 的 FastAPI 应用 - 带有 access/refresh token 轮换的 JWT 身份验证: - 响应体中的 access token(15 分钟) - `HttpOnly` cookie 中的 refresh token(30 天) - 通过 Redis `GETDEL` 实现具有一次性使用语义的 refresh token 轮换 - 使用 Argon2 进行密码哈希 - 异步 SQLAlchemy + asyncpg - Alembic 迁移设置 - 集中化异常处理 + HTTP 异常映射 - 速率限制 middleware(SlowAPI,可配置) - CORS middleware - 健康检查:liveness (`/health/live`) 和 readiness (`/health/ready`) 探测 PostgreSQL 和 Redis - **样本(ELF 二进制文件)上传**: - 验证 ELF magic bytes `\x7fELF` - 文件大小限制(10 MB) - SHA256 去重 - 上传到兼容 S3 的存储(MinIO / AWS S3) - 将样本元数据存储在 PostgreSQL 中(size、sha256、object_name) - 通过 `user_samples` 关联表跟踪用户所有权 - **Job 和 Task 管理**: - 上传时,创建一个 `Job`(状态:pending)和三个 `JobTask` 实例(STATIC、SANDBOX、ML) - 每个任务都有自己的状态生命周期(pending → running → completed/failed) - Job 保留 started_at/finished_at 时间戳 - 每个 `JobTask` 可以存储分析结果:`report_object_name`(报告的 S3 key)和/或 `result`(小的内联 JSON) - **分析作业编排**: - 通过 `JOB_LAUNCHER` 设置选择可插拔的 `JobLauncher`: - `noop` — 仅记录启动 payload(本地开发/测试) - `k8s` — 每个任务创建一个 Flux `HelmRelease`(chart `charts/job-to-run`),以便 Flux 启动一个一次性的 Kubernetes `Job` - Kubernetes 配置在集群内加载,并为本地调试提供 kubeconfig 后备 - launcher 在应用生命周期内构建一次(类似于 DB/Redis/S3 客户端) - **Worker 回调 API** (`POST /api/v1/internal/tasks/{id}/callback`): - 通过 `X-Worker-Token` header 使用共享密钥进行身份验证(恒定时间比较) - 更新相应的 `JobTask`(状态、时间戳、报告指针/内联结果、错误) - 重新计算父 `Job` 的状态,并从其任务中推导出 `started_at`/`finished_at` - 并发回调在每个 job 中通过 `SELECT ... FOR UPDATE` 进行串行化,以避免 job 卡在 `running` 状态 - 使用 `aiobotocore` 的 S3 存储抽象(异步 S3 客户端) - 基于 Redis 的 refresh token - GitHub Actions CI:black、ruff、mypy、pytest - 使用伪造 CRUD 实现的 `AuthService` 和 `UserService` 单元测试 - 生产级 Dockerfile(多阶段构建、非 root 用户、健康检查) - 用于开发和生产的 Docker Compose 文件 ## 技术栈 | 组件 | 技术 | |------------------------|--------------------------------------------------| | 语言 | Python 3.12+ | | 框架 | FastAPI 0.136+ | | 数据库 ORM | SQLAlchemy 2.x (异步) + asyncpg | | 数据库 | PostgreSQL 16 | | 缓存 / Token 存储 | Redis 7 (redis-py 异步) | | 对象存储 | 兼容 S3 (通过 aiobotocore 使用 MinIO / AWS S3) | | 作业编排 | Kubernetes + Flux (`HelmRelease`),通过 `kubernetes` 客户端 | | 身份验证 | JWT (HS256),使用 httpOnly cookie | | 密码哈希 | Argon2 (passlib) | | Schema 验证 | Pydantic v2 (设置、模型、验证) | | 数据迁移 | Alembic | | 速率限制 | SlowAPI | | 测试 | pytest, pytest-asyncio, fakeredis, httpx | | Linting 和格式化 | Black, Ruff, Mypy | | CI/CD | GitHub Actions | | 容器化 | Docker, Docker Compose | ## 项目结构 ``` app/ ├── api/ # HTTP layer (incl. internal worker callback) ├── services/ # Business logic (incl. JobLauncher orchestration) ├── crud/ # Data access layer ├── models/ # Database models ├── schemas/ # API schemas ├── auth/ # Authentication & authorization ├── db/ # PostgreSQL, Redis, S3 ├── dependencies/ # Dependency injection (incl. worker token auth) ├── core/ # Config, security, logging └── utils/ # Shared helpers tests/ # Unit tests alembic/ # Database migrations ``` ## 身份验证流程 1. **注册** (`POST /api/v1/register`) — 创建一个新的用户账户(用户名、电子邮件、密码)。密码在存储前使用 Argon2 进行哈希处理。 2. **登录** (`POST /api/v1/login`) — 验证凭据,在 JSON body 中返回 `access_token`,并在 `HttpOnly` cookie 中设置 `refresh_token`。refresh token 的 JTI 存储在 Redis 中(TTL 等于其生命周期)。 3. **访问受保护的 endpoint** — 使用 `Authorization: Bearer ` header。该 token 包含 `sub`(用户 ID)、`role` 和 `type`("access")。 4. **刷新** (`POST /api/v1/refresh`) — 当 access token 过期时,使用 `refresh_token` cookie 获取一对新的 token。旧的 refresh token 通过 Redis `GETDEL` 被消耗(一次性使用),并签发一个新的 `refresh_token`。 5. **登出** (`POST /api/v1/logout`) — 撤销当前的 refresh token(从 Redis 中删除)并清除 cookie。 ### Token 详情 | Token | 位置 | 生命周期 | 包含 | |-----------------|----------------|-----------------|------------------------------------------| | `access_token` | 响应体 | 15 分钟(默认) | `sub`, `role`, `type`, `exp`, `iat`, `jti` | | `refresh_token` | HttpOnly cookie| 30 天(默认) | `sub`, `type`, `exp`, `iat`, `jti` | ## 样本上传与分析 Pipeline 流程 1. **用户上传文件** (`POST /api/v1/samples`,multipart,需要 Bearer token) 2. **验证** — 检查: - 文件大小 ≤ 10 MB - Magic bytes = `\x7fELF`(ELF 二进制文件) 3. 计算文件内容的 **SHA256 哈希** 4. **去重** — 检查具有相同 SHA256 的样本是否已存在于数据库中 - 如果 **存在**: - 检查此用户是否已拥有指向此样本的链接(通过 `user_samples` 表)。如果没有,则创建一个新的 `UserSample` 记录。 - 如果该样本有 **最近的(pending/running/completed)job**,则返回该 job(避免重复分析)。 - 否则,为现有样本创建一个新的 `Job`。 - 如果 **不存在**: - 将文件上传到 S3 bucket,key 为 `uploads/{sha256}` - 在 PostgreSQL 中创建 `Sample` 记录 - 创建 `UserSample` 记录 - 创建一个新的包含三个 `JobTask` 记录(STATIC、SANDBOX、ML)的 `Job` 5. **响应** 返回 `Job` 对象及其当前状态(`pending`)和相关时间戳。 6. **作业启动** — 对于每个新创建的 `Job`,backend 通过配置的 `JobLauncher` 为每个 `JobTask` 启动一个 Kubernetes job(见下文)。此启动发生在事务提交之后,以便 worker 可以立即进行回调。 ### 分析作业编排 backend 本身不运行分析;它将每个 `JobTask` 委托给 Kubernetes 中的一个短期 worker container。 - 启动在 `JobLauncher` 之后进行抽象,并由 `JOB_LAUNCHER` 环境变量选择: - `noop`(默认) — 仅记录启动 payload;非常适合本地开发和测试(不需要集群)。 - `k8s` — 每个任务创建一个 Flux `HelmRelease`(chart `charts/job-to-run`);然后 Flux 将其协调为一个一次性的 Kubernetes `Job`。 - 对于每个任务,backend 会将这些值发送到 chart 中: | 值 | 来源 | 描述 | |-------|--------|-------------| | `taskId` | `JobTask.id` | 标识任务;在回调 URL 中返回 | | `taskType` | `static` / `sandbox` / `ml` | 让 chart 选择 worker 镜像 | | `backendCallbackUrl` | `BACKEND_CALLBACK_URL` | 回调的基础 URL | | `s3EndpointUrl` | `S3_ENDPOINT_URL` | S3 endpoint | | `s3BucketName` | `S3_BUCKET_NAME` | S3 bucket | | `objectKey` | `Sample.object_name` (`uploads/{sha256}`) | 二进制文件的 S3 key | | `sha256` | `Sample.sha256` | 样本哈希 | ### Worker 回调协议 当一个 worker 完成时,它必须报告回来: ``` curl -X POST "$BACKEND_CALLBACK_URL/api/v1/internal/tasks/$TASK_ID/callback" \ -H "X-Worker-Token: " \ -H "Content-Type: application/json" \ -d '{"status":"completed","report_object_name":"reports//static.json"}' ``` body 可以携带报告指针(`report_object_name`)、一个小的内联 `result` 对象,或一个 `error`(带有 `"status":"failed"`);`started_at`/`finished_at` 是可选的 ISO-8601 时间戳。 ### Job / Task 模型 - **Job** — 表示样本的一次分析运行。状态生命周期:`pending` → `running` → `completed` / `failed`。`started_at`/`finished_at` 派生自其任务。 - **JobTask** — 作业内的单个分析步骤。类型:`STATIC`、`SANDBOX`、`ML`。每个任务都有自己的状态、开始/结束时间、可选的错误消息以及结果字段(`report_object_name`、`result`)。 - Task 和 job 的状态通过 worker 向内部回调 endpoint 报告来推进。同一个 job 的并发回调通过行锁进行串行化。 ## API Endpoints ### 概述 | 方法 | 路径 | 认证 | 描述 | |--------|-------------------------------|------------------|----------------------------------------------------| | GET | `/health/live` | 无 | Liveness 探针 | | GET | `/health/ready` | 无 | Readiness 探针(检查 PostgreSQL + Redis) | | POST | `/api/v1/register` | 无 | 注册新用户 | | POST | `/api/v1/login` | 无 | 登录(用户名/密码表单) | | POST | `/api/v1/refresh` | 无 | 轮换 refresh token,签发新的 access token | | POST | `/api/v1/logout` | 无 | 撤销 refresh token,清除 cookie | | GET | `/api/v1/about_me` | Bearer | 获取当前用户的个人资料 | | GET | `/api/v1/samples` | Bearer | 列出当前用户拥有的所有样本 | | POST | `/api/v1/samples` | Bearer | 上传 ELF 二进制文件以进行分析 | | DELETE | `/api/v1/samples/{id}` | Bearer | 删除用户-样本链接(移除) | | GET | `/api/v1/jobs/{id}` | Bearer | 通过 job ID 获取 job 详情(包含任务) | | POST | `/api/v1/internal/tasks/{id}/callback` | Worker token | 报告任务的分析结果(内部) | ### Endpoint 详情 #### `GET /health/live` 简单的 liveness 检查。无需身份验证。 **示例请求:** ``` curl -X GET "http://127.0.0.1:8000/health/live" ``` **示例响应(200 OK):** ``` { "status": "ok" } ``` #### `GET /health/ready` Readiness 探针。验证与 PostgreSQL 和 Redis 的连接。 **示例请求:** ``` curl -X GET "http://127.0.0.1:8000/health/ready" ``` **示例响应(200 OK):** ``` { "status": "ok", "services": { "postgres": "ok", "redis": "ok" } } ``` **当服务不可用时(503 Service Unavailable)的示例响应:** ``` { "status": "error", "services": { "postgres": "error", "redis": "ok" } } ``` #### `POST /api/v1/register` 创建一个新的用户账户。 **请求体** — JSON (`UserRegister` schema): | 字段 | 类型 | 约束 | 描述 | |----------|----------|---------------|-----------------| | username | string | 4–24 个字符 | 自动转为小写 | | email | string | 合法邮箱 | 用户的电子邮件地址 | | password | string | 8–24 个字符 | 明文密码(使用 Argon2 进行哈希处理) | **示例请求:** ``` curl -X POST "http://127.0.0.1:8000/api/v1/register" \ -H "Content-Type: application/json" \ -d '{"username":"john_doe","email":"john@example.com","password":"StrongPass123"}' ``` **示例响应(201 Created):** ``` { "id": 1, "username": "john_doe", "email": "john@example.com", "is_active": true, "role": "user", "created_at": "2025-03-28T12:00:00", "updated_at": "2025-03-28T12:00:00" } ``` **可能出现的错误:** - `409 Conflict` — 用户名或电子邮件已存在。 #### `POST /api/v1/login` 使用 `application/x-www-form-urlencoded`(标准 OAuth2 密码流程)通过用户名和密码进行身份验证。 设置名为 `refresh_token` 的 `HttpOnly` cookie,并在响应体中返回 access token。 **请求体** — 表单数据: | 字段 | 类型 | 描述 | |----------|--------|--------------------| | username | string | 用户名 | | password | string | 用户的密码 | **示例请求:** ``` curl -i -X POST "http://127.0.0.1:8000/api/v1/login" \ -H "Content-Type: application/x-www-form-urlencoded" \ -c cookies.txt \ -d "username=john_doe&password=StrongPass123" ``` **示例响应(200 OK) — Headers 将包含带有 refresh token 的 `Set-Cookie`:** ``` { "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer" } ``` **可能出现的错误:** - `401 Unauthorized` — 凭据无效。 #### `POST /api/v1/refresh` 使用有效的 refresh token(来自 cookie)换取新的 access/refresh 对。要求存在 `refresh_token` cookie。旧的 refresh token 将失效(一次性使用)。 **示例请求(使用之前登录的 cookie):** ``` curl -i -X POST "http://127.0.0.1:8000/api/v1/refresh" \ -b cookies.txt -c cookies.txt ``` **示例响应(200 OK):** ``` { "access_token": "eyJhbGciOiJIUzI1NiIs...", "token_type": "bearer" } ``` **可能出现的错误:** - `401 Unauthorized` — 缺少、过期或无效的 refresh token。 #### `POST /api/v1/logout` 从 Redis 撤销当前的 refresh token 并清除 `refresh_token` cookie。 **示例请求:** ``` curl -i -X POST "http://127.0.0.1:8000/api/v1/logout" \ -b cookies.txt -c cookies.txt ``` **示例响应(200 OK):** ``` { "message": "logout successfully!" } ``` #### `GET /api/v1/about_me` 获取当前已认证用户的资料。 **Headers:** `Authorization: Bearer ` **示例请求:** ``` curl -X GET "http://127.0.0.1:8000/api/v1/about_me" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." ``` **示例响应(200 OK):** ``` { "id": 1, "username": "john_doe", "email": "john@example.com", "is_active": true, "role": "user", "created_at": "2025-03-28T12:00:00", "updated_at": "2025-03-28T12:00:00" } ``` **可能出现的错误:** - `401 Unauthorized` — 缺少或无效的 token。 #### `GET /api/v1/samples` 列出当前用户拥有的所有样本。返回一个 `SampleListItem` 对象数组。 **Headers:** `Authorization: Bearer ` **示例请求:** ``` curl -X GET "http://127.0.0.1:8000/api/v1/samples" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." ``` **示例响应(200 OK):** ``` [ { "sample_id": 1, "filename": "malware.elf", "uploaded_at": "2025-03-28T12:05:00", "latest_job_id": 1, "latest_job_status": "pending" }, { "sample_id": 2, "filename": "benign.elf", "uploaded_at": "2025-03-28T12:10:00", "latest_job_id": 2, "latest_job_status": "completed" } ] ``` **可能出现的错误:** - `401 Unauthorized` — 缺少或无效的 token。 #### `POST /api/v1/samples` 上传 ELF 二进制文件以进行分析。文件将验证 ELF magic bytes,按 SHA256 去重,并存储在 S3 中。如果样本是新的,将创建一个带有三个任务的新 Job;否则可能返回现有的 Job。 **Headers:** `Authorization: Bearer ` **Body:** `multipart/form-data`,字段名为 `sample` **示例请求:** ``` curl -X POST "http://127.0.0.1:8000/api/v1/samples" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." \ -F "sample=@/path/to/malware.elf" ``` **示例响应(201 Created):** ``` { "id": 1, "sample_id": 1, "status": "pending", "created_at": "2025-03-28T12:15:00", "started_at": null, "finished_at": null } ``` **可能出现的错误:** - `400 Bad Request` — 文件不是 ELF(magic bytes 不匹配)。 - `413 Payload Too Large` — 文件超过 10 MB。 - `401 Unauthorized` — 缺少或无效的 token。 #### `DELETE /api/v1/samples/{id}` 删除当前用户与样本之间的链接(移除所有权)。样本本身(S3 对象 + 数据库记录)**不会**被删除。 **Headers:** `Authorization: Bearer ` **路径参数:** `id` — 整数,列表中的 `sample_id`。 **示例请求:** ``` curl -X DELETE "http://127.0.0.1:8000/api/v1/samples/1" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." ``` **示例响应(204 No Content):** — 空 body。 **可能出现的错误:** - `404 Not Found` — 样本不存在或不属于此用户。 - `401 Unauthorized` — 缺少或无效的 token。 #### `GET /api/v1/jobs/{id}` 获取特定 job 的详细信息,包括其任务(`JobTaskRead` 列表)。 **Headers:** `Authorization: Bearer ` **路径参数:** `id` — 整数,job ID。 **示例请求:** ``` curl -X GET "http://127.0.0.1:8000/api/v1/jobs/1" \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..." ``` **示例响应(200 OK):** ``` { "id": 1, "sample_id": 1, "status": "pending", "created_at": "2025-03-28T12:15:00", "started_at": null, "finished_at": null, "tasks": [ { "id": 1, "job_id": 1, "task_type": "STATIC", "status": "pending", "report_object_name": null, "result": null, "created_at": "2025-03-28T12:15:00", "started_at": null, "finished_at": null, "error": null }, { "id": 2, "job_id": 1, "task_type": "SANDBOX", "status": "pending", "report_object_name": null, "result": null, "created_at": "2025-03-28T12:15:00", "started_at": null, "finished_at": null, "error": null }, { "id": 3, "job_id": 1, "task_type": "ML", "status": "pending", "report_object_name": null, "result": null, "created_at": "2025-03-28T12:15:00", "started_at": null, "finished_at": null, "error": null } ] } ``` **可能出现的错误:** - `404 Not Found` — job 不存在或不属于此用户。 - `401 Unauthorized` — 缺少或无效的 token。 #### `POST /api/v1/internal/tasks/{id}/callback` 供分析 worker 报告任务结果的内部 endpoint。它**不**属于面向用户的 API,并使用共享密钥而非 JWT 进行身份验证。 **Headers:** `X-Worker-Token: `(必须等于 `WORKER_CALLBACK_SECRET`) **路径参数:** `id` — 整数,`JobTask` ID(作为 `taskId` 传递给 worker)。 **请求体** — JSON (`TaskCallback` schema): | 字段 | 类型 | 必需 | 描述 | |----------------------|-----------------|----------|--------------------------------------------------| | status | string | 是 | `completed` 或 `failed` | | report_object_name | string \| null | 否 | 上传报告的 S3 key | | result | object \| null | 否 | 小的内联 JSON 结果/结论 | | error | string \| null | 否 | 错误消息(通常带有 `status=failed`) | | started_at | datetime \| null| 否 | ISO-8601 UTC 分析开始时间 | | finished_at | datetime \| null| 否 | ISO-8601 UTC 分析结束时间 | **示例请求:** ``` curl -X POST "http://backend/api/v1/internal/tasks/1/callback" \ -H "X-Worker-Token: " \ -H "Content-Type: application/json" \ -d '{"status":"completed","report_object_name":"reports//static.json"}' ``` **示例响应(204 No Content):** — 空 body。 **可能出现的错误:** - `403 Forbidden` — 缺少或无效的 `X-Worker-Token`。 - `404 Not Found` — 任务不存在。 - `422 Unprocessable Entity` — 无效的 body(例如 `status` 不是 `completed`/`failed`)。 ## 错误模型 所有应用特定的错误都会返回一个包含 `{"detail": "..."}` 的 JSON body。 ### 常见 HTTP 状态码 | 状态码 | 错误 | 描述 | |--------|--------------------------------|--------------------------------------------| | 200 | OK | 操作成功 | | 201 | Created | 资源已创建(注册、上传样本) | | 204 | No Content | 资源删除成功 | | 400 | Bad Request | 无效的文件格式(非 ELF) | | 401 | Unauthorized | 凭据无效/token 过期或无效 | | 403 | Forbidden | 拒绝访问(角色权限不足) | | 404 | Not Found | 未找到用户或未找到资源 | | 409 | Conflict | 用户已存在 | | 413 | Payload Too Large | 文件超过最大大小 (10 MB) | | 429 | Too Many Requests | 超出速率限制 | | 500 | Internal Server Error | 意外的服务器错误 | ## 快速开始 ### 1. 前置条件 - Python 3.12+ - Poetry 2.0+ - PostgreSQL 16 正在运行 - Redis 7 正在运行 - 兼容 S3 的存储(例如 MinIO、AWS S3) ### 2. 安装 Poetry ``` pipx install poetry ``` ### 3. 安装依赖 ``` poetry install --with dev ``` ### 4. 配置环境 ``` cp .env.template .env ``` 根据你的配置编辑 `.env`。最低要求的变量: | 变量 | 必需 | 描述 | |------------------------|----------|------------------------------------------| | `DATABASE_URL` | 是 | `postgresql+asyncpg://user:pass@host:5432/dbname` | | `REDIS_URL` | 是 | `redis://host:6379/0` 或 `rediss://...` | | `ACCESS_SECRET` | 是 | 最少 32 个字符,用于签发 access token | | `REFRESH_SECRET` | 是 | 最少 32 个字符,用于签发 refresh token | | `S3_ACCESS_KEY` | 是 | S3 access key | | `S3_SECRET_KEY` | 是 | S3 secret key | | `S3_ENDPOINT_URL` | 是 | S3 endpoint(例如,用于 MinIO 的 `http://localhost:9000`) | | `S3_BUCKET_NAME` | 是 | S3 bucket 名称 | | `BACKEND_CALLBACK_URL` | k8s 需要 | worker 用于访问回调 API 的基础 URL | | `WORKER_CALLBACK_SECRET`| k8s 需要 | 通过 `X-Worker-Token` 验证的共享密钥(建议最少 32 个字符) | 可选变量:`DEBUG`, `CORS_ORIGINS`, `ACCESS_TOKEN_EXPIRE_M`, `REFRESH_TOKEN_EXPIRE_M`, `COOKIE_SECURE`, `COOKIE_SAMESITE`, `ENGINE_VERSION`, `JOB_LAUNCHER` (`noop` | `k8s`,默认为 `noop`),`JOBS_NAMESPACE`(默认为 `jobs`)。 ### 5. 运行数据库迁移 ``` poetry run alembic upgrade head ``` ### 6. 启动应用 ``` poetry run uvicorn app.main:app --reload --host 0.0.0.0 --port 8000 ``` Swagger UI:`http://127.0.0.1:8000/docs` \ ReDoc:`http://127.0.0.1:8000/redoc` ## 快速开始 ### 开发环境 1. 使用真实的密钥更新 `.env.docker`(参见上面的环境变量)。 2. 启动服务: ``` docker compose up --build ``` 3. 运行迁移: ``` docker compose run --rm app alembic upgrade head ``` Makefile 快捷方式: ``` make up # docker compose up --build make down # docker compose down make migrate # run migrations make makemigrations m="message" # create new migration make logs # follow logs ``` ### 生产环境 对于生产环境部署(例如在 VPS 上),请使用提供的生产环境 compose 文件: ``` docker compose -f docker-compose.prod.yml up -d --build ``` 此 compose 期望从 GitHub Container Registry 获取预构建的镜像,并正确填写 `.env` 文件。 ## 测试 运行所有测试: ``` poetry run pytest ``` 仅运行单元测试: ``` poetry run pytest tests/unit ``` 运行测试覆盖率(如果安装了 `coverage`): ``` poetry run coverage run -m pytest && poetry run coverage report ``` 测试套件使用 **伪造实现**(例如 `FakeUserCRUD`)来避免外部依赖,并使用 `fakeredis` 进行 Redis 模拟。 ## 开发工具 使用 Black 格式化代码: ``` poetry run black . ``` 使用 Ruff lint 代码: ``` poetry run ruff check . ``` 使用 Mypy 检查类型: ``` poetry run mypy app ``` ## 数据迁移 创建新迁移: ``` poetry run alembic revision --autogenerate -m "describe change" ``` 升级到最新版本: ``` poetry run alembic upgrade head ``` 回退一个版本: ``` poetry run alembic downgrade -1 ``` ## 当前局限性 - **没有监控堆栈** — 当前代码库中未部署 Prometheus、Grafana、ELK。 ## 许可证 MIT ## 作者 Rushan Shafeev — [GitHub](https://github.com/Lntck)
标签:AV绕过, DAST, ELF分析, FastAPI, 任务编排, 后端开发, 子域名突变, 恶意软件分析, 搜索引擎查询, 测试用例, 请求拦截, 逆向工具