Files
full/docs/logs.md

136 lines
9.0 KiB
Markdown
Raw Normal View History

2026-09-22 18:53:53 +08:00
# logs业务操作日志代码审计报告
| 项 | 内容 |
| --- | --- |
| 审计对象 | `module/base/logs` |
| 服务域 | 基础与平台服务 |
| 审计日期 | 2026-09-22 |
| 代码规模 | Go 文件 12 个(全部手写,无 `proto`/生成代码) |
| 入口 | `cmd/main`Gin 单进程)、聚合入口 `pkgs/all``pkgs/ecmall` |
| 对外协议 | 仅原生 REST前缀 `/rest/logs` |
| 结论摘要 | 结构简单、职责清晰;但核心接口存在**过滤逻辑写反**、**取参键名不一致**、**分页参数未生效**、**类型断言可 panic**,且全部路由挂匿名组、无鉴权。 |
## 1. 服务定位与职责
集中保存与查询业务操作记录供后台追踪操作行为和排查问题。数据模型包含服务名、操作人、IP、数据类型、级别、内容等字段。它是业务日志的**存储与查询**服务,不负责进程日志采集、指标监控或链路追踪。
## 2. 代码结构与入口
| 路径 | 职责 |
| --- | --- |
| `cmd/main/main.go` | 独立进程入口:读配置 → 初始化 impl → 建 Gin → 注册路由 → 监听 `Spec.Port` |
| `cmd/cli/main.go` | 命令行辅助入口 |
| `internal/config/config.go` | 配置结构Base、Databases、Rpc、APM、Etcd与校验 |
| `internal/impl/impl.go` | 初始化 Redis、DB、Etcd 实例 |
| `internal/routers/register.go` | REST 路由注册(全部挂在匿名组) |
| `internal/logic/hello/ping.go` | 健康探测 |
| `internal/logic/log/{create,fetch,total}.go` | 创建、分页查询、按级别统计 |
| `internal/models/log_data.go` | `LogData` 模型 + 自动迁移注册 |
| `service/{expose,dependencies}.go` | 聚合宿主注入接口 |
依赖注入:`service.Dependencies` 支持外部传入 Redis/Etcd/DB/Cache非 nil 时覆盖 `internal/impl` 对象,供 `pkgs/all` 复用共享连接。
## 3. 接口清单
| 方法 | 路径 | 功能 | 鉴权 | 实现位置 |
| --- | --- | --- | --- | --- |
| GET | `/rest/logs/ping` | 健康探测,返回 `{"message":"Pong"}` | 无 | `logic/hello/ping.go` |
| POST | `/rest/logs/create` | 批量写入日志记录 | 无 | `logic/log/create.go` |
| POST | `/rest/logs/fetch` | 按条件分页查询日志 | 无 | `logic/log/fetch.go` |
| POST | `/rest/logs/total` | 按 level 分组统计数量 | 无 | `logic/log/total.go` |
| HEAD | `/` | 健康检查(独立入口注册) | 无 | `cmd/main/main.go` |
> 该模块没有 `proto` 定义,接口契约以逻辑层实际读取的 JSON 字段为准。
## 4. 数据模型
### 表 `log_data`(经 GORM 自动迁移创建)
| 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- |
| `id` | uint | PK | 自增主键 |
| `created_at` | TIMESTAMP | - | 创建时间 |
| `service` | varchar(255) | 默认 `def` | 服务名称 |
| `op_id` | uint | - | 操作人 ID |
| `op_name` | varchar(255) | - | 操作人姓名 |
| `ip` | varchar(255) | 默认 `0.0.0.0` | 操作 IP |
| `data_type` | varchar(255) | - | 数据类型 |
| `level` | uint | 默认 1 | 日志级别1 信息 / 2 警告 / 3 错误) |
| `content` | text | - | 日志内容 |
| `hmac` | varchar | - | 预留校验字段,写入逻辑未计算 |
| `encry` | - | `gorm:"-"` | 非持久化字段 |
## 5. 核心流程
```mermaid
flowchart TD
A["POST /rest/logs/create"] --> B["ClientIP 过滤"]
B --> C["BindJSON 到 []LogData"]
C --> D["DBService.Create 批量写入"]
E["POST /rest/logs/fetch"] --> F["BindJSON 到 map"]
F --> G["按 op_name/service/ip/level 过滤"]
G --> H["Count + Limit/Offset 分页"]
```
## 6. 审计发现
### 6.1 安全
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| **高** | `internal/logic/log/create.go:21` | **IP 过滤逻辑与注释相反**。注释为"禁止公网网段提交",但代码在 IP 属于 `127.0.0.1`/`localhost`/`10.`/`172.`/`192.` **内网段时返回错误**,等于只拦内网、放行公网,与设计意图完全颠倒。 |
| **高** | `internal/routers/register.go:21-27` | 全部业务路由注册在**匿名组**`JwtAuth` 中间件被注释掉。`create`/`fetch`/`total` 可被任意匿名调用,日志可被伪造写入、全量读取。 |
| 中 | `internal/logic/log/fetch.go:36,39,42` | 查询关键词未做长度/字符限制即拼接进 `LIKE`;虽使用参数占位符未直接注入 SQL`%kw%` 全表扫描与超长入参缺少约束。 |
| 低 | `internal/logic/log/create.go:22,30,35,41` | 用标准库 `log.Printf` 打印请求 IP、解析错误与 DB 错误,可能将内部信息写入进程日志。 |
### 6.2 正确性与逻辑缺陷
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| **高** | `internal/logic/log/fetch.go:42` | **取参键名不一致**:判断用 `request["op_ip"]`,取值用 `request["ip"].(string)`。实际传入 `op_ip` 时取到 `nil``nil.(string)` 触发 **panic**,服务 500。该过滤条件永远生效不了或直接崩溃。 |
| **高** | `internal/logic/log/fetch.go:33-53` | **分页参数未生效**`size`/`page` 声明后从未从 `request` 赋值,判断 `page<=0`/`size<=0` 恒成立,永远固定第 1 页、每页 50 条。调用方传 `page`/`size` 无效。 |
| **高** | `internal/logic/log/fetch.go:45` | `request["level"].(int)`JSON 数字反序列化为 `float64`,直接断言 `int`**panic**。只要带上 `level` 字段即崩溃。 |
| 中 | `internal/logic/log/fetch.go:29-31` | 返回体变量名为 `data []any`,未指定 `LogData` 类型GORM `Find``any` 填充,字段结构依赖 GORM 猜测,易与表结构脱节。 |
| 中 | `internal/logic/log/total.go:24` | `tx.Where(...)` 未回写 `tx`,在 GORM v2 下当前能生效但依赖内部行为,一旦链式调用顺序变化即静默丢失条件;同类写法在 `fetch.go` 同样存在。 |
| 中 | `internal/logic/log/total.go:28` | `Select("level","count(level)").Group("level").First(&result)``First` 会附加 `ORDER BY id LIMIT 1`,与 `GROUP BY` 组合语义不明确,多分组时只返回一条。 |
| 低 | `internal/models/log_data.go:19-20` | `Hmac` 字段存在于表中,但 `create.go` 未计算/校验任何 HMAC`Encry` 为非持久化字段。二者目前无实际用途。 |
### 6.3 未完成实现
- 未发现显式 TODO/占位。`Hmac` 完整性校验字段有模型无逻辑,属"设计预留未落地"。
### 6.4 健壮性与可维护性
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| 中 | `internal/impl/impl.go:32` | `DBService = with.Databases(config.Spec.Databases, opts)`;而 `config.go:27``conf.NotNil` 只校验 `Service`/`Cache`**未校验 `Databases`**。配置漏配时 DB 可能为 nil首个查询即空指针崩溃。 |
| 低 | `internal/impl/impl.go:22,35` | `RedisService``EtcdService` 被初始化但全模块无任何使用点(死代码)。`Dependencies.Cache` 也被声明却未在 `applyDependencies` 中应用。 |
| 低 | `internal/routers/register.go:20` | 保留调试用 `fmt.Println(v1_key)`。 |
| 低 | 全模块 | **无任何单元测试**`*_test.go` 缺失)。`fetch.go` 的两处 panic 若有测试即可暴露。 |
| 低 | `internal/logic/log/fetch.go:19` | 请求体用 `map[string]any` 承载,缺失字段/类型错误只能运行时发现,无误用反馈。 |
## 7. 风险汇总
| 编号 | 级别 | 问题 | 影响面 |
| --- | --- | --- | --- |
| L1 | 高 | 路由无鉴权,日志可伪造/泄露 | 数据可信性、信息安全 |
| L2 | 高 | `fetch` 取参键名不一致 → panic | 接口可用性 |
| L3 | 高 | `level` 类型断言 → panic | 接口可用性 |
| L4 | 高 | 分页参数完全失效 | 功能不可用 + 全表返回风险 |
| L5 | 高 | `create` IP 过滤逻辑写反 | 防护失效 |
| L6 | 中 | `Databases` 未做非空校验 | 启动后运行期崩溃 |
| L7 | 中 | GORM 链式未回写、`First`+`Group` 语义问题 | 查询结果不确定 |
| L8 | 低 | 死代码、调试输出、无测试 | 可维护性 |
## 8. 修复建议(务实项)
1. **鉴权**`create`/`fetch`/`total` 至少接入入口层统一鉴权(聚合入口已有 JWT 能力,直接启用即可),不要留在匿名组。
2. **修正 IP 过滤**:明确"允许来源"与"禁止来源",按白名单/内网判定写正;`172.`/`192.` 需收敛为 `172.16-31.``192.168.`,并考虑网关代理下的 `X-Forwarded-For`
3. **`fetch` 修复**:统一键名为 `op_ip``level``mapstructure`/`json.Number` 或结构体接收,避免 `.(int)` 断言;把 `page`/`size` 真正从请求读出并设上限。
4. **`total` 修复**:确认是要"按 level 分组返回全部"还是"单一统计值",按结果语义改用 `Find`/`Scan`,并回写 `tx`
5. **配置校验**`conf.NotNil` 补上 `Databases`;确需可选时在初始化后显式判空并给出清晰错误。
6. **清理**:删除调试 `Println` 与该模块未使用的 Redis/Etcd 初始化(或按规划保留并注明)。
7. **补测试**:为 `create`/`fetch`/`total` 各写一条正例 + 一条边界用例,覆盖本次发现的三处 panic。
> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。日志审计中"统一请求校验框架""引入 DTO 层"一类改造不在此列。