9.0 KiB
9.0 KiB
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. 核心流程
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. 修复建议(务实项)
- 鉴权:
create/fetch/total至少接入入口层统一鉴权(聚合入口已有 JWT 能力,直接启用即可),不要留在匿名组。 - 修正 IP 过滤:明确"允许来源"与"禁止来源",按白名单/内网判定写正;
172./192.需收敛为172.16-31.、192.168.,并考虑网关代理下的X-Forwarded-For。 fetch修复:统一键名为op_ip;level用mapstructure/json.Number或结构体接收,避免.(int)断言;把page/size真正从请求读出并设上限。total修复:确认是要"按 level 分组返回全部"还是"单一统计值",按结果语义改用Find/Scan,并回写tx。- 配置校验:
conf.NotNil补上Databases;确需可选时在初始化后显式判空并给出清晰错误。 - 清理:删除调试
Println与该模块未使用的 Redis/Etcd 初始化(或按规划保留并注明)。 - 补测试:为
create/fetch/total各写一条正例 + 一条边界用例,覆盖本次发现的三处 panic。
本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。日志审计中"统一请求校验框架""引入 DTO 层"一类改造不在此列。