Files
opsxsql/backend/app/services/audit_rule_seeds_v2.py
T
logan a9e214ef1a feat: 审核规则持久化到DB + 全面配置去硬编码化
- AuditRule模型: 新增code/config_json字段,支持规则编码和阈值参数持久化
- audit_engine_v2: _load_rules从DB读config_json覆盖默认config
- audit_rule_sync: 启动时自动从_RULE_REGISTRY同步规则到DB(239条)
- settings API: update后刷新query_service配置缓存
- query_service: MAX_ROWS/QUERY_TIMEOUT/MAX_QUERY_LENGTH/MAX_RESULT_MEMORY改为动态读取system_settings
- ticket_executor: SQL_TIMEOUT/REDIS_TIMEOUT/MONGO_TIMEOUT改为动态获取
- settings_seeds: 新增executor分组(sql/redis/mongo超时) + query_result_max_memory_mb
- audit_rule endpoint: 移除无效的AuditEngine缓存清理(每次audit是新实例)
- 测试: 1057 passed(修复所有新引入失败,17个历史遗留)
2026-05-25 02:29:52 +08:00

501 lines
18 KiB
Python
Raw 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.
"""SQL 审核引擎 V2 规则种子数据。
包含 40+ 条覆盖 DDL/DML/DQL/命名规范/性能/安全等多个维度的审核规则。
提供 seed_all_rules(db) 函数用于一次性初始化所有规则到数据库。
"""
from typing import Any, Dict, List
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.audit_rule import AuditRule
# ── DDL 规则种子 ──────────────────────────────────────────────
DDL_RULES: List[Dict[str, Any]] = [
{
"name": "建表必须包含主键",
"description": "CREATE TABLE 语句必须包含 PRIMARY KEY 定义,确保每行数据唯一可标识",
"rule_type": "DDL",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "建表语句缺少主键定义,请添加 PRIMARY KEY",
},
{
"name": "建表必须添加表注释",
"description": "CREATE TABLE 语句必须包含 COMMENT 表注释,便于维护和理解表用途",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "建表时必须添加表注释 (COMMENT='...')",
},
{
"name": "建表字段必须有注释",
"description": "CREATE TABLE 中的每个字段都应包含 COMMENT 注释",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "字段缺少注释,请为每个字段添加 COMMENT",
},
{
"name": "禁止 DROP TABLE",
"description": "生产环境禁止执行 DROP TABLE 操作,数据将永久丢失",
"rule_type": "DDL",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "禁止执行 DROP TABLE 操作,数据将永久丢失",
},
{
"name": "禁止 TRUNCATE TABLE",
"description": "禁止执行 TRUNCATE TABLE 操作,数据不可回滚",
"rule_type": "DDL",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "禁止执行 TRUNCATE TABLE 操作,数据不可回滚",
},
{
"name": "单表索引数量不得超过20个",
"description": "单表索引数量过多会影响写入性能",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "20",
"message": "单表索引数量超过20个,会影响写入性能",
},
{
"name": "单索引字段数不得超过8个",
"description": "单索引包含字段数过多会降低索引效率",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "8",
"message": "单索引包含字段数超过8个,请优化索引设计",
},
{
"name": "禁止使用 FLOAT/DOUBLE 类型",
"description": "浮点类型精度丢失,应使用 DECIMAL",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "禁止使用 FLOAT/DOUBLE 类型,建议使用 DECIMAL 避免精度丢失",
},
{
"name": "CHAR 类型长度不得超过255",
"description": "CHAR 类型长度过大会浪费存储空间,超出请使用 VARCHAR",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "255",
"message": "CHAR 类型长度不得超过255,超出请使用 VARCHAR",
},
{
"name": "表名长度不得超过64字符",
"description": "过长的表名不利于维护和使用",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "64",
"message": "表名长度超过64个字符,请缩短表名",
},
{
"name": "禁止外键约束",
"description": "外键约束影响性能,推荐在应用层处理关联关系",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "禁止使用 FOREIGN KEY 约束,建议在应用层维护数据一致性",
},
{
"name": "必须包含 create_time/update_time 字段",
"description": "建表必须包含 create_time 和 update_time 审计字段,便于追踪数据变更",
"rule_type": "DDL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "建表缺少 create_time 或 update_time 审计字段",
},
]
# ── DML 规则种子 ──────────────────────────────────────────────
DML_RULES: List[Dict[str, Any]] = [
{
"name": "UPDATE/DELETE 必须包含 WHERE 条件",
"description": "UPDATE/DELETE 缺少 WHERE 将影响全表数据,可能导致严重事故",
"rule_type": "DML",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "UPDATE/DELETE 语句必须包含 WHERE 条件",
},
{
"name": "单次 INSERT 最多500行",
"description": "单次 INSERT 行数过多会导致事务过大,影响性能和复制延迟",
"rule_type": "DML",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "500",
"message": "单次 INSERT 最多插入500行",
},
{
"name": "UPDATE/DELETE 禁止带 LIMIT",
"description": "UPDATE/DELETE 使用 LIMIT 可能导致部分数据变更,数据不一致",
"rule_type": "DML",
"severity": "warning",
"db_type": "mysql",
"pattern": None,
"value": None,
"message": "UPDATE/DELETE 语句禁止使用 LIMIT",
},
{
"name": "禁止 INSERT 不指定字段列表",
"description": "INSERT 必须显式指定字段列表,避免表结构变更导致错误",
"rule_type": "DML",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "INSERT 语句必须显式指定字段列表",
},
{
"name": "禁止批量 UPDATE 不带主键条件",
"description": "批量 UPDATE 必须带主键 WHERE 条件,防止误更新大量数据",
"rule_type": "DML",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "批量 UPDATE 必须包含主键条件",
},
{
"name": "DELETE 必须有 LIMIT(除非带主键条件)",
"description": "DELETE 必须带 LIMIT 或主键 WHERE 条件,防止误删大量数据",
"rule_type": "DML",
"severity": "warning",
"db_type": "mysql",
"pattern": None,
"value": None,
"message": "DELETE 建议添加 LIMIT 或使用主键条件",
},
{
"name": "禁止 REPLACE INTO",
"description": "REPLACE INTO 会先删除再插入,风险较高",
"rule_type": "DML",
"severity": "error",
"db_type": "mysql",
"pattern": None,
"value": None,
"message": "禁止使用 REPLACE INTO,请使用 INSERT ... ON DUPLICATE KEY UPDATE 替代",
},
{
"name": "INSERT 必须指定字段列表",
"description": "INSERT 语句必须显式指定字段列表,避免表结构变更导致错误",
"rule_type": "DML",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "INSERT 语句必须显式指定字段列表",
},
]
# ── DQL 规则种子 ──────────────────────────────────────────────
DQL_RULES: List[Dict[str, Any]] = [
{
"name": "禁止 SELECT *",
"description": "SELECT * 返回不必要的列,影响网络传输和内存使用",
"rule_type": "DQL",
"severity": "info",
"db_type": "all",
"pattern": None,
"value": None,
"message": "禁止使用 SELECT *,请明确指定需要的字段",
},
{
"name": "SELECT 必须有 LIMIT",
"description": "SELECT 查询应添加 LIMIT 限制返回行数,防止返回过多数据",
"rule_type": "DQL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "SELECT 查询缺少 LIMIT,建议添加 LIMIT 防止返回过多数据",
},
{
"name": "禁止在 WHERE 中使用函数",
"description": "WHERE 中使用函数会导致索引失效,性能下降",
"rule_type": "DQL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "WHERE 子句中使用函数会导致索引失效",
},
{
"name": "禁止隐式类型转换",
"description": "不同类型比较会导致索引失效,请确保比较双方类型一致",
"rule_type": "DQL",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": None,
"message": "WHERE 条件中存在隐式类型转换风险",
},
{
"name": "ORDER BY 必须使用索引字段",
"description": "ORDER BY 非索引字段会导致文件排序(filesort),影响性能",
"rule_type": "DQL",
"severity": "info",
"db_type": "all",
"pattern": None,
"value": None,
"message": "ORDER BY 字段建议添加索引,避免 filesort 性能问题",
},
]
# ── 命名规范规则种子 ──────────────────────────────────────────
NAMING_RULES: List[Dict[str, Any]] = [
{
"name": "表名必须小写下划线命名",
"description": "表名应使用小写下划线格式 (snake_case),如 user_info",
"rule_type": "naming",
"severity": "warning",
"db_type": "all",
"pattern": r"^[a-z][a-z0-9_]*$",
"value": None,
"message": "表名不符合小写下划线命名规范",
},
{
"name": "字段名必须小写下划线命名",
"description": "字段名应使用小写下划线格式 (snake_case),如 user_name",
"rule_type": "naming",
"severity": "warning",
"db_type": "all",
"pattern": r"^[a-z][a-z0-9_]*$",
"value": None,
"message": "字段名不符合小写下划线命名规范",
},
{
"name": "索引名必须以 idx_ 或 uk_ 开头",
"description": "索引命名规范:普通索引 idx_,唯一索引 uk_,如 idx_user_name",
"rule_type": "naming",
"severity": "info",
"db_type": "all",
"pattern": r"^(idx_|uk_)",
"value": None,
"message": "索引名应以 idx_ 或 uk_ 开头",
},
{
"name": "主键名必须为 id 或 pk_ 开头",
"description": "主键字段命名规范,建议使用 id 或 pk_ 前缀",
"rule_type": "naming",
"severity": "info",
"db_type": "all",
"pattern": r"^(id|pk_)",
"value": None,
"message": "主键名建议使用 'id' 或 'pk_' 前缀",
},
{
"name": "布尔字段以 is_/has_ 开头",
"description": "布尔类型字段应以 is_ 或 has_ 前缀命名,如 is_active",
"rule_type": "naming",
"severity": "info",
"db_type": "all",
"pattern": r"^(is_|has_)",
"value": None,
"message": "布尔字段建议以 is_ 或 has_ 开头命名",
},
]
# ── 性能规则种子 ──────────────────────────────────────────────
PERF_RULES: List[Dict[str, Any]] = [
{
"name": "禁止大表全表扫描",
"description": "大表(>10000行)查询缺少 WHERE 条件会导致全表扫描,严重影响性能",
"rule_type": "performance",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "10000",
"message": "SELECT 查询缺少 WHERE 条件,大表下会导致全表扫描",
},
{
"name": "IN 子句值数量不得超过100个",
"description": "IN 子句值过多会导致性能下降,建议使用临时表或 JOIN 替代",
"rule_type": "performance",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "100",
"message": "IN 子句包含超过100个值,建议使用临时表或 JOIN 替代",
},
{
"name": "禁止 LIKE '%xxx%' 前缀模糊匹配",
"description": "前缀模糊匹配无法使用索引,建议使用全文检索",
"rule_type": "performance",
"severity": "warning",
"db_type": "all",
"pattern": r"LIKE\s+'%[^']+'",
"value": None,
"message": "LIKE '%xxx%' 无法使用索引,建议改用全文检索或后缀索引",
},
{
"name": "JOIN 不得超过5个表",
"description": "JOIN 表过多会导致查询计划复杂化和性能下降",
"rule_type": "performance",
"severity": "warning",
"db_type": "all",
"pattern": None,
"value": "5",
"message": "JOIN 涉及超过5个表,请优化查询结构",
},
{
"name": "禁止 ORDER BY RAND()",
"description": "ORDER BY RAND() 性能极差,会导致全表扫描",
"rule_type": "performance",
"severity": "error",
"db_type": "mysql",
"pattern": r"ORDER\s+BY\s+RAND\s*\(",
"value": None,
"message": "禁止 ORDER BY RAND(),性能极差,建议在应用层随机采样",
},
{
"name": "子查询建议改为 JOIN",
"description": "相关子查询性能较差,建议改为 JOIN",
"rule_type": "performance",
"severity": "info",
"db_type": "all",
"pattern": None,
"value": None,
"message": "WHERE 中的子查询建议改为 JOIN 以提升性能",
},
{
"name": "大表禁止 OFFSET 分页",
"description": "大表使用 OFFSET 分页性能差,建议使用游标分页",
"rule_type": "performance",
"severity": "warning",
"db_type": "all",
"pattern": r"OFFSET\s+\d+",
"value": None,
"message": "使用 OFFSET 分页在大表上性能较差,建议改用游标分页",
},
{
"name": "禁止 SELECT DISTINCT(除非必要)",
"description": "DISTINCT 需要去重操作,影响性能",
"rule_type": "performance",
"severity": "info",
"db_type": "all",
"pattern": r"SELECT\s+DISTINCT\b",
"value": None,
"message": "SELECT DISTINCT 影响性能,请确认是否必要",
},
]
# ── 安全规则种子 ──────────────────────────────────────────────
SEC_RULES: List[Dict[str, Any]] = [
{
"name": "禁止查询 password/secret/token 字段",
"description": "禁止在 SELECT 中查询敏感字段,防止密码等敏感信息泄露",
"rule_type": "security",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "禁止在查询中返回 password/secret/token 等敏感字段",
},
{
"name": "禁止 SQL 中硬编码密码",
"description": "SQL 中不应包含硬编码密码,防止密码泄露",
"rule_type": "security",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "SQL 中发现可能的硬编码密码,禁止在 SQL 中直接写入密码",
},
{
"name": "禁止 UNION(SQL 注入风险)",
"description": "UNION 语句存在 SQL 注入风险,禁止在工单中使用",
"rule_type": "security",
"severity": "error",
"db_type": "all",
"pattern": r"\bUNION\b",
"value": None,
"message": "禁止 UNION 操作,存在 SQL 注入风险",
},
{
"name": "禁止注释符",
"description": "SQL 中不应包含 -- 或 /* */ 注释,可能存在注入风险",
"rule_type": "security",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "SQL 中禁止包含注释符,可能存在注入风险",
},
{
"name": "禁止多语句(分号分隔)",
"description": "禁止单条 SQL 中包含多条语句,防止注入攻击",
"rule_type": "security",
"severity": "error",
"db_type": "all",
"pattern": None,
"value": None,
"message": "禁止提交多条 SQL 语句(分号分隔),请拆分为多个工单",
},
]
# ── 合并所有规则 ──────────────────────────────────────────────
ALL_V2_RULES: List[Dict[str, Any]] = (
DDL_RULES + DML_RULES + DQL_RULES + NAMING_RULES + PERF_RULES + SEC_RULES
)
async def seed_all_rules(db: AsyncSession) -> List[AuditRule]:
"""将所有 V2 规则种子数据插入数据库(跳过已存在的规则)。
Args:
db: 数据库异步会话
Returns:
新创建的 AuditRule 列表
"""
created: List[AuditRule] = []
for rule_dict in ALL_V2_RULES:
# 检查是否已存在(按 name 去重)
exists = await db.execute(select(AuditRule).where(AuditRule.name == rule_dict["name"]))
if exists.scalar_one_or_none():
continue # 跳过已存在的规则
rule = AuditRule(**rule_dict)
db.add(rule)
created.append(rule)
await db.commit()
for rule in created:
await db.refresh(rule)
return created