feat: 实现 API 安全审计与管理端 API (#45)
This commit is contained in:
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Architecture-and-Code-Map
|
||||
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Architecture-and-Code-Map.-
|
||||
wiki_revision: e0125624a64ee0ed176215ca68009ec2efb5c25b
|
||||
synchronized_at: 2026-08-25T01:11:57Z
|
||||
wiki_revision: 47afee496f6d4105e51cddb47e9ba9dae7380293
|
||||
synchronized_at: 2026-08-25T01:52:58Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 架构与代码地图
|
||||
@@ -436,3 +436,12 @@ Provider 限流检查发生在真实上游调用前。放行后才执行 `BeginP
|
||||
- worker 在调用 Provider 前按 Provider ID 限流。本地 Provider 限流不创建真实 Provider attempt、不更新熔断状态,也不进入 retryable/failover 判定;同一任务的其他可用 Provider 仍可继续尝试。
|
||||
- 所有候选 Provider 都只因本地限流暂不可用时,`queue.MySQLRepository.Defer` 以 `running + lease_token` CAS 把任务恢复为 `pending`,将 `available_at` 设置为最早可重试时间,并记录 `local_rate_limit` 队列事件。该事件不增加 `provider_attempt_count`。
|
||||
- `ClaimNext` 仅认领 `available_at <= NOW(6)` 的 pending 任务,同时保留过期 running 租约的恢复逻辑。原有上游 `429 / 5xx / 超时 / 连接错误` 与 `400 / 401 / 内容策略拒绝` 分类没有改变。
|
||||
|
||||
## MVP-2 API 安全审计与管理端 API(#45)
|
||||
|
||||
- `internal/core/apiaudit` 是 Portal 与 Admin 共用的追加写入口,只依赖 GORM、标准库和 core model。审计 `Summary` 是强类型结构,只允许 `source`、`kind`、`created`、`already_revoked`、`operator_id`;调用方没有字段可写入 Prompt、文件、完整 Key、Authorization、Cookie、响应正文或原始 IP。
|
||||
- Portal 的 API Key 创建、改名和用户撤销在原业务事务内写 `api_audit_events`;审计失败会回滚对应生命周期变更。幂等撤销记录 `already_revoked`,但不返回或记录凭据材料。
|
||||
- OpenAPI 认证成功后,提交审计中间件包围限流和提交 handler:成功、幂等重放、参数/幂等拒绝、限流拒绝和内部失败都记录同一 `openapi.generation.submit` 动作,并仅保存 kind、是否新建、响应状态与脱敏错误码。普通查询不逐条审计。
|
||||
- 缺失、格式非法、未知、过期、已撤销或所属用户停用的 Key 都返回同一 401。认证失败不会写数据库审计行,避免攻击者制造无界写入;受控安全日志/指标由后续运维采集处理。成功认证的 `last_used_at` 最多每分钟实际变更一次。
|
||||
- 管理端继续复用 go-admin JWT/Casbin:`GET /api/v1/chorus/api-keys` 支持 `keyword/status/page/page_size`,`GET /api/v1/chorus/api-keys/:id` 返回元数据和最近 20 条脱敏安全事件,`POST /api/v1/chorus/api-keys/:id/revoke` 幂等撤销。列表和详情不返回 `public_id`、`secret_hash` 或完整 Key,并设置 `no-store`。
|
||||
- 管理撤销用 `FOR UPDATE` 锁定 Key,在一个事务中更新 `revoked_at`,同时追加 `admin_audit_events` 的 `api_key.revoke` 和 `api_audit_events` 的 `api_key.admin_revoked`。Chorus 管理 API 只把合法 UUID 用作审计 request_id,其他客户端值替换为服务端 UUID,避免误把凭据写入关联字段。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Business-Rules-and-Glossary
|
||||
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Business-Rules-and-Glossary.-
|
||||
wiki_revision: 0e7981e5a175ace20cf406885050d701106963d3
|
||||
synchronized_at: 2026-08-25T01:12:03Z
|
||||
wiki_revision: ea843c01b7549150a9aadde703ee07419cfe401d
|
||||
synchronized_at: 2026-08-25T01:53:03Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 业务规则与术语
|
||||
@@ -180,3 +180,12 @@ synchronized_at: 2026-08-25T01:12:03Z
|
||||
- OpenAPI 生成提交同时受 API Key 和终端用户提交限流;任一维度超限时都返回 HTTP 429 和整数秒 `Retry-After`,且不得因为组合检查失败而消耗未超限维度的次数。
|
||||
- Provider 本地限流发生在真实上游调用前,不算 Provider 调用失败,不写真实 Provider attempt,不触发熔断,不改变上游 retryable 规则。若仍有其他候选 Provider,继续选择;若全部候选都只受本地限流,则任务延后到最早窗口恢复时间。
|
||||
- 限流状态只存在单个 portal 进程内,进程重启会清空。当前仍是单 portal 实例;多实例共享限流需要独立设计,不得把本实现当成分布式配额。
|
||||
|
||||
## API 安全审计已实现规则(#45)
|
||||
|
||||
1. `api_audit_events` 只追加,不因 Key 或用户停用而删除;事件结果只允许 `succeeded|failed|denied`。
|
||||
2. Key 创建、改名、用户撤销、管理员撤销和 OpenAPI 生成提交必须审计;普通成功查询不逐条审计,`last_used_at` 使用一分钟节流更新。
|
||||
3. 未认证请求不写数据库审计。未知或不可用 Key 与其他认证失败返回相同 401,不允许攻击者借此制造无限审计行或确认 Key 是否存在。
|
||||
4. summary 只允许来源、生成种类、是否新建、是否已经撤销和管理员编号;禁止 Prompt、文件、完整 Key、public_id、secret_hash、Authorization、Cookie、响应正文和原始 IP。
|
||||
5. 管理员只能检索名称、前缀、状态、归属用户和时间元数据,不能创建用户 Key 或查看完整 Key;撤销必须同时写管理员审计与 API 安全审计。
|
||||
6. 管理端 Key 列表、详情和撤销响应禁止缓存。幂等撤销不得改变第一次撤销时间。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Local-Development-and-Verification
|
||||
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Local-Development-and-Verification.-
|
||||
wiki_revision: ce59c50808fb1f9c8b80a49385962d4833cbe4da
|
||||
synchronized_at: 2026-08-25T01:12:08Z
|
||||
wiki_revision: 01d2c4aaf24e17d8b9f6cc90d59c1f7244941110
|
||||
synchronized_at: 2026-08-25T01:53:09Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 本地开发与验证
|
||||
@@ -507,3 +507,26 @@ python dev_scripts/harness.py check --strict
|
||||
```
|
||||
|
||||
MySQL 集成测试需在受控测试库或确认无在途任务的本地开发库中设置 `CHORUS_TEST_DSN`,运行 `go test -count=1 -run TestMySQL ./internal/core/queue ./portal/worker`。测试使用 mock Provider,不消耗真实上游额度。限流为进程内固定窗口,重启 portal 会清空计数。
|
||||
|
||||
## #45 API 安全审计与管理 API 验证
|
||||
|
||||
常规验证:
|
||||
|
||||
```powershell
|
||||
go test ./...
|
||||
go vet ./...
|
||||
go test -race -count=1 ./internal/core/apiaudit ./portal/service ./portal/handler
|
||||
go -C admin test ./...
|
||||
go -C admin test -race -count=1 ./app/chorus
|
||||
```
|
||||
|
||||
MySQL 8 集成验证需要受控测试 DSN,且运行 Portal 集成前暂停常驻 worker:
|
||||
|
||||
```powershell
|
||||
$env:CHORUS_TEST_DSN = $env:CHORUS_DSN
|
||||
$env:CHORUS_MIGRATION_TEST_DATABASE = $env:CHORUS_MYSQL_DATABASE
|
||||
go test -count=1 -run '^TestPortalAuthenticationSubmissionAndAuthorization$' ./portal/handler
|
||||
go -C admin test -count=1 -run '^(TestAdminAPIKeyGovernanceMySQL|TestChorusAPIMySQLRejectsUnauthorizedAndRedactsCredentials)$' ./app/chorus
|
||||
```
|
||||
|
||||
验证覆盖:生命周期事务审计、未知 Key 不写行、成功/拒绝/429 提交审计、summary 脱敏、`last_used_at` 节流、管理检索/详情、并发安全的幂等撤销、双审计和 JWT/Casbin 负向路径。管理 API 的真实 go-admin 鉴权失败沿用框架约定,可能返回 HTTP 200 且 JSON `code=401`;判断时必须同时检查响应 JSON,不能只看 HTTP 状态。
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
|
||||
wiki_page: Troubleshooting
|
||||
wiki_url: https://git.ilapage.cn/OPC/chorus/wiki/Troubleshooting
|
||||
wiki_revision: 604293744eb29bd1ec4ac03d939e0305336e1d48
|
||||
synchronized_at: 2026-08-22T03:08:35Z
|
||||
wiki_revision: 343eedb7cb319e2de24725965817444454ef9b8d
|
||||
synchronized_at: 2026-08-25T01:53:19Z
|
||||
<!-- gitea-wiki-mirror:end -->
|
||||
|
||||
# 故障排查
|
||||
@@ -120,3 +120,13 @@ LIMIT 10;
|
||||
2. 当前实现只读取 `sys_casbin_rule` 并关闭 Casbin adapter AutoMigrate;若旧进程曾生成空的 `casbin_rule`,它不是权限事实来源,不要向其中补数据。
|
||||
3. 确认 bootstrap 账号绑定启用的 `chorus_operator`,重新登录取得新 JWT,再检查 `/api/v1/menurole`。
|
||||
4. `settings.yml` 出现 `Unknown database` 时先创建空库并执行版本化 migration;出现 `Access denied` 时修正本地凭据,不运行 AutoMigrate 绕过。
|
||||
|
||||
## API Key 审计排查(#45)
|
||||
|
||||
1. 先使用服务端 `request_id`、API Key 数据库编号或 generation 编号查询 `api_audit_events`,不要用完整 Key、Prompt、文件名或文件内容搜索日志和数据库。
|
||||
2. OpenAPI 提交没有事件时,先判断请求是否通过 API Key 认证。缺失、未知、过期、已撤销或停用用户的 Key 按设计不写数据库审计;这不是审计丢失。
|
||||
3. 已认证提交或限流拒绝没有事件时,检查 `api_audit_events` 写权限、外键目标、JSON CHECK 和应用日志中的 `api audit write failed request_id=...`。不要临时关闭审计或把请求正文写入日志。
|
||||
4. 管理员撤销应同时出现 `admin_audit_events.action=api_key.revoke` 和 `api_audit_events.action=api_key.admin_revoked`。只出现一类表示事务没有按设计提交,应停止重复操作并检查数据库错误。
|
||||
5. `last_used_at` 在一分钟内不变化是节流行为,不代表认证未发生。判断调用结果使用请求状态和对应提交审计。
|
||||
6. go-admin 鉴权失败可能使用 HTTP 200 包装 JSON `code=401|403`;排查权限时同时检查 JSON 业务码、JWT 和 Casbin,不把 HTTP 200 误判为已授权。
|
||||
7. 任何 summary 出现 Prompt、完整 Key、`public_id`、`secret_hash`、Authorization、Cookie、响应正文或原始 IP 都属于安全缺陷,应立即停止相关入口并建立缺陷工单,不直接清理审计数据。
|
||||
|
||||
Reference in New Issue
Block a user