更新架构文档

This commit is contained in:
yyhuni
2025-12-17 18:18:20 +08:00
parent de77057679
commit 379abaeca7
4 changed files with 490 additions and 0 deletions

View File

@@ -29,6 +29,8 @@
- [📖 技术文档](./docs/README.md) - 技术文档导航(🚧 持续完善中) - [📖 技术文档](./docs/README.md) - 技术文档导航(🚧 持续完善中)
- [🚀 快速开始](./docs/quick-start.md) - 一键安装和部署指南 - [🚀 快速开始](./docs/quick-start.md) - 一键安装和部署指南
- [🔄 版本管理](./docs/version-management.md) - Git Tag 驱动的自动化版本管理系统 - [🔄 版本管理](./docs/version-management.md) - Git Tag 驱动的自动化版本管理系统
- [📦 Nuclei 模板架构](./docs/nuclei-template-architecture.md) - 模板仓库的存储与同步
- [📖 字典文件架构](./docs/wordlist-architecture.md) - 字典文件的存储与同步
--- ---

View File

@@ -4,6 +4,8 @@
### 架构设计 ### 架构设计
- [版本管理架构](./version-management.md) - Git Tag 驱动的自动化版本管理系统 - [版本管理架构](./version-management.md) - Git Tag 驱动的自动化版本管理系统
- [Nuclei 模板架构](./nuclei-template-architecture.md) - 模板仓库的存储、同步、分发机制
- [字典文件架构](./wordlist-architecture.md) - 字典文件的存储、同步、分发机制
### 开发指南 ### 开发指南
- [快速开始](./quick-start.md) - 一键安装和部署指南 - [快速开始](./quick-start.md) - 一键安装和部署指南

View File

