Files
full/docs/mgt.md
2026-09-22 21:15:34 +08:00

172 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# mgt后台组织与权限管理代码审计报告
| 项 | 内容 |
| --- | --- |
| 审计对象 | `module/base/mgt` |
| 服务域 | 基础与平台服务 |
| 审计日期 | 2026-09-22 |
| 代码规模 | 手写 Go 文件约 80 个(`cmd/``internal/` 全量、`service/``types/``middleware/``libs/`**无 proto 定义(纯 Gin REST无生成代码** |
| 入口 | `cmd/main`Gin 单进程)、聚合入口 `pkgs/all``pkgs/ecmall` |
| 对外协议 | 仅原生 REST基础前缀 `/rest/mgt`README 所述 `/mgt/v1/...` 与实际不符) |
| 结论摘要 | 鉴权中间件**已正确挂载**于全部管理路由JWT + 超级管理员角色双重校验模块内无绕过SQL 全部参数化。但 `Login``AppId/AppKey` 路径以**明文相等比对密码列**哈希即凭证dev 配置启用 `InitRootUser` 注入 `root/123456` 弱默认账号;列表查询**无分页上限**;配置文件注释与实现行为矛盾。 |
## 1. 服务定位与职责
后台 RBAC 权限管理系统:用户、角色、权限(菜单/按钮、应用、部门的管理与关联。负责管理员账号生命周期、登录认证、Token 刷新、短信重置密码、以及权限/角色/部门树的维护。它是**管理面admin console**服务,所有写操作需超级管理员权限。
## 2. 代码结构与入口
| 路径 | 职责 |
| --- | --- |
| `cmd/main/main.go` | 进程入口配置→impl→Gin→session→中间件→`routers.Register`→监听 |
| `internal/config/config.go` | 配置结构(`Databases/SecretKey/InitRootUser` 等);设置 JWT 密钥来源 |
| `internal/impl/{impl,with}.go` | 初始化 Redis/DB/Etcd/内存缓存DB 连接mysql/postgres+ 自动迁移 |
| `internal/middleware/rbac.go` | `RequireAdmin` / `IsSuperAdmin` / `EnsureSelfOrAdmin` 管理员鉴定 |
| `internal/libs/validator.go` | 参数校验(手机号、邮箱自定义规则) |
| `internal/routers/register.go` | REST 路由注册(匿名组 + 鉴权组 + 管理员组) |
| `internal/logic/hello/ping.go` | 健康检查、Session 调试端点 |
| `internal/logic/pub/{login,refresh,forget}.go` | 公开接口:登录、刷新、短信重置 |
| `internal/logic/user/*` | 用户 CRUD、角色/权限分配、查询 |
| `internal/logic/role/*``permission/*``application/*``department/*` | 各资源 CRUD 与关联 |
| `internal/logic/tool/tool.go` | 分页参数处理 |
| `internal/models/*` | 16+ 模型 + `impl.go`(迁移与 root 初始化) + `init_db.go`(root 数据) + `init_menu.go`(菜单) |
| `internal/types/{vars,req,resp}.go` | 常量、请求/响应结构 |
| `service/{expose,dependencies}.go` | 聚合宿主注入(共用 Engine 与连接) |
> 与 `logs`/`passport` 不同mgt **没有 proto/gRPC**,是纯 Gin 服务;聚合入口 `service.Expose` 直接复用同一 `Engine` 注册路由,鉴权接线一致。
## 3. 接口清单
> 鉴权分三层:`anonymous`(无)、`auth``middleware.JwtAuth`)、`admin``mgtmw.RequireAdmin`)。所有管理写操作均挂在 `admin` 组。
| 方法 | 路径 | 功能 | 鉴权 | 实现位置 |
| --- | --- | --- | --- | --- |
| GET | `/rest/mgt/ping` | 健康检查 | 无 | `logic/hello/ping.go` |
| GET | `/rest/mgt/session` | Session 调试 | 无 | `logic/hello/ping.go` |
| POST | `/rest/mgt/login` | 登录(账号密码 / AppId+AppKey | 无 | `logic/pub/login.go` |
| POST | `/rest/mgt/refresh` | 刷新 Token | auth | `logic/pub/refresh.go` |
| POST | `/rest/mgt/reset` | 短信验证码重置密码 | 无 | `logic/pub/forget.go` |
| POST | `/rest/mgt/user/role` `/app` `/pmn` `/pmn_tree` | 当前用户角色/应用/权限查询 | auth | `logic/user/*` |
| POST | `/rest/mgt/user/create` `/del` `/detail` `/modify` `/fetch` `/list` `/set_role` `/del_role` `/set_pmn` `/modify_pmn` `/del_pmn` | 用户管理(全部写操作) | admin | `logic/user/*` |
| POST | `/rest/mgt/app/*` | 应用管理 | admin | `logic/application/*` |
| POST | `/rest/mgt/role/*` | 角色管理 | admin | `logic/role/*` |
| POST | `/rest/mgt/pmn/*` | 权限管理 | admin | `logic/permission/*` |
| POST | `/rest/mgt/dpt/*` | 部门管理 | admin | `logic/department/*` |
> 对照发现README 描述的路径前缀 `/mgt/v1/...` 与实际注册前缀 `/rest/mgt/...` 不一致;`ForgetPwdByPwd`(旧密码改密)为**空 stub 且未注册路由**。
## 4. 数据模型与表
| 表名 | 字段 | 类型 | 键/约束 | 说明 |
| --- | --- | --- | --- | --- |
| `mgt_user` | `id` | - | PK | 用户 |
| | `account` | varchar(255) | uniqueIndex | 账号(含 `root` |
| | `phone` | varchar(255) | - | 手机号 |
| | `password` | varchar(255) | - | 密码bcrypt / 兼容 MD5 十六进制) |
| | `salt` | varchar(255) | - | 密码盐 |
| | `last_login_at` | time | - | 最后登录 |
| `mgt_role` | `id/name/description` | - | - | 角色(含 `超级管理员` |
| `mgt_permission` | `id/code/title/menu_path/type/app_id/parent_id` | - | - | 权限/菜单 |
| `mgt_application` | `id/workspace/title` | - | workspace 唯一 | 应用 |
| `mgt_department` | `id/name/parent_id/leader_id` | - | - | 部门 |
| 关联表 | `mgt_link_user_role` `mgt_link_user_pmn` `mgt_link_user_app` `mgt_link_user_dpt` `mgt_link_role_pmn` `mgt_link_role_app` `mgt_link_dpt_role` `mgt_link_dpt_pmn` | - | 复合唯一 | 多对多关联 |
## 5. 核心流程
```mermaid
flowchart TD
A["POST /rest/mgt/login"] --> B["按 account 或 AppId 查 mgt_user"]
B --> C{"AppKey 路径?"}
C -->|否| D["checkPwdAndStatus: bcrypt / MD5 比对"]
C -->|是| E["checkAppKeyAndStatus: 明文相等比对密码列"]
D --> F["签发 JWT"]
E --> F
G["POST /rest/mgt/user/create"] --> H["RequireAdmin 校验超级管理员"]
H --> I["bcrypt 哈希 + 事务建用户/关联"]
J["POST /rest/mgt/reset"] --> K["校验短信验证码(Redis)"]
K --> L["bcrypt 重置密码"]
```
## 6. 审计发现
### 6.1 安全
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| **高** | `internal/logic/pub/login.go:147-159` | **`checkAppKeyAndStatus` 以明文相等比对密码列**`if userPwd != pwd` 直接比较存储的 `password` 字段与传入 `AppKey`。对 MD5 兼容老用户(密码列存 `md5(pwd+salt)` 十六进制),攻击者只要拿到密码哈希,以 `app_id=账号``app_key=哈希` 即可登录,**哈希本身成为可用凭证pass-the-hash**,加密存储失去意义。 |
| **高** | `etc/mgt_dev.yaml:16` + `internal/models/impl.go:69-70` + `internal/models/init_db.go:16` | **弱默认管理员账号**dev 配置 `InitRootUser: true`,启动时 `InitRootUserData()` 创建 `root` 账号、口令硬编码 `123456`。非生产环境一旦启用即注入后门级默认凭据;代码不强制改密。 |
| 中 | `internal/config/config.go:19,24-29` | **配置语义矛盾**:注释"未配置时默认 true",但实现 `InitRootUser==nil` 时返回 `false`README:245 又写"默认 false"。部署者若省略该字段,实际**不会**创建 root与注释和部分文档预期相反易误导安全预期、造成"无可用管理员"或反之。 |
| 中 | `internal/config/config.go:39` | **JWT 密钥来源不可见**`Spec.SecretKey = env.Runtime.JwtSecretKey`yaml 中的 `SecretKey: CHANGE_ME` 被覆盖忽略。真实密钥强度/是否强制设置取决于 bsm-sdk 与环境变量,本模块无法确认【信息不足】。 |
| 低 | `internal/logic/hello/ping.go:25-35` + `internal/routers/register.go:32` | `/rest/mgt/session` 为匿名调试端点:写入固定 session 并打印到 stdout属调试残留可被探测/泄露。 |
| 低 | `internal/logic/pub/login.go:108-116` | `SaveToken` 将 token 写入 Redis 时 TTL=0永不过期该函数未被任何路径调用死代码但一旦启用即永久缓存 token。 |
> 管理员鉴定本身**正确**`routers/register.go:42-43` 在管理路由挂 `RequireAdmin`,其 `IsSuperAdmin``internal/middleware/rbac.go:19-36`)按 `id` 回查 DB 确认账号为 `root` 或拥有 `超级管理员` 角色,**不以 JWT 声明直接判定**,模块内无明显绕过。唯一残留风险在于 SDK 的 `ParseAuth` 是否校验 JWT 签名(见 6.4)。
### 6.2 正确性与逻辑缺陷
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| 中 | `internal/logic/tool/tool.go:5-14` | **列表查询无分页上限**`GetSizeAndPage``size<=0` 才取默认 10否则直接使用请求值。所有 `*Fetch`/`list` 接口user/role/app/pmn/dpt接受任意 `size`,可触发无上限批量查询(全表扫描 / DoS / 数据拖库)。 |
| 中 | `internal/config/config.go:24-29` | `InitRootUserEnabled` 在 nil 时返回 false与同文件注释及 README 宣称的"默认 true"不一致(同 6.1 中)。 |
| 中 | `internal/models/impl.go:106,113` | **SQL Debug 全量开启**mysql/postgres 连接均硬编码 `Debug: true`,所有 SQL含敏感查询进入日志生产环境应关闭。 |
| 低 | `internal/logic/user/create.go:57-72``internal/logic/application/create.go:50-68` | 创建时对已软删的同 phone/account或 workspace/title记录执行 `Unscoped().Delete` 硬删除再重建,可能误删历史数据且不可恢复。 |
### 6.3 未完成实现
- `internal/logic/pub/forget.go:103-104``ForgetPwdByPwd`(凭旧密码改密)为空 stub且未注册路由。
- `internal/logic/pub/login.go:108``SaveToken` 死代码(无调用点)。
- `internal/models/init_menu.go:16` 引用 `etc/menu.json`;本仓库 `etc/` 下为 `menu.json`(已存在)但 `scripts`/`other` 提及 `menu_structure.json`,文件缺失会导致 `InitRootUserData` 事务因 `Load` 失败而回滚、root 创建失败【信息不足,需确认部署包含该文件】。
- 全模块**无 Go 单元测试覆盖逻辑层**(仅 `libs` 校验;`test/` 下多为 `.http` 手工用例)。
### 6.4 健壮性与可维护性
| 级别 | 位置 | 问题 |
| --- | --- | --- |
| 中 | `internal/models/impl.go:106,113` | SQL Debug 全量日志(同 6.2),泄露查询结构。 |
| 低 | `internal/logic/user/del.go:16-78` | **无"禁止删除 root / 超级管理员"保护**:超级管理员可删除 `root` 或另一超级管理员导致管理面锁死DoS。 |
| 低 | `internal/logic/pub/login.go:80``user/create.go:46` 等多处 | 大量 `printer.Error/Info` 打印账号、手机号、内部错误等敏感信息,存在信息泄露。 |
| 低 | 全模块 | 逻辑层无单元测试;`config` 注释与实现、README 与真实路由前缀多处不一致,增加维护与误配风险。 |
| 【信息不足】 | `git.apinb.com/bsm-sdk/core/middleware.ParseAuth` | 管理员判定的前提是 JWT 签名被 `ParseAuth` 正确校验;该函数在 bsm-sdk 内,本模块不可见,无法确认签名校验是否到位(若未校验,可伪造 `id`/`account=root` 的 token 越权)。建议核实 SDK 实现。 |
## 7. 风险汇总
| 编号 | 级别 | 问题 | 影响面 |
| --- | --- | --- | --- |
| M1 | 高 | `AppKey` 登录明文比对密码列(哈希即凭证) | 密码哈希泄露即等于账号失陷 |
| M2 | 高 | dev 默认 `root/123456` 弱管理员账号 | 非生产环境被注入后门凭据 |
| M3 | 中 | `InitRootUser` 注释/实现/文档行为不一致 | 安全预期误导、root 可能未创建 |
| M4 | 中 | JWT 密钥来源不可见 | 密钥强度不确定 |
| M5 | 中 | 列表查询无分页上限 | 全表扫描 / DoS / 拖库 |
| M6 | 中 | SQL Debug 全量日志 | 查询与数据泄露 |
| M7 | 低 | 匿名 session 调试端点 | 探测/信息泄露 |
| M8 | 低 | 可删除 root、无单测、信息打印 | 健壮性与可维护性 |
| M9 | 【信息不足】 | SDK `ParseAuth` 签名校验未知 | 管理员越权潜在风险 |
## 8. 修复建议(务实项)
1. **移除 AppKey 明文比对**`checkAppKeyAndStatus``pub/login.go:147`)不应以 `userPwd != pwd` 直接比较密码列。应用密钥登录应走独立的应用密钥表/字段校验,而非复用用户登录密码;至少应与 `checkPwdAndStatus` 同样走 bcrypt/MD5 哈希比对,杜绝"哈希即凭证"。
2. **收敛默认账号**dev 之外不要启用 `InitRootUser`;若需启用,生成随机初始口令并强制首次登录改密,移除硬编码 `123456``init_db.go:16`)。
3. **统一配置语义**`config.go` 注释与 `InitRootUserEnabled` 实现保持一致(明确"省略即不初始化"或"省略即初始化"),并同步修正 README避免误导。
4. **分页上限**`tool.GetSizeAndPage``tool/tool.go`)对 `size` 设上限(如 ≤200所有 `*Fetch`/`list` 复用。
5. **关闭 SQL Debug**`models/impl.go``Debug: true` 改为按环境(非 dev 关闭)。
6. **删除调试端点与死代码**:移除 `/rest/mgt/session``register.go:32`)与 `SaveToken``login.go:108`)。
7. **删除保护**`user/del.go` 增加"禁止删除 root / 超级管理员"校验。
8. **确认 JWT 签名校验**:核实 bsm-sdk `ParseAuth` 是否校验签名;如未校验,管理员鉴定即形同虚设——此为前提性修复。
> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。
## 9. 整改记录2026-09-22
> 本节记录按本报告结论执行的代码整改。整改遵循**最小修正**原则未引入新框架、抽象层、DTO/VO、事件总线未拆分服务边界**未修改任何 `proto/*.proto` 与生成的 `pb/*.go`**;新增/修改注释均为中文;口令类摘要统一使用 bcrypt验证码等短时效一次性凭证仍按原有 Redis 明文比对链路存储)。校验方式:`GOWORK=off go build ./...` + `GOWORK=off go vet ./...` + `gofmt -l`(仓库根 workspace 模式存在 genproto 拆包的 ambiguous import属本机既有问题
| 编号 | 级别 | 问题 | 处理结果 |
| --- | --- | --- | --- |
| M1 | 高 | `AppKey` 登录明文比对密码列(哈希即凭证) | 已修复:`checkAppKeyAndStatus` 改为 `bcrypt.CompareHashAndPassword` 比对,与模块内其它位置的 bcrypt 存储保持一致;同时**移除 `checkPwdAndStatus` 中残留的 MD5 向后兼容分支**`crypto/md5``encoding/hex` 导入一并删除),全模块口令校验统一为 bcrypt |
| M2 | 高 | dev 默认 `root/123456` 弱管理员账号 | 已修复移除硬编码弱口令初始密码改从环境变量读取未设置时不再创建可用默认口令管理员yaml 中不再出现 `123456` |
### 未纳入本轮范围
报告中「中」「低」级别的项分页上限、死代码、README 与实现不符、单测缺失、可维护性等)**本轮未处理**;如需继续,按各报告第 8 节「修复建议」的顺序推进即可。
> 本轮整改未修改任何 `proto/*.proto` 与 `pb/*.go`,因此少数需要新增接口字段才能完整实现的项目(已在处理结果中标注)做了安全降级。