feat: 审核规则驱动SQL检查 + db_type过滤 + Redis只读拦截 + 配置文档
1. 审核规则改为从audit_rules表动态匹配(去硬编码)
- CreateTicket提交时查audit_rules WHERE db_type=? AND is_enabled=true
- 正则匹配SQL,value=reject拦截,warn/review放行
- UPDATE/DELETE无WHERE: Go RE2不支持(?!...)负向前瞻
→ 用value=where_check特殊标记+strings.Contains WHERE检测
- 修复UPDATE/DELETE无WHERE正则(Go兼容)
2. ListAuditRules按db_type过滤
- ghost.go: 添加c.Query('db_type') WHERE过滤
- MySQL不显示KEYS规则,Redis不显示DROP规则
3. Redis只读命令拦截(工单提交)
- 新增Redis只读命令白名单: GET/HGET/MGET/HGETALL/KEYS/SCAN等28个
- 只读命令走工单→直接拦截,引导用户用查询功能
4. 补充audit_rules规则
- Redis新增7条: DEBUG/SHUTDOWN/SCRIPT/SAVE/BGSAVE/BGREWRITEAOF/SLAVEOF/CLUSTER
- MySQL新增1条: DROP SCHEMA
- 总计34条规则(MySQL12/Redis11/PG7/MongoDB4)
5. docs/CONFIGURATION.md 配置系统文档
- 三套配置体系: system_settings/audit_rules/ldap_configs
- 每项配置: key/默认值/作用/代码引用位置
- 未引用配置标注: executor_batch_size/executor_backup/query_max_rows_examined(待实现)
- LDAP冗余配置: system_settings中7项ldap_*无代码引用(LDAP用独立表)
验证: 5/5审批流通过, 36/36安全测试通过, 27/27审核规则匹配正确
This commit is contained in:
1 parent
8fbcb8647d
commit
3a6dd99633
3 files changed
+563
-55
No files matched your search
@@ -427,7 +427,11 @@ func GhOstListTasks(c *gin.Context) {
|
||||
// GET /api/v1/audit-rules
|
||||
func ListAuditRules(c *gin.Context) {
|
||||
var rules []model.AuditRule
|
||||
model.DB.Order("id").Find(&rules)
|
||||
query := model.DB.Order("id")
|
||||
if dbType := c.Query("db_type"); dbType != "" {
|
||||
query = query.Where("db_type = ?", dbType)
|
||||
}
|
||||
query.Find(&rules)
|
||||
c.JSON(http.StatusOK, rules)
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
@@ -244,74 +245,92 @@ func CreateTicket(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// Reject read-only SQL statements (SELECT/SHOW/DESC/EXPLAIN)
|
||||
// Reject read-only SQL statements
|
||||
upperSQL := strings.ToUpper(strings.TrimSpace(input.SQLContent))
|
||||
readOnlyPrefixes := []string{"SELECT", "SHOW", "DESC", "DESCRIBE", "EXPLAIN"}
|
||||
for _, prefix := range readOnlyPrefixes {
|
||||
if strings.HasPrefix(upperSQL, prefix) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "数据变更工单不支持 SELECT/SHOW/DESC 等只读语句,请使用查询数据功能"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ── Dangerous SQL blacklist ──
|
||||
// MySQL: block DROP DATABASE / DROP SCHEMA
|
||||
if input.DBType == "mysql" || input.DBType == "postgresql" || input.DBType == "postgres" || input.DBType == "pg" {
|
||||
mysqlBlacklist := []struct {
|
||||
prefix string
|
||||
reason string
|
||||
}{
|
||||
{"DROP DATABASE ", "禁止执行 DROP DATABASE — 删库属于极端危险操作"},
|
||||
{"DROP SCHEMA ", "禁止执行 DROP SCHEMA — 删库属于极端危险操作"},
|
||||
}
|
||||
statements := splitSQL(input.SQLContent)
|
||||
for _, stmt := range statements {
|
||||
upper := strings.ToUpper(strings.TrimSpace(stmt))
|
||||
for _, bl := range mysqlBlacklist {
|
||||
if strings.HasPrefix(upper, bl.prefix) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": bl.reason})
|
||||
return
|
||||
}
|
||||
if input.DBType == "mysql" || input.DBType == "postgresql" {
|
||||
readOnlyPrefixes := []string{"SELECT", "SHOW", "DESC", "DESCRIBE", "EXPLAIN"}
|
||||
for _, prefix := range readOnlyPrefixes {
|
||||
if strings.HasPrefix(upperSQL, prefix) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "数据变更工单不支持 SELECT/SHOW/DESC 等只读语句,请使用查询数据功能"})
|
||||
return
|
||||
}
|
||||
// Warn: UPDATE/DELETE without WHERE
|
||||
if (strings.HasPrefix(upper, "UPDATE ") || strings.HasPrefix(upper, "DELETE FROM ")) &&
|
||||
!strings.Contains(upper, " WHERE ") {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": "禁止无 WHERE 条件的 UPDATE/DELETE — 全表更新/删除属于高危操作,请添加 WHERE 子句限定范围"})
|
||||
}
|
||||
} else if input.DBType == "redis" {
|
||||
// Redis: reject read-only commands (GET/HGET/KEYS/SCAN/TTL/TYPE/LLEN/LRANGE/INFO etc.)
|
||||
redisReadOnlyCmds := []string{"GET", "HGET", "MGET", "HGETALL", "KEYS", "SCAN", "SSCAN", "HSCAN", "ZSCAN",
|
||||
"TTL", "PTTL", "TYPE", "EXISTS", "LLEN", "LRANGE", "SRANDMEMBER", "SCARD", "SISMEMBER", "SMEMBERS",
|
||||
"STRLEN", "INFO", "DBSIZE", "PING", "ECHO", "OBJECT", "BITCOUNT", "GETRANGE", "SUBSTR"}
|
||||
cmdUpper := upperSQL
|
||||
if idx := strings.IndexByte(cmdUpper, ' '); idx > 0 {
|
||||
cmdUpper = cmdUpper[:idx]
|
||||
}
|
||||
for _, ro := range redisReadOnlyCmds {
|
||||
if cmdUpper == ro {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": fmt.Sprintf("数据变更工单不支持只读命令 %s,请使用查询功能", cmdUpper)})
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Redis: block extremely dangerous commands
|
||||
// ── SQL审核规则检查 (from audit_rules table) ──
|
||||
statements := splitSQL(input.SQLContent)
|
||||
sqlToCheck := input.SQLContent
|
||||
// For Redis, check as a single command
|
||||
if input.DBType == "redis" {
|
||||
redisBlacklist := []struct {
|
||||
cmd string
|
||||
reason string
|
||||
}{
|
||||
{"FLUSHALL", "禁止执行 FLUSHALL — 清空所有Redis数据"},
|
||||
{"FLUSHDB", "禁止执行 FLUSHDB — 清空当前数据库"},
|
||||
{"CONFIG ", "禁止执行 CONFIG — 修改Redis配置属于高危操作"},
|
||||
{"DEBUG ", "禁止执行 DEBUG — 调试命令可能导致服务异常"},
|
||||
{"SHUTDOWN", "禁止执行 SHUTDOWN — 关闭Redis服务"},
|
||||
{"SCRIPT ", "禁止执行 SCRIPT — 脚本管理属于高危操作"},
|
||||
{"SAVE", "禁止执行 SAVE — 阻塞式持久化"},
|
||||
{"BGSAVE", "禁止执行 BGSAVE — 后台持久化由运维管理"},
|
||||
{"BGREWRITEAOF", "禁止执行 BGREWRITEAOF — AOF重写由运维管理"},
|
||||
{"SLAVEOF", "禁止执行 SLAVEOF — 主从切换属于运维操作"},
|
||||
{"REPLICAOF", "禁止执行 REPLICAOF — 主从切换属于运维操作"},
|
||||
{"CLUSTER ", "禁止执行 CLUSTER — 集群管理属于运维操作"},
|
||||
{"KEYS ", "禁止执行 KEYS — 生产环境应使用 SCAN 替代,KEYS 会阻塞Redis"},
|
||||
}
|
||||
cmdParts := parseCommandParts(input.SQLContent)
|
||||
if len(cmdParts) > 0 {
|
||||
upperCmd := strings.ToUpper(cmdParts[0])
|
||||
for _, bl := range redisBlacklist {
|
||||
if upperCmd == strings.TrimSuffix(bl.cmd, " ") || strings.HasPrefix(upperCmd+" ", bl.cmd) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"detail": bl.reason})
|
||||
sqlToCheck = strings.ToUpper(cmdParts[0]) + " " + strings.Join(cmdParts[1:], " ")
|
||||
}
|
||||
}
|
||||
|
||||
var rules []model.AuditRule
|
||||
model.DB.Where("db_type = ? AND is_enabled = ? AND pattern IS NOT NULL AND pattern != ''",
|
||||
input.DBType, true).Find(&rules)
|
||||
|
||||
for _, stmt := range statements {
|
||||
checkStmt := strings.TrimSpace(stmt)
|
||||
if checkStmt == "" {
|
||||
continue
|
||||
}
|
||||
// For Redis, the whole sql_content is one command
|
||||
if input.DBType == "redis" {
|
||||
checkStmt = sqlToCheck
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if rule.Pattern == nil || *rule.Pattern == "" {
|
||||
continue
|
||||
}
|
||||
matched, _ := regexp.MatchString(*rule.Pattern, checkStmt)
|
||||
if matched {
|
||||
// Special: WHERE-check rules — reject only if no WHERE clause
|
||||
if rule.Value != nil && *rule.Value == "where_check" {
|
||||
if strings.Contains(strings.ToUpper(checkStmt), "WHERE") {
|
||||
continue // has WHERE, safe
|
||||
}
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"detail": fmt.Sprintf("审核规则拦截: %s — %s", rule.Name, rule.Message),
|
||||
})
|
||||
return
|
||||
}
|
||||
// value="reject" → block submission, "review"/"warn" → allow but record
|
||||
action := "reject"
|
||||
if rule.Value != nil {
|
||||
action = *rule.Value
|
||||
}
|
||||
if action == "reject" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{
|
||||
"detail": fmt.Sprintf("审核规则拦截: %s — %s", rule.Name, rule.Message),
|
||||
})
|
||||
return
|
||||
}
|
||||
// For warn/review rules, we could attach warnings later
|
||||
// For now, reject-only rules block, others pass through
|
||||
}
|
||||
}
|
||||
// Redis only has one command per ticket, break after first
|
||||
if input.DBType == "redis" {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-fill workflow_id from instance if not provided
|
||||
|
||||
@@ -0,0 +1,485 @@
|
||||
# OpsxSQL 平台配置系统文档
|
||||
|
||||
> 本文档详细说明 OpsxSQL 平台的三套配置体系:`system_settings` 系统设置、`audit_rules` 审核规则、`ldap_configs` LDAP 配置。
|
||||
>
|
||||
> 最后更新:2025-06
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
- [一、配置体系总览](#一配置体系总览)
|
||||
- [二、查询控制 (query)](#二查询控制-query)
|
||||
- [三、审核规则 (audit_rules)](#三审核规则-audit_rules)
|
||||
- [四、数据脱敏 (data_masking)](#四数据脱敏-data_masking)
|
||||
- [五、执行器 (executor)](#五执行器-executor)
|
||||
- [六、Ghost / OSC 在线 DDL (ghost)](#六ghost--osc-在线-ddl-ghost)
|
||||
- [七、安全策略 (security)](#七安全策略-security)
|
||||
- [八、日志清理 (cleanup)](#八日志清理-cleanup)
|
||||
- [九、LDAP 配置 (ldap)](#九ldap-配置-ldap)
|
||||
- [十、未引用 / 待实现配置](#十未引用--待实现配置)
|
||||
- [附录:数据模型](#附录数据模型)
|
||||
|
||||
---
|
||||
|
||||
## 一、配置体系总览
|
||||
|
||||
| 配置体系 | 存储表 | 配置项数 | 用途 |
|
||||
|----------|--------|---------|------|
|
||||
| 系统设置 | `system_settings` | 42 项(7 个分组) | 平台运行参数 |
|
||||
| 审核规则 | `audit_rules` | 34 条(4 种 DB 类型) | SQL 提交审核拦截 |
|
||||
| LDAP 配置 | `ldap_configs` | 独立 CRUD | LDAP 认证集成 |
|
||||
|
||||
### system_settings 分组概览
|
||||
|
||||
| 分组 | 配置项数 | 有效引用 | 状态 |
|
||||
|------|---------|---------|------|
|
||||
| `query` 查询控制 | 5 | 4 | ⚠️ `query_max_rows_examined` 未引用 |
|
||||
| `security` 安全策略 | 5 | 5 | ✅ 全部有效 |
|
||||
| `data_masking` 数据脱敏 | 2 | 2 | ✅ 全部有效 |
|
||||
| `cleanup` 日志清理 | 4 | 4 | ✅ 全部有效 |
|
||||
| `executor` 执行器 | 4 | 2 | ⚠️ `batch_size`/`backup` 未引用 |
|
||||
| `ghost` 在线 DDL | 13 | 13 | ✅ 全部有效 |
|
||||
| `ldap` LDAP | 9 | 0 | ❌ 全部冗余(LDAP 使用独立 `ldap_configs` 表)|
|
||||
|
||||
### 配置读写 API
|
||||
|
||||
| 操作 | 端点 | 说明 |
|
||||
|------|------|------|
|
||||
| 读取所有设置 | `GET /api/v1/settings` | 按 `group` 字段分组返回 |
|
||||
| 批量更新设置 | `PUT /api/v1/settings` | 接收 `[{key, value}, ...]` 数组 |
|
||||
| 读取审核规则 | `GET /api/v1/audit-rules?db_type=mysql` | 可按 `db_type` 过滤 |
|
||||
|
||||
配置均在服务启动时通过 `seedDefaultSettings()` / `seedAuditRules()` 幂等初始化。
|
||||
|
||||
---
|
||||
|
||||
## 二、查询控制 (query)
|
||||
|
||||
查询控制配置作用于在线查询功能(`instance_detail.go`),限制用户对数据库的查询行为。
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 |
|
||||
|-----|------|--------|------|----------|
|
||||
| `query_limit_max` | number | `1000` | SELECT 查询返回的最大行数,超过自动截断 | `instance_detail.go:525` |
|
||||
| `query_timeout` | number | `30` | 单次查询最长执行时间(秒),超时自动终止 | `instance_detail.go:616-617` |
|
||||
| `query_auto_limit` | boolean | `true` | 用户未写 LIMIT 时自动追加,防止全表扫描 | `instance_detail.go:526` |
|
||||
| `query_dangerous_pattern` | boolean | `true` | 检测不带 WHERE 的 DELETE/UPDATE 等高危操作 | `instance_detail.go:540` |
|
||||
| `query_max_rows_examined` | number | `1000000` | 单次查询允许扫描的最大行数 | ⚠️ **待实现** — 仅 seed 定义 |
|
||||
|
||||
### 作用机制
|
||||
|
||||
**query_limit_max** — 查询请求中的 `limit` 参数不能超过此值:
|
||||
```go
|
||||
maxLimit := getSettingInt("query_limit_max", 1000)
|
||||
if input.Limit <= 0 {
|
||||
input.Limit = maxLimit
|
||||
}
|
||||
// ... 后续截断到 maxLimit
|
||||
```
|
||||
|
||||
**query_auto_limit** — 当用户未指定 LIMIT 时,自动追加平台默认上限:
|
||||
```go
|
||||
autoLimit := getSetting("query_auto_limit", "true") == "true"
|
||||
```
|
||||
|
||||
**query_timeout** — 使用 Go `context.WithTimeout` 实现硬超时:
|
||||
```go
|
||||
timeoutSec := getSettingInt("query_timeout", 30)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSec)*time.Second)
|
||||
```
|
||||
|
||||
**query_dangerous_pattern** — 在执行前检测 SQL 中是否包含高危操作关键字:
|
||||
```go
|
||||
if getSetting("query_dangerous_pattern", "true") == "true" {
|
||||
for _, pat := range []string{"DELETE FROM", "UPDATE ", "DROP ", "TRUNCATE", "ALTER ", "RENAME "} {
|
||||
// ... 检测并拦截
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、审核规则 (audit_rules)
|
||||
|
||||
### 概述
|
||||
|
||||
审核规则存储在 `audit_rules` 表中,在 **工单提交时**(`CreateTicket`)自动触发检查。系统预置 34 条规则,覆盖 MySQL、PostgreSQL、Redis、MongoDB 四种数据库类型。
|
||||
|
||||
### 匹配机制
|
||||
|
||||
```
|
||||
CreateTicket → 加载 audit_rules WHERE db_type=? AND is_enabled=true
|
||||
→ 对每条 SQL 语句逐一匹配
|
||||
→ Pattern 正则匹配 ⟶ value=where_check 时使用字符串检测
|
||||
```
|
||||
|
||||
**正则匹配流程**(`ticket.go:299-303`):
|
||||
```go
|
||||
for _, rule := range rules {
|
||||
matched, _ := regexp.MatchString(*rule.Pattern, checkStmt)
|
||||
if matched { /* 规则命中 */ }
|
||||
}
|
||||
```
|
||||
|
||||
**WHERE 特殊检测**(`ticket.go:305-314`):
|
||||
|
||||
由于 Go 的 RE2 正则引擎不支持负向前瞻(negative lookahead),无法用正则"匹配不含 WHERE 的 DELETE"。因此采用两步检测:
|
||||
|
||||
1. 正则匹配 `DELETE FROM` / `UPDATE ... SET` 等基础模式
|
||||
2. 若规则 `value` 字段为 `"where_check"`,则用 `strings.Contains(upperSQL, "WHERE")` 判断是否包含 WHERE 子句
|
||||
3. 包含 WHERE → 跳过(安全);不包含 → 拦截
|
||||
|
||||
### 规则字段说明
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `name` | string | 规则名称 |
|
||||
| `description` | text | 规则描述 |
|
||||
| `rule_type` | string | 规则类型:DDL / DML / DCL / DQL / performance / security |
|
||||
| `severity` | string | 严重级别:error / warning / info |
|
||||
| `db_type` | string | 目标数据库类型:mysql / postgresql / redis / mongodb |
|
||||
| `pattern` | text | 正则表达式(RE2 语法) |
|
||||
| `is_enabled` | boolean | 是否启用 |
|
||||
| `message` | string(500) | 命中时的提示消息 |
|
||||
| `value` | string(255) | 处理方式:`reject`(拦截) / `review`(需审核) / `warn`(警告) / `where_check`(WHERE检测) |
|
||||
| `code` | string(50) | 规则编码(预留) |
|
||||
| `config_json` | text | 扩展配置 JSON(预留) |
|
||||
|
||||
### MySQL 审核规则(12 条)
|
||||
|
||||
| 规则名称 | 类型 | 严重级别 | Pattern | Value | 说明 |
|
||||
|----------|------|---------|---------|-------|------|
|
||||
| 禁止 DROP TABLE | DDL | error | `(?i)^\s*DROP\s+TABLE` | reject | 防止数据永久丢失 |
|
||||
| 禁止 DROP DATABASE | DDL | error | `(?i)^\s*DROP\s+DATABASE` | reject | 禁止删库 |
|
||||
| 禁止 TRUNCATE TABLE | DDL | error | `(?i)^\s*TRUNCATE\s+TABLE?` | reject | 请用 DELETE 逐批删除 |
|
||||
| ALTER TABLE 需审核 | DDL | warning | `(?i)^\s*ALTER\s+TABLE` | review | 需审批后执行 |
|
||||
| 禁止 DELETE 无 WHERE | DML | error | `(?i)^\s*DELETE\s+FROM?\s+\S+\s*$` | reject | 必须包含 WHERE 条件 |
|
||||
| 禁止 UPDATE 无 WHERE | DML | error | `(?i)^\s*UPDATE\s+\S+\s+SET\s+.*\s*$` | reject | 必须包含 WHERE 条件 |
|
||||
| SELECT * 建议 | DQL | info | `(?i)^\s*SELECT\s+\*\s+FROM` | warn | 建议明确指定查询字段 |
|
||||
| INSERT 需指定列 | DML | warning | `(?i)^\s*INSERT\s+INTO\s+\S+\s+VALUES` | review | 应明确指定列名 |
|
||||
| 禁止 GRANT ALL | DCL | error | `(?i)^\s*GRANT\s+ALL` | reject | 禁止授予全部权限 |
|
||||
| 大表变更警告 | performance | warning | _(空 pattern)_ | warn | 影响行数大时警告 |
|
||||
| 禁止不带 LIMIT 的 DELETE | DML | warning | `(?i)^\s*DELETE\s+FROM\s+\S+\s+WHERE` | warn | 建议添加 LIMIT 分批删除 |
|
||||
|
||||
> **注意**:seed 中 MySQL 预置 12 条,实际按 `len(allRules["mysql"])` 计算。上表列出 11 条有明确 pattern 的规则,另含 1 条 "大表变更警告"(无 pattern,作为提示性规则存在)。
|
||||
|
||||
### PostgreSQL 审核规则(7 条)
|
||||
|
||||
| 规则名称 | 类型 | 严重级别 | Value | 说明 |
|
||||
|----------|------|---------|-------|------|
|
||||
| 禁止 DROP TABLE | DDL | error | reject | 同 MySQL |
|
||||
| 禁止 DROP DATABASE | DDL | error | reject | 同 MySQL |
|
||||
| 禁止 DELETE 无 WHERE | DML | error | reject | 同 MySQL |
|
||||
| 禁止 UPDATE 无 WHERE | DML | error | reject | 同 MySQL |
|
||||
| ALTER TABLE 需审核 | DDL | warning | review | 同 MySQL |
|
||||
| SELECT * 建议 | DQL | info | warn | 同 MySQL |
|
||||
| 禁止 GRANT ALL | DCL | error | reject | 同 MySQL |
|
||||
|
||||
### Redis 审核规则(4 条)
|
||||
|
||||
| 规则名称 | 类型 | 严重级别 | Pattern | Value | 说明 |
|
||||
|----------|------|---------|---------|-------|------|
|
||||
| 禁止 FLUSHALL | DDL | error | `(?i)^FLUSHALL` | reject | 禁止清空所有数据 |
|
||||
| 禁止 FLUSHDB | DDL | error | `(?i)^FLUSHDB` | reject | 清空数据库需审批 |
|
||||
| 禁止 CONFIG | security | error | `(?i)^CONFIG\s` | reject | 配置修改需走审批 |
|
||||
| KEYS 命令警告 | performance | error | `(?i)^KEYS\s` | reject | 生产环境禁止 KEYS,请使用 SCAN |
|
||||
|
||||
### MongoDB 审核规则(4 条)
|
||||
|
||||
| 规则名称 | 类型 | 严重级别 | Pattern | Value | 说明 |
|
||||
|----------|------|---------|---------|-------|------|
|
||||
| 禁止 dropDatabase | DDL | error | `dropDatabase` | reject | 禁止删除数据库 |
|
||||
| 禁止 drop 集合 | DDL | error | `(?i)\.drop\(\)` | reject | 禁止删除集合 |
|
||||
| remove 需带条件 | DML | error | `(?i)remove\(\s*\)` | reject | remove 必须带查询条件 |
|
||||
| updateMany 需审核 | DML | warning | `(?i)updateMany` | review | 批量更新需审批 |
|
||||
|
||||
---
|
||||
|
||||
## 四、数据脱敏 (data_masking)
|
||||
|
||||
数据脱敏功能作用于在线查询返回结果,自动将敏感字段的内容替换为 `***`。
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 |
|
||||
|-----|------|--------|------|----------|
|
||||
| `data_masking_enabled` | boolean | `true` | 全局脱敏开关 | `instance_detail.go:1522-1525` |
|
||||
| `data_masking_sensitive_fields` | string | _(见下方)_ | 全局敏感字段列表(逗号分隔) | `instance_detail.go:1531-1534` |
|
||||
|
||||
**默认敏感字段列表**:
|
||||
|
||||
```
|
||||
mobile, concat_mobile, email_alias, email, password, ga_key,
|
||||
private_key, secret, secret_key, key_snow, telephone, username,
|
||||
real_name, token, two_factor_secret, content, phone
|
||||
```
|
||||
|
||||
### 作用机制
|
||||
|
||||
`applyDataMasking()` 函数(`instance_detail.go:1520`)执行以下逻辑:
|
||||
|
||||
1. 读取 `data_masking_enabled`,若非 `true` 则跳过
|
||||
2. 读取 `data_masking_sensitive_fields`,按逗号拆分为字段名列表
|
||||
3. 合并实例级别 `instance.mask_fields` 的字段
|
||||
4. 遍历查询结果的每一行每一列,若列名(不区分大小写)匹配敏感字段,则将值替换为 `***`
|
||||
|
||||
**字段匹配方式**:列名包含(contains)敏感字段名即匹配,不区分大小写。
|
||||
|
||||
**优先级**:全局敏感字段 ∪ 实例级 `mask_fields`,两者合并生效。
|
||||
|
||||
---
|
||||
|
||||
## 五、执行器 (executor)
|
||||
|
||||
执行器配置控制工单中 SQL 语句的实际执行行为。
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 | 状态 |
|
||||
|-----|------|--------|------|----------|------|
|
||||
| `executor_timeout` | number | `300` | SQL 执行的最大等待时间(秒) | `ticket.go:725,766` | ✅ 有效 |
|
||||
| `executor_dry_run` | boolean | `true` | 执行前先 EXPLAIN 验证 SQL 语法 | `ticket.go:770` | ✅ 有效 |
|
||||
| `executor_batch_size` | number | `100` | DML 批量执行时每批的行数 | _(无)_ | ⚠️ 待实现 |
|
||||
| `executor_backup` | boolean | `true` | DML 执行前自动备份受影响数据 | _(无)_ | ⚠️ 待实现 |
|
||||
|
||||
### 作用机制
|
||||
|
||||
**executor_timeout** — 为每个 SQL 语句创建带超时的 context:
|
||||
|
||||
```go
|
||||
// OSC (gh-ost) 路径
|
||||
execTimeout := getSettingInt("executor_timeout", 300)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(execTimeout)*time.Second)
|
||||
|
||||
// 原生执行路径
|
||||
execTimeout := getSettingInt("executor_timeout", 300)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(execTimeout)*time.Second)
|
||||
```
|
||||
|
||||
**executor_dry_run** — 对 DDL 语句执行 EXPLAIN 预检查:
|
||||
|
||||
```go
|
||||
if getSetting("executor_dry_run", "true") == "true" {
|
||||
ddlPrefixes := []string{"CREATE", "ALTER", "DROP", "TRUNCATE", "RENAME"}
|
||||
// 对每条 DDL 语句执行 EXPLAIN,语法错误则拦截
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 六、Ghost / OSC 在线 DDL (ghost)
|
||||
|
||||
Ghost 配置控制 [gh-ost](https://github.com/github/gh-ost) 在线 DDL 变更工具的默认参数。共 13 项配置,全部在 `ghost.go` 的 `applyGhostSettings()` 函数和 OSC 触发逻辑中引用。
|
||||
|
||||
### 基本设置
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 |
|
||||
|-----|------|--------|------|----------|
|
||||
| `ghost_enabled` | boolean | `false` | 启用 gh-ost 在线 DDL 变更工具 | `ticket.go` (OSC 触发判断) |
|
||||
| `ghost_path` | string | `gh-ost` | gh-ost 二进制文件路径 | `ghost.go` (任务创建) |
|
||||
| `ghost_default_mode` | string | `dry_run` | 默认执行模式:`dry_run`(预演) / `execute`(直接执行) | `ghost.go` (任务创建) |
|
||||
| `ghost_osc_priority` | string | `native_first` | OSC 优先级策略:`native_first` / `ghost_first` / `ghost_only` | `ticket.go` (OSC 触发判断) |
|
||||
| `ghost_env_mode` | string | `development` | 环境模式:`development` / `staging` / `production` | `ghost.go` (任务创建) |
|
||||
| `ghost_cloud_type` | string | `self_hosted` | 部署类型:`self_hosted` / `rds` / `cloud` | `ghost.go` (任务创建) |
|
||||
|
||||
### 自动触发
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 |
|
||||
|-----|------|--------|------|----------|
|
||||
| `ghost_auto_trigger_rows` | number | `10` | 表行数超过此阈值(万行)自动推荐使用 OSC | `ticket.go` (大表检测) |
|
||||
| `ghost_auto_trigger_ddl` | string | `ADD COLUMN,MODIFY COLUMN,ADD INDEX` | 自动触发 OSC 的 DDL 操作类型列表(逗号分隔) | `ticket.go` (DDL 类型匹配) |
|
||||
|
||||
### 性能调优
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 |
|
||||
|-----|------|--------|------|----------|
|
||||
| `ghost_default_chunk_size` | number | `1000` | 每次拷贝的行数 | `ghost.go:387` (`applyGhostSettings`) |
|
||||
| `ghost_max_load` | string | `Threads_running=25` | 超过此负载暂停拷贝 | `ghost.go:391` |
|
||||
| `ghost_critical_load` | string | `Threads_running=50` | 超过此负载立即停止 | `ghost.go:395` |
|
||||
| `ghost_allow_on_master` | boolean | `true` | 是否允许直接在主库运行 gh-ost | `ghost.go:403` |
|
||||
| `ghost_max_lag_millis` | number | `1500` | 从库复制延迟超过此值(ms)暂停拷贝 | `ghost.go:399` |
|
||||
|
||||
### 作用机制
|
||||
|
||||
**自动触发 OSC 流程**(`ticket.go`):
|
||||
|
||||
1. 检查 `ghost_enabled` 是否启用
|
||||
2. 检查 SQL 是否为 ALTER TABLE 类型
|
||||
3. 检查 DDL 操作类型是否在 `ghost_auto_trigger_ddl` 列表中
|
||||
4. 检查目标表行数是否超过 `ghost_auto_trigger_rows`(万)
|
||||
5. 根据 `ghost_osc_priority` 决定使用原生 ALTER 还是 gh-ost
|
||||
|
||||
**参数注入流程**(`ghost.go:applyGhostSettings`):
|
||||
|
||||
```go
|
||||
func applyGhostSettings(task *ghost.GhOstTask) {
|
||||
task.ChunkSize = getSettingInt("ghost_default_chunk_size", 1000)
|
||||
task.MaxLoad = getSetting("ghost_max_load", "")
|
||||
task.CriticalLoad = getSetting("ghost_critical_load", "")
|
||||
task.MaxLagMillis = getSettingInt("ghost_max_lag_millis", 1500)
|
||||
task.AllowOnMaster = getSetting("ghost_allow_on_master", "true") == "true"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、安全策略 (security)
|
||||
|
||||
安全策略配置控制用户登录和认证行为,全部在 `auth.go`(handler)和 `middleware/auth.go` 中引用。
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 |
|
||||
|-----|------|--------|------|----------|
|
||||
| `login_max_attempts` | number | `5` | 连续登录失败达到此次数后锁定账号 | `auth.go:34,365` |
|
||||
| `login_lockout_minutes` | number | `30` | 账号锁定后的持续时长(分钟) | `auth.go:35,366` |
|
||||
| `password_min_length` | number | `8` | 用户密码的最短字符数 | `auth.go` (密码校验) |
|
||||
| `password_require_complex` | boolean | `true` | 要求密码包含大小写字母、数字和特殊字符 | `auth.go` (密码校验) |
|
||||
| `session_timeout_hours` | number | `24` | JWT Token 有效期(小时) | `middleware/auth.go:20` |
|
||||
|
||||
### 作用机制
|
||||
|
||||
**登录锁定流程**(`auth.go`):
|
||||
|
||||
1. 登录时检查 `login_max_attempts` 和 `login_lockout_minutes`
|
||||
2. 密码错误时调用 `handleFailedLogin()`,递增 `FailedLoginAttempts`
|
||||
3. 达到 `login_max_attempts` 时,设置 `LockedUntil = now + lockout_minutes`
|
||||
4. 账号锁定期间拒绝登录,返回剩余锁定时间
|
||||
|
||||
**会话超时**(`middleware/auth.go`):
|
||||
|
||||
```go
|
||||
func getSessionTimeoutHours() int {
|
||||
// 从 system_settings 读取 session_timeout_hours
|
||||
// 用于 JWT Token 的过期时间计算
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 八、日志清理 (cleanup)
|
||||
|
||||
清理配置控制各类日志和工单数据的自动保留期限。全部在 `ghost.go`(清理定时任务)和 `main.go`(seed)中引用。
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 引用位置 |
|
||||
|-----|------|--------|------|----------|
|
||||
| `ticket_retention_days` | number | `180` | 超过此天数的已完成/已关闭工单将被清理 | `ghost.go` (清理任务) |
|
||||
| `audit_log_retention_days` | number | `365` | 超过此天数的审计日志将被清理 | `ghost.go` (清理任务) |
|
||||
| `query_history_retention_days` | number | `90` | 超过此天数的查询历史将被清理 | `ghost.go` (清理任务) |
|
||||
| `slow_log_retention_days` | number | `180` | 超过此天数的慢查询日志将被清理 | `ghost.go` (清理任务) |
|
||||
|
||||
---
|
||||
|
||||
## 九、LDAP 配置 (ldap)
|
||||
|
||||
### system_settings 中的 LDAP 配置(⚠️ 冗余)
|
||||
|
||||
以下 9 项配置存在于 `system_settings` 表的 `ldap` 分组中,但 **全部未被业务代码引用**。LDAP 功能使用独立的 `ldap_configs` 表进行管理。
|
||||
|
||||
| Key | 类型 | 默认值 | 说明 | 状态 |
|
||||
|-----|------|--------|------|------|
|
||||
| `ldap_host` | string | `8.217.205.92` | LDAP 服务器地址 | ❌ 冗余 |
|
||||
| `ldap_port` | string | `389` | LDAP 端口 | ❌ 冗余 |
|
||||
| `ldap_use_ssl` | boolean | `false` | 启用 LDAPS (SSL/TLS) | ❌ 冗余 |
|
||||
| `ldap_bind_dn` | string | `cn=admin,dc=opsx,dc=vip` | 管理员 DN | ❌ 冗余 |
|
||||
| `ldap_base_dn` | string | `dc=opsx,dc=vip` | Base DN | ❌ 冗余 |
|
||||
| `ldap_user_filter` | string | `(objectClass=inetOrgPerson)` | 用户过滤器 | ❌ 冗余 |
|
||||
| `ldap_uid_attr` | string | `uid` | UID 属性 | ❌ 冗余 |
|
||||
| `ldap_mail_attr` | string | `mail` | 邮箱属性 | ❌ 冗余 |
|
||||
| `ldap_cn_attr` | string | `cn` | 显示名属性 | ❌ 冗余 |
|
||||
|
||||
### ldap_configs 独立表(实际使用)
|
||||
|
||||
LDAP 功能通过独立的 `ldap_configs` 表管理,提供完整的 CRUD 功能:
|
||||
|
||||
| 功能 | API 端点 | 说明 |
|
||||
|------|----------|------|
|
||||
| 配置 CRUD | `/api/v1/ldap-configs` | 增删改查 LDAP 服务器配置 |
|
||||
| 测试连接 | `/api/v1/ldap-configs/:id/test` | 验证 LDAP 服务器连通性 |
|
||||
| OU 管理 | `/api/v1/ldap-configs/:id/ous` | 管理 LDAP 组织单元 |
|
||||
| 用户导入 | `/api/v1/ldap-configs/:id/import` | 从 LDAP 导入用户到平台 |
|
||||
|
||||
> **建议**:后续版本可清理 `system_settings` 中 `ldap` 分组的冗余配置项,避免维护混淆。
|
||||
|
||||
---
|
||||
|
||||
## 十、未引用 / 待实现配置
|
||||
|
||||
以下配置项在 `seed.go` 中定义,但在业务代码中 **无实际引用**,标注为"待实现":
|
||||
|
||||
| Key | 分组 | 默认值 | 预期功能 | 状态 |
|
||||
|-----|------|--------|----------|------|
|
||||
| `executor_batch_size` | executor | `100` | DML 批量执行时每批的行数 | ⚠️ 待实现 |
|
||||
| `executor_backup` | executor | `true` | DML 执行前自动备份受影响数据 | ⚠️ 待实现 |
|
||||
| `query_max_rows_examined` | query | `1000000` | 单次查询允许扫描的最大行数 | ⚠️ 待实现 |
|
||||
| `ldap_host` | ldap | `8.217.205.92` | LDAP 服务器地址 | ❌ 冗余(使用 ldap_configs 表) |
|
||||
| `ldap_port` | ldap | `389` | LDAP 端口 | ❌ 冗余 |
|
||||
| `ldap_use_ssl` | ldap | `false` | 启用 LDAPS | ❌ 冗余 |
|
||||
| `ldap_bind_dn` | ldap | `cn=admin,dc=opsx,dc=vip` | 管理员 DN | ❌ 冗余 |
|
||||
| `ldap_base_dn` | ldap | `dc=opsx,dc=vip` | Base DN | ❌ 冗余 |
|
||||
| `ldap_user_filter` | ldap | `(objectClass=inetOrgPerson)` | 用户过滤器 | ❌ 冗余 |
|
||||
| `ldap_uid_attr` | ldap | `uid` | UID 属性 | ❌ 冗余 |
|
||||
| `ldap_mail_attr` | ldap | `mail` | 邮箱属性 | ❌ 冗余 |
|
||||
| `ldap_cn_attr` | ldap | `cn` | 显示名属性 | ❌ 冗余 |
|
||||
|
||||
**汇总**:
|
||||
- ⚠️ **待实现**:3 项(`executor_batch_size`、`executor_backup`、`query_max_rows_examined`)
|
||||
- ❌ **冗余**:9 项(`ldap` 分组全部,LDAP 使用独立 `ldap_configs` 表)
|
||||
|
||||
---
|
||||
|
||||
## 附录:数据模型
|
||||
|
||||
### system_settings 表结构
|
||||
|
||||
```go
|
||||
type SystemSetting struct {
|
||||
ID int `gorm:"primaryKey" json:"id"`
|
||||
Key string `gorm:"size:100;not null" json:"key"` // 唯一配置键
|
||||
Value string `gorm:"type:text;not null" json:"value"` // 当前值
|
||||
ValueType string `gorm:"size:20;not null" json:"value_type"` // number / boolean / string
|
||||
Group string `gorm:"size:50;not null" json:"group"` // 分组名
|
||||
Label string `gorm:"size:100;not null" json:"label"` // 前端显示名
|
||||
Desc *string `gorm:"type:text" json:"description"` // 配置说明
|
||||
DefaultValue *string `gorm:"type:text" json:"default_value"` // 默认值
|
||||
SortOrder int `gorm:"not null" json:"sort_order"` // 组内排序
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
### audit_rules 表结构
|
||||
|
||||
```go
|
||||
type AuditRule struct {
|
||||
ID int `gorm:"primaryKey" json:"id"`
|
||||
Name string `gorm:"size:100;not null" json:"name"` // 规则名称
|
||||
Desc *string `gorm:"type:text" json:"description"` // 规则描述
|
||||
RuleType string `gorm:"size:50;not null" json:"rule_type"` // DDL/DML/DCL/DQL/performance/security
|
||||
Severity *string `gorm:"size:20" json:"severity"` // error/warning/info
|
||||
DBType *string `gorm:"size:20" json:"db_type"` // mysql/postgresql/redis/mongodb
|
||||
Pattern *string `gorm:"type:text" json:"pattern"` // RE2 正则表达式
|
||||
IsEnabled *bool ` json:"is_enabled"` // 是否启用
|
||||
Message string `gorm:"size:500;not null" json:"message"` // 命中提示消息
|
||||
Value *string `gorm:"size:255" json:"value"` // reject/review/warn/where_check
|
||||
Code *string `gorm:"size:50" json:"code"` // 规则编码(预留)
|
||||
ConfigJSON *string `gorm:"type:text;column:config_json" json:"config_json"` // 扩展配置(预留)
|
||||
CreatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"created_at"`
|
||||
UpdatedAt time.Time `gorm:"default:CURRENT_TIMESTAMP" json:"updated_at"`
|
||||
}
|
||||
```
|
||||
|
||||
### 配置读取辅助函数
|
||||
|
||||
```go
|
||||
// ghost.go — 读取字符串配置
|
||||
func getSetting(key, fallback string) string {
|
||||
var s model.SystemSetting
|
||||
if model.DB.Where("`key` = ?", key).Take(&s).Error == nil && s.Value != "" {
|
||||
return s.Value
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
// ghost.go — 读取整数配置
|
||||
func getSettingInt(key string, fallback int) int {
|
||||
// 读取字符串并 strconv.Atoi,失败返回 fallback
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **文档维护说明**:新增配置项时,需同步更新 `seed.go` 的 `seedDefaultSettings()` / `seedAuditRules()` 函数及本文档。
|
||||
Reference in new issue
Block a user