@@ -0,0 +1,229 @@
# Nuclei 模板管理架构
本文档介绍 XingRin 中 Nuclei 模板的存储、同步和使用机制。
## 目录结构
```
/opt/xingrin/nuclei-repos/
├── nuclei-templates/ # 官方模板仓库(按仓库名命名)
│ ├── .git/
│ ├── http/
│ ├── network/
│ └── ...
└── custom-repo/ # 自定义模板仓库
```
## 一、存储位置
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `NUCLEI_TEMPLATES_REPOS_BASE_DIR` | `/opt/xingrin/nuclei-repos` | 模板仓库根目录 |
每个模板仓库会在根目录下创建独立子目录,目录名由仓库名称 slugify 生成。
## 二、数据模型
```
NucleiTemplateRepo
├── id # 仓库 ID
├── name # 仓库名称(用于前端展示和 Worker 查询)
├── repo_url # Git 仓库地址
├── local_path # 本地克隆路径(自动生成)
├── commit_hash # 当前同步的 commit hash
└── last_synced_at # 最后同步时间
```
## 三、Server 端同步流程
1. 用户在前端添加模板仓库(填写名称和 Git URL
2. 点击「同步」触发 `NucleiTemplateRepoService.refresh_repo()`
3. 首次同步:`git clone --depth 1`(浅克隆,节省空间)
4. 后续同步:`git pull --ff-only`(快进合并)
5. 同步成功后更新数据库:`commit_hash``last_synced_at`
```
┌──────────────────────────────────────────────────────────────────────────┐
│ Server 容器 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 前端 UI │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 添加仓库 │ │ 同步仓库 │ │ 浏览模板 │ │ │
│ │ │ name + url │ │ 点击刷新 │ │ 目录树 │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │ │
│ └─────────┼───────────────────┼───────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ NucleiTemplateRepoViewSet │ │
│ │ POST /api/nuclei/repos/ | POST .../refresh/ │ │
│ └─────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ NucleiTemplateRepoService │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌────────────────────────────────┐ │ │
│ │ │ ensure_local_path()│ │ refresh_repo() │ │ │
│ │ │ 生成本地目录路径 │ │ 执行 Git 同步 │ │ │
│ │ └────────────────────┘ └───────────────┬────────────────┘ │ │
│ └────────────────────────────────────────────┼────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ 首次同步(无 .git │ │ 后续同步(有 .git │ │
│ └──────────┬──────────┘ └──────────┬──────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ ┌─────────────────────┐ │
│ │ git clone --depth 1 │ │ git pull --ff-only │ │
│ │ <repo_url> │ │ │ │
│ └──────────┬──────────┘ └──────────┬──────────┘ │
│ │ │ │
│ └──────────────┬─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ git rev-parse HEAD │ │
│ │ 获取当前 commit hash │ │
│ └──────────────────────────┬──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL 数据库 │ │
│ │ │ │
│ │ UPDATE nuclei_template_repo SET │ │
│ │ local_path = '/opt/xingrin/nuclei-repos/xxx', │ │
│ │ commit_hash = 'abc123...', │ │
│ │ last_synced_at = NOW() │ │
│ │ WHERE id = ? │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 文件系统 │ │
│ │ /opt/xingrin/nuclei-repos/ │ │
│ │ ├── nuclei-templates/ # 官方模板 │ │
│ │ │ ├── .git/ │ │
│ │ │ ├── http/ │ │
│ │ │ ├── network/ │ │
│ │ │ └── ... │ │
│ │ └── custom-repo/ # 自定义模板 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
│ git clone / pull
┌─────────────────────┐
│ GitHub / GitLab │
│ 远程 Git 仓库 │
└─────────────────────┘
```
## 四、Worker 端同步流程
Worker 执行扫描任务时,通过 `ensure_nuclei_templates_local()` 确保本地模板与 Server 版本一致:
1. 从数据库查询仓库记录,获取 `repo_url``commit_hash`
2. 检查本地是否存在仓库目录
- 不存在:`git clone --depth 1`
- 存在:比较本地 commit hash 与 Server 的 `commit_hash`
3. 如果 commit 不一致:`git fetch` + `git checkout <commit_hash>`
4. 返回本地模板目录路径,供 nuclei 命令使用
```
┌──────────────────────────────────────────────────────────────────────────┐
│ Worker 容器 │
│ │
│ ┌─────────────┐ │
│ │ 扫描任务 │ │
│ │ 开始执行 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ ensure_nuclei_ │ │ PostgreSQL │ │
│ │ templates_local() │─────▶│ 查询 NucleiTemplateRepo 表 │ │
│ │ │ │ 获取 repo_url, commit_hash │ │
│ └───────────┬─────────────┘ └─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ 检查本地 .git 目录 │ │
│ └───────────┬─────────────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────┐ ┌────────────┐ │
│ │ 不存在 │ │ 存在 │ │
│ └───┬────┘ └─────┬──────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────────┐ ┌─────────────────────┐ │
│ │ git clone │ │ 比较 commit hash │ │
│ │ --depth 1 │ │ local vs server │ │
│ └─────┬──────┘ └──────────┬──────────┘ │
│ │ │ │
│ │ ┌───────┴───────┐ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌──────────┐ ┌──────────────┐ │
│ │ │ 一致 │ │ 不一致 │ │
│ │ │ 直接使用 │ │ │ │
│ │ └────┬─────┘ └───────┬──────┘ │
│ │ │ │ │
│ │ │ ▼ │
│ │ │ ┌──────────────────┐ │
│ │ │ │ git fetch origin │ │
│ │ │ │ git checkout │ │
│ │ │ │ <commit_hash> │ │
│ │ │ └────────┬─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 返回本地模板目录路径 │ │
│ │ /opt/xingrin/nuclei-repos/<repo-name>/ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 执行 nuclei 扫描 │ │
│ │ nuclei -t /opt/xingrin/nuclei-repos/xxx/ -l targets.txt │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
## 五、版本一致性保证
- Server 同步时记录 `commit_hash`
- Worker 使用前检查本地 hash 是否与 Server 一致
- 不一致时自动同步到指定 commit
- 确保所有节点使用相同版本的模板
## 六、配置项
`docker/.env` 或环境变量中配置:
```bash
# Nuclei 模板仓库根目录
NUCLEI_TEMPLATES_REPOS_BASE_DIR=/opt/xingrin/nuclei-repos
```
## 七、常见问题
### Q: Worker 报错「未找到模板仓库」?
A: 需要先在 Server 端添加并同步模板仓库Worker 通过数据库查询仓库信息。
### Q: 如何添加自定义模板仓库?
A: 在前端「Nuclei 模板」页面点击添加,填写仓库名称和 Git URL然后点击同步即可。
### Q: 模板更新后 Worker 如何获取最新版本?
A: 在 Server 端点击「同步」更新模板Worker 下次执行扫描时会检测到 commit hash 不一致并自动同步。

View File

@@ -0,0 +1,257 @@
# 字典文件管理架构
本文档介绍 XingRin 中字典文件的存储、同步和使用机制。
## 目录结构
```
/opt/xingrin/wordlists/
├── common.txt # 通用字典
├── subdomains.txt # 子域名字典
├── directories.txt # 目录字典
└── ...
```
## 一、存储位置
| 配置项 | 默认值 | 说明 |
|--------|--------|------|
| `WORDLISTS_BASE_PATH` | `/opt/xingrin/wordlists` | 字典文件存储目录 |
## 二、数据模型
```
Wordlist
├── id # 字典 ID
├── name # 字典名称(唯一,用于查询)
├── description # 描述
├── file_path # 文件绝对路径
├── file_size # 文件大小(字节)
├── line_count # 行数
└── file_hash # SHA256 哈希值(用于校验)
```
## 三、Server 端上传流程
1. 用户在前端上传字典文件
2. `WordlistService.create_wordlist()` 处理:
- 保存文件到 `WORDLISTS_BASE_PATH` 目录
- 计算 SHA256 哈希值
- 统计文件大小和行数
- 创建数据库记录
```
┌──────────────────────────────────────────────────────────────────────────┐
│ Server 容器 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 前端 UI │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ 上传字典 │ │ 编辑内容 │ │ 删除字典 │ │ │
│ │ │ 选择文件 │ │ 在线修改 │ │ │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────────────┘ │ │
│ └─────────┼───────────────────┼───────────────────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ WordlistViewSet │ │
│ │ POST /api/wordlists/ | PUT .../content/ │ │
│ └─────────────────────────────┬───────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ WordlistService │ │
│ │ │ │
│ │ ┌────────────────────┐ ┌────────────────────────────────┐ │ │
│ │ │ create_wordlist() │ │ update_wordlist_content() │ │ │
│ │ │ 创建字典 │ │ 更新字典内容 │ │ │
│ │ └────────┬───────────┘ └───────────────┬────────────────┘ │ │
│ └───────────┼────────────────────────────────┼────────────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 处理流程 │ │
│ │ │ │
│ │ 1. 保存文件到 /opt/xingrin/wordlists/<filename> │ │
│ │ 2. 计算 SHA256 哈希值 │ │
│ │ 3. 统计文件大小和行数 │ │
│ │ 4. 创建/更新数据库记录 │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ PostgreSQL 数据库 │ │
│ │ │ │
│ │ INSERT INTO wordlist (name, file_path, file_size, │ │
│ │ line_count, file_hash) │ │
│ │ VALUES ('subdomains', '/opt/xingrin/wordlists/subdomains.txt', │ │
│ │ 1024000, 50000, 'sha256...') │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 文件系统 │ │
│ │ /opt/xingrin/wordlists/ │ │
│ │ ├── common.txt │ │
│ │ ├── subdomains.txt │ │
│ │ └── directories.txt │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
## 四、Worker 端获取流程
Worker 执行扫描任务时,通过 `ensure_wordlist_local()` 获取字典:
1. 根据字典名称查询数据库,获取 `file_path``file_hash`
2. 检查本地是否存在字典文件
- 存在且 hash 匹配:直接使用
- 存在但 hash 不匹配:重新下载
- 不存在:从 Server API 下载
3. 下载地址:`GET /api/wordlists/download/?wordlist=<name>`
4. 返回本地字典文件路径
```
┌──────────────────────────────────────────────────────────────────────────┐
│ Worker 容器 │
│ │
│ ┌─────────────┐ │
│ │ 扫描任务 │ │
│ │ 需要字典 │ │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ ┌─────────────────────────────────┐ │
│ │ ensure_wordlist_local() │ │ PostgreSQL │ │
│ │ 参数: wordlist_name │─────▶│ 查询 Wordlist 表 │ │
│ │ │ │ 获取 file_path, file_hash │ │
│ └───────────┬─────────────┘ └─────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────┐ │
│ │ 检查本地文件是否存在 │ │
│ │ /opt/xingrin/wordlists/ │ │
│ └───────────┬─────────────┘ │
│ │ │
│ ┌───────┴───────┐ │
│ │ │ │
│ ▼ ▼ │
│ ┌────────┐ ┌────────────┐ │
│ │ 不存在 │ │ 存在 │ │
│ └───┬────┘ └─────┬──────┘ │
│ │ │ │
│ │ ▼ │
│ │ ┌─────────────────────┐ │
│ │ │ 计算本地文件 SHA256 │ │
│ │ │ 与数据库 hash 比较 │ │
│ │ └──────────┬──────────┘ │
│ │ │ │
│ │ ┌───────┴───────┐ │
│ │ │ │ │
│ │ ▼ ▼ │
│ │ ┌──────────┐ ┌──────────────┐ │
│ │ │ 一致 │ │ 不一致 │ │
│ │ │ 直接使用 │ │ 需重新下载 │ │
│ │ └────┬─────┘ └───────┬──────┘ │
│ │ │ │ │
│ │ │ │ │
│ ▼ │ ▼ │
│ ┌─────────────┴─────────────────────────────────────────────────────┐ │
│ │ 从 Server API 下载 │ │
│ │ GET /api/wordlists/download/?wordlist=<name> │ │
│ │ │ │
│ │ ┌──────────┐ HTTP Request ┌──────────────────────┐ │ │
│ │ │ Worker │ ───────────────────────▶│ Server (Django) │ │ │
│ │ │ │◀─────────────────────── │ 返回文件内容 │ │ │
│ │ └──────────┘ File Content └──────────────────────┘ │ │
│ │ │ │
│ │ 保存到: /opt/xingrin/wordlists/<filename> │ │
│ └───────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 返回本地字典文件路径 │ │
│ │ /opt/xingrin/wordlists/subdomains.txt │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 执行扫描工具 │ │
│ │ puredns bruteforce -w /opt/xingrin/wordlists/xxx.txt │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
## 五、Hash 校验机制
- 上传时计算 SHA256 并存入数据库
- Worker 使用前校验本地文件 hash
- 不匹配时自动重新下载
- 确保所有节点使用相同内容的字典
## 六、本地 Worker vs 远程 Worker
本地 Worker 和远程 Worker 获取字典的方式相同:
1. 从数据库查询字典元数据file_hash
2. 检查本地缓存是否存在且 hash 匹配
3. 不匹配则通过 HTTP API 下载
**注意**Worker 容器只挂载了 `results``logs` 目录,没有挂载 `wordlists` 目录,所以字典文件需要通过 API 下载。
```
Worker本地/远程) Server
│ │
│ 1. 查询数据库获取 file_hash │
│─────────────────────────────────▶│
│ │
│ 2. 检查本地缓存 │
│ - 存在且 hash 匹配 → 直接使用│
│ - 不存在或不匹配 → 继续下载 │
│ │
│ 3. GET /api/wordlists/download/ │
│─────────────────────────────────▶│
│ │
│ 4. 返回文件内容 │
│◀─────────────────────────────────│
│ │
│ 5. 保存到本地缓存 │
│ /opt/xingrin/wordlists/ │
│ │
```
### 本地 Worker 的优势
虽然获取方式相同,但本地 Worker 有以下优势:
- 网络延迟更低(容器内网络)
- 下载后的缓存可复用(同一宿主机上的多次任务)
## 七、配置项
`docker/.env` 或环境变量中配置:
```bash
# 字典文件存储目录
WORDLISTS_PATH=/opt/xingrin/wordlists
# Server 地址Worker 用于下载文件)
PUBLIC_HOST=your-server-ip
SERVER_PORT=8888
```
## 八、常见问题
### Q: 字典文件更新后 Worker 没有使用新版本?
A: 更新字典内容后会重新计算 hashWorker 下次使用时会检测到 hash 不匹配并重新下载。
### Q: 远程 Worker 下载文件失败?
A: 检查:
1. `PUBLIC_HOST` 是否配置为 Server 的外网 IP
2. Server 端口(默认 8888是否开放
3. Worker 到 Server 的网络是否通畅
### Q: 如何批量导入字典?
A: 目前只支持通过前端逐个上传,后续可能支持批量导入功能。