369 lines
11 KiB
Markdown
369 lines
11 KiB
Markdown
# Nexus 安全巡检阶段 4:Settings/Auth 与宝塔长期会话边界审查
|
||
|
||
时间:2026-07-07
|
||
对象:测试机 `/opt/nexus-dev-current`,分支 `audit/security-review`
|
||
最新提交:`524acd5 fix-stop-exposing-global-api-key-in-settings`
|
||
|
||
> 本报告不输出任何密码、API Key、Token、Cookie、PEM 内容。
|
||
|
||
---
|
||
|
||
## 1. 本轮确认结果
|
||
|
||
### 1.1 全局 API Key 不显示:已完成代码层修复
|
||
|
||
只读检查结果:
|
||
|
||
- 后端 `/api/settings/api-key/reveal` 已删除。
|
||
- 前端 SettingsPage 中全局 API Key 显示/复制逻辑已删除。
|
||
- 源码残留仅有:
|
||
- `server/api/servers.py` 两处注释仍提到旧名字 `reveal_api_key`。
|
||
- `frontend/src/utils/auditLabels.ts` 仍有旧审计 label `reveal_api_key: 查看 API 密钥`。
|
||
|
||
安全判断:
|
||
|
||
- 残留注释/label 不会造成 API Key 暴露。
|
||
- 建议后续做一次小清理,避免误导维护者和审计日志翻译。
|
||
|
||
### 1.2 当前代码没有全局 API 限速
|
||
|
||
沿用上一份报告结论:
|
||
|
||
- 没有 slowapi / fastapi-limiter / nginx limit_req 一类全局限速。
|
||
- 429 主要来自:
|
||
- 管理员登录失败锁定:5 次 / 15 分钟。
|
||
- Agent 脚本回调:job 10 次 / 60 秒,IP 60 次 / 60 秒。
|
||
- 大多数“速度限制”是远程任务并发保护,不是 HTTP API 限速。
|
||
|
||
---
|
||
|
||
## 2. 宝塔 2 小时后刷新掉线:当前代码仍有一个关键边界
|
||
|
||
### 2.1 当前一键登录链路
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant UI as Nexus 前端
|
||
participant API as /api/btpanel/servers/{id}/login-url
|
||
participant SVC as BtPanelService.create_login_url
|
||
participant Redis as Redis login_url_lock
|
||
participant SSH as ssh_bootstrap_panel
|
||
participant BT as 宝塔面板
|
||
|
||
UI->>API: 请求一键登录 URL
|
||
API->>SVC: create_login_url(server_id)
|
||
SVC->>Redis: 获取每台服务器分布式锁
|
||
SVC->>SSH: best-effort 检查/patch 宝塔 task.py session cleanup
|
||
SVC->>BT: config?action=set_temp_login(expire_time=now+86400)
|
||
BT-->>SVC: 返回 tmp_token 或登录 URL
|
||
SVC-->>UI: /login?tmp_token=...
|
||
```
|
||
|
||
### 2.2 已修复的问题
|
||
|
||
当前代码已经解决:
|
||
|
||
1. `tmp_token` 使用 86400 秒 TTL,而不是宝塔默认较短 TTL。
|
||
2. 登录 URL 不缓存,因为 `tmp_token` 是一次性链接。
|
||
3. 对同一台服务器生成登录 URL 时使用 Redis 锁,避免并发覆盖。
|
||
4. 登录前会 best-effort patch 宝塔 `/www/server/panel/task.py`:把硬编码 `if f_time > 3600:` 改成读取 `public.get_session_timeout() or 86400`。
|
||
|
||
### 2.3 仍可能导致“2 小时后刷新掉线”的关键点
|
||
|
||
当前 patch 的语义是:
|
||
|
||
```python
|
||
session_cleanup_timeout = int(public.get_session_timeout() or 86400)
|
||
if f_time > session_cleanup_timeout:
|
||
删除 session 文件
|
||
```
|
||
|
||
这意味着:
|
||
|
||
- 如果宝塔面板里已经配置了 session timeout,例如 7200 秒(2 小时),patch 会尊重这个值。
|
||
- `or 86400` 只在没有配置值时生效。
|
||
- 所以用户遇到“打开 10 个宝塔,2 小时后陆续刷新掉线”,仍可能是目标宝塔的 `session_timeout` 本身就是 7200 秒。
|
||
|
||
结论:
|
||
|
||
> 现在的修复主要解决 `task.py` 硬编码 3600 秒清理问题,但不一定强制把宝塔会话生命周期提升到 24 小时。如果目标面板配置仍是 2 小时,会继续 2 小时后掉线。
|
||
|
||
### 2.4 并发打开 10 个宝塔的边界
|
||
|
||
当前 Redis 锁可以保证同一台服务器不会同时生成多个互相覆盖的 `tmp_token`。但用户说的是“同时打开 10 个不同宝塔”,这属于不同 server_id:
|
||
|
||
- 每台服务器独立锁,不互相等待。
|
||
- 各自的 session 生命周期取决于各自宝塔面板配置和 task.py patch 状态。
|
||
- 10 台里有的先掉、有的后掉,符合“每台服务器 session_timeout/task.py/定时清理状态不一致”的表现。
|
||
|
||
---
|
||
|
||
## 3. Settings/Auth 发现的问题与建议
|
||
|
||
### 3.1 中风险:Telegram Token reveal 不需要二次认证
|
||
|
||
位置:
|
||
|
||
- `server/api/settings.py` `/telegram/reveal-token`
|
||
- `server/api/settings.py` `/telegram/offline/reveal-token`
|
||
|
||
现状:
|
||
|
||
- 需要 JWT。
|
||
- 会写审计日志。
|
||
- 但不像密码预设 reveal、SSH Key reveal 那样要求当前密码二次认证。
|
||
|
||
风险:
|
||
|
||
- 如果管理员 access token 被盗,在 token 有效期内可直接读取 Telegram Bot Token。
|
||
- 虽然这是管理员功能,但与同文件中“密码预设/SSH 私钥 reveal 需要二次认证”的策略不一致。
|
||
|
||
建议:
|
||
|
||
- 若继续允许显示 Telegram Token,建议改成 `current_password` 二次认证。
|
||
- 如果你倾向于“全局 API Key 一样不显示”,也可以直接删除 Telegram Token reveal,只允许覆盖写入。
|
||
|
||
### 3.2 中风险:TOTP setup/enable 不要求当前密码
|
||
|
||
位置:
|
||
|
||
- `server/api/auth.py` `/totp/setup`
|
||
- `server/api/auth.py` `/totp/enable`
|
||
|
||
现状:
|
||
|
||
- 需要 JWT。
|
||
- 只允许对自己操作。
|
||
- enable 前会校验 TOTP code。
|
||
- 但不要求当前密码。
|
||
|
||
风险:
|
||
|
||
- 如果 access token 被盗,攻击者可以生成自己的 TOTP secret 并启用,造成账号被锁或持久化控制风险。
|
||
|
||
建议:
|
||
|
||
- setup 或 enable 至少其中一步要求当前密码。
|
||
- 更严格方案:setup、enable、disable、password change 都统一走“敏感操作二次认证”。
|
||
|
||
### 3.3 低风险:全局 API Key 相关审计 label/注释残留
|
||
|
||
位置:
|
||
|
||
- `server/api/servers.py` 注释里仍有 `same pattern as reveal_api_key`。
|
||
- `frontend/src/utils/auditLabels.ts` 仍有 `reveal_api_key: 查看 API 密钥`。
|
||
|
||
风险:
|
||
|
||
- 不暴露 secret。
|
||
- 但会误导后续开发,以为还有 reveal_api_key 功能。
|
||
|
||
建议:
|
||
|
||
- 后续清理为通用描述,例如 `same pattern as sensitive operation re-auth`。
|
||
- 删除前端旧 label 或保留但标注 legacy。
|
||
|
||
### 3.4 低风险:refresh 接口仍接受 body refresh_token fallback
|
||
|
||
位置:
|
||
|
||
- `server/api/auth.py` `/api/auth/refresh`
|
||
|
||
现状:
|
||
|
||
- 优先读取 HttpOnly cookie。
|
||
- 没有 cookie 时允许从 body 读取 refresh_token。
|
||
|
||
风险:
|
||
|
||
- 当前注释解释为兼容 fallback。
|
||
- 如果没有明确的历史客户端需要,body fallback 增加了一条 token 传输路径。
|
||
|
||
建议:
|
||
|
||
- 保守:保留但加审计/废弃期。
|
||
- 严格:只允许 HttpOnly cookie refresh。
|
||
|
||
---
|
||
|
||
## 4. 下一步修改方向:至少 6 个方案供选择
|
||
|
||
> 按你的要求,涉及安全策略/代码改动前先给多个选项,不直接替你定。
|
||
|
||
### 方案 A:只修宝塔长期会话,强制把宝塔 session timeout 提升到 24 小时
|
||
|
||
改动:
|
||
|
||
- 在 `ssh_bootstrap.py` 里增加写入/修正宝塔 session timeout 配置的逻辑。
|
||
- 登录前 best-effort 确保目标面板的 session timeout >= 86400。
|
||
- 保留现有 task.py patch。
|
||
|
||
好处:
|
||
|
||
- 最直接解决“2 小时后刷新掉线”。
|
||
- 不影响 Nexus 自身安全策略。
|
||
|
||
风险:
|
||
|
||
- 会修改宝塔面板自身配置。
|
||
- 如果用户希望宝塔短会话,这会改变预期。
|
||
|
||
是否需要重启/部署:
|
||
|
||
- 需要重建/重启 Nexus 后端容器。
|
||
- 对目标宝塔可能需要 reload bt。
|
||
|
||
推荐度:高,针对当前痛点最直接。
|
||
|
||
### 方案 B:宝塔 session timeout 只做检测和告警,不自动修改
|
||
|
||
改动:
|
||
|
||
- Nexus 登录前 SSH 检测宝塔当前 session timeout。
|
||
- 如果低于 86400,在 UI 或接口响应中返回 warning。
|
||
- 不自动改宝塔。
|
||
|
||
好处:
|
||
|
||
- 风险低,不擅自改目标面板。
|
||
- 可解释每台为什么 2 小时掉。
|
||
|
||
风险:
|
||
|
||
- 不自动解决问题,仍需要人工确认/点击修复。
|
||
|
||
推荐度:中。
|
||
|
||
### 方案 C:给宝塔登录 URL 接口增加“会话保活/会话诊断”返回字段
|
||
|
||
改动:
|
||
|
||
- `/api/btpanel/servers/{id}/login-url` 返回:
|
||
- `session_cleanup_ttl_ok`
|
||
- `session_cleanup_ttl_patched`
|
||
- `session_timeout_seconds`
|
||
- `warnings`
|
||
- 前端一键登录时提示“当前宝塔会话约 2 小时后失效”。
|
||
|
||
好处:
|
||
|
||
- 用户能看到问题原因。
|
||
- 便于排查 10 台里哪台会掉。
|
||
|
||
风险:
|
||
|
||
- 需要补前端显示。
|
||
- 只诊断,不一定修复。
|
||
|
||
推荐度:中高,适合和 A/B 搭配。
|
||
|
||
### 方案 D:敏感 Token 全部不显示,只允许覆盖写入
|
||
|
||
改动:
|
||
|
||
- 删除 Telegram Token reveal。
|
||
- 保留 token 是否已设置状态。
|
||
- 修改前端:显示“已设置,留空不改”,不提供查看/复制。
|
||
|
||
好处:
|
||
|
||
- 和“全局 API Key 不显示”策略一致。
|
||
- 降低 access token 被盗后的进一步泄露面。
|
||
|
||
风险:
|
||
|
||
- 管理员无法从 Nexus 找回 Telegram Token,只能重填。
|
||
|
||
推荐度:中高,安全更干净。
|
||
|
||
### 方案 E:保留 Telegram/密码/SSH reveal,但统一要求二次认证
|
||
|
||
改动:
|
||
|
||
- Telegram reveal 请求体增加 `current_password`。
|
||
- 前端弹窗输入当前密码。
|
||
- 可以同时把 reveal 审计统一标准化。
|
||
|
||
好处:
|
||
|
||
- 兼顾可用性和安全。
|
||
- 与密码预设、SSH Key reveal 保持一致。
|
||
|
||
风险:
|
||
|
||
- 使用上多一步。
|
||
|
||
推荐度:高,如果你还想保留“查看 Telegram Token”。
|
||
|
||
### 方案 F:统一敏感操作二次认证框架
|
||
|
||
改动范围更大:
|
||
|
||
- TOTP setup/enable/disable。
|
||
- 修改密码。
|
||
- reveal 密码预设/SSH Key/Telegram Token。
|
||
- 宝塔 API Key 修改/显示类操作。
|
||
- 服务器凭据 reveal/更新。
|
||
|
||
好处:
|
||
|
||
- 安全策略统一,不会每个接口散落实现。
|
||
- 后续审计更清晰。
|
||
|
||
风险:
|
||
|
||
- 改动较大,测试量更大。
|
||
- 前端交互也要统一。
|
||
|
||
推荐度:长期推荐,短期可分阶段做。
|
||
|
||
### 方案 G:refresh token 只允许 HttpOnly Cookie,不再接受 body fallback
|
||
|
||
改动:
|
||
|
||
- `/api/auth/refresh` 删除 body refresh_token fallback。
|
||
- 如有历史客户端,先加配置开关或废弃日志。
|
||
|
||
好处:
|
||
|
||
- Token 传输路径更少。
|
||
- 更符合当前注释里的安全目标。
|
||
|
||
风险:
|
||
|
||
- 如果有脚本/旧前端依赖 body refresh,会受影响。
|
||
|
||
推荐度:中,先确认没有兼容需求再做。
|
||
|
||
---
|
||
|
||
## 5. 我建议的执行顺序
|
||
|
||
如果目标是先解决“宝塔 2 小时掉线”,建议:
|
||
|
||
1. 先做方案 C 的诊断字段或 SSH 检测脚本,确认目标面板 session timeout 是否为 7200。
|
||
2. 再做方案 A:自动把宝塔 session timeout 提升到 86400。
|
||
3. 回归测试:同一台宝塔一键登录后刷新不掉;多台并发生成登录 URL 不互相覆盖。
|
||
4. 然后再做 settings/auth 安全收口:优先方案 D 或 E。
|
||
|
||
如果目标是先收安全面,建议:
|
||
|
||
1. 方案 D 或 E 处理 Telegram Token。
|
||
2. 方案 F 分阶段统一敏感操作二次认证。
|
||
3. 方案 G 作为兼容性确认后的增强项。
|
||
|
||
---
|
||
|
||
## 6. 待用户选择
|
||
|
||
请在以下方向里选一个或多个:
|
||
|
||
1. A:自动强制宝塔 session timeout >= 86400,优先解决掉线。
|
||
2. B:只检测告警,不自动改宝塔。
|
||
3. C:一键登录返回会话诊断字段,前端提示原因。
|
||
4. D:Telegram Token 像全局 API Key 一样完全不显示。
|
||
5. E:Telegram Token 继续可显示,但必须当前密码二次认证。
|
||
6. F:做统一敏感操作二次认证框架。
|
||
7. G:refresh token 只允许 HttpOnly Cookie。
|
||
|