查询规则与脱敏规则编写
当内置规则不能完全覆盖团队规范时,可以在 CloudDM 中编写自定义查询规则或脱敏规则。本文只介绍规则脚本的写法,并通过可直接修改的示例说明常用语法。
CloudDM 使用 ANTLR4 定义规则脚本语法。编写本文中的规则不需要预先掌握 ANTLR4;如需进一步了解其词法规则、语法规则和解析器,可以阅读 ANTLR4 官方文档。
开始编写规则
进入 数据访问 > 安全规则 > 规则模版,选择 查询规则 或 脱敏规则,然后新建规则模版。
编写查询规则时,需要先选择规则的对象类型,例如表、列、查询、更新或删除。对象类型决定脚本可以读取哪些 @domain 字段。建议先打开一个对象类型相同的内置规则,在其脚本上修改;这样更容易选到正确的上下文字段。
一个规则脚本通常由以下部分组成:
// 1. 定义可配置参数
#define "参数名" as string
default "默认值"
hint "参数说明"
// 2. 根据上下文判断是否需要检查
if @domain.sqlType != '目标语句类型' then
return true
end
// 3. 返回检查结果
return true
脚本语句不需要以分号结尾。单行注释使用 //,多行注释使用 /* ... */。
读取数据
规则中最常用的三种数据写法如下:
| 写法 | 用途 | 示例 |
|---|---|---|
@domain.字段 | 读取本次检查的 SQL 或结果列信息 | @domain.sqlType、@domain.column |
#{参数名} | 读取规则设置中填写的参数 | #{maxCount} |
@func.分类.函数(...) | 调用规则内置函数 | @func.string.isBlank(@domain.comment) |
查询规则常用的通用字段包括:
| 字段 | 含义 |
|---|---|
@domain.sqlType | 当前 SQL 类型,例如 SELECT、CREATE_TABLE、UPDATE、DELETE |
@domain.dsType | 数据源类型 |
@domain.envName | 环境名称 |
@domain.dsName | 数据源名称 |
@domain.userName | 当前用户名 |
@domain.userRole | 当前用户角色 |
不同对象类型还会提供自己的字段。例如,表规则可以使用 table、comment、columns、hasPrimary;列规则可以使用 column、typeName、typeDesc、nullable、length;查询、更新和删除规则可以按类型使用 hasWhere、whereColumns、hasUnion、setColumns 等字段。实际可用字段以当前版本中相同对象类型的内置规则为准。
脱敏规则针对查询结果中的单元格执行,常用字段包括:
| 字段 | 含义 |
|---|---|
@domain.catalog | Catalog 名称 |
@domain.schema | Schema 名称 |
@domain.table | 表名 |
@domain.column | 列名 |
@domain.dbType | 列的数据类型 |
@domain.value | 当前单元格的字符串值;数据库值为 NULL 时这里为空字符串 |
@domain.itIsNull | 当前数据库值是否为 NULL |
@domain.index | 当前列在结果集中的位置 |
@domain.allColumns | 当前结果集的全部列名 |
@domain.hasRange | 当前脱敏规则是否配置了生效范围 |
定义参数
使用 #define 把容易变化的值交给规则使用者配置:
#define "maxCount" as int
default "50"
hint "表允许的最大字段数量"
#define "allow" as bool
default "false"
enum ["true", "false"]
hint "是否允许不带 WHERE 条件"
参数定义支持以下内容:
| 部分 | 是否必填 | 说明 |
|---|---|---|
#define "名称" | 是 | 声明参数,名称通过 #{名称} 引用 |
as 类型 | 否 | 可使用 bool、int、integer、float、decimal、string、date、time、datetime |
default "值" | 否 | 参数默认值 |
enum ["值1", "值2"] | 否 | 限制可选值 |
hint "说明" | 否 | 在规则设置页展示的参数说明 |
规则参数以字符串形式传入。需要参与布尔或数值运算时,先使用 cast 转换:
cast(#{allow} as bool)
cast(#{maxCount} as int)
cast(@domain.length as int)
脚本写好后,点击 提取参数,确认参数名、类型、默认值、可选范围和说明是否正确。
编写条件
条件分支
条件分支以 if 开始、以 end 结束,可以使用 elseif 和 else:
if @domain.sqlType == 'CREATE_TABLE' then
checkName = @domain.table
elseif @domain.sqlType == 'ALTER_TABLE_RENAME' then
checkName = @domain.newName
else
return true
end
return @func.string.lowerCase(checkName) == checkName
checkName = ... 用于定义脚本内的临时变量,后续可以直接通过变量名读取。
常用运算符
| 类型 | 运算符 | 示例 |
|---|---|---|
| 比较 | ==、!=、>、>=、<、<= | @domain.sqlType == 'SELECT' |
| 逻辑 | and、or、not,也可写成 &&、` | |
| 集合 | in、not in | @domain.sqlType in ['UPDATE', 'DELETE'] |
| 正则 | matches、not matches | @domain.column matches '.*phone.*' |
| 算术 | +、-、*、/、% | cast(#{maxCount} as int) + 1 |
字符串使用单引号或双引号,列表使用方括号,例如 ['SELECT', 'UPDATE']。复杂条件建议用括号明确优先级。
常用函数
| 函数 | 用途 |
|---|---|
@func.string.isBlank(value) | 判断字符串是否为空 |
@func.string.isNotBlank(value) | 判断字符串是否非空 |
@func.string.trim(value) | 去除首尾空格 |
@func.string.upperCase(value) | 转为大写 |
@func.string.lowerCase(value) | 转为小写 |
@func.string.contains(value, search) | 判断是否包含指定字符串 |
@func.string.containsIgnoreCase(value, search) | 忽略大小写判断是否包含 |
@func.string.containsIgnoreCaseAny(value, list) | 忽略大小写判断是否包含列表中的任一字符串 |
@func.string.equalsIgnoreCaseAny(value, list) | 忽略大小写判断是否等于列表中的任一字符串 |
@func.string.startsWith(value, prefix) | 判断字符串开头 |
@func.string.endsWith(value, suffix) | 判断字符串结尾 |
@func.string.split(value, separator) | 按分隔符拆分字符串 |
@func.string.length(value) | 获取字符串长度 |
@func.string.substring(value, start, end) | 截取字符串,起始下标从 0 开始 |
@func.array.size(value) | 获取数组或列表长度 |
@func.array.containAny(list1, list2) | 判断两个列表是否存在相同元素 |
@func.number.isNumber(value) | 判断字符串能否表示数字 |
查询规则示例
查询规则必须返回布尔值:
true:检查通过。false:检查不通过,CloudDM 按规则配置的风险等级处理。
建议让每一条执行路径都显式返回结果。与规则无关的 SQL 应尽早返回 true,避免误报。
下面的规则限制新建表的字段数量:
#define "maxCount" as int
default "50"
hint "表允许的最大字段数量"
if @domain.sqlType != 'CREATE_TABLE' then
return true
end
return @func.array.size(@domain.columns) <= cast(#{maxCount} as int)
下面的规则要求 UPDATE 必须带有 WHERE 条件,同时允许管理员在规则设置中关闭限制:
#define "allow" as bool
default "false"
enum ["true", "false"]
hint "是否允许 UPDATE 不带 WHERE 条件"
if @domain.sqlType != 'UPDATE' then
return true
end
return cast(#{allow} as bool) or @domain.hasWhere
脱敏规则示例
脱敏规则返回脱敏算法名称。当前可使用:
"algorithm::FULL_MASK":将命中的值显示为******。"algorithm::ORIGINAL":保留原值。
下面的规则对指定列中的中国大陆手机号进行全遮掩:
#define "columns" as string
default "phone,mobile"
hint "需要识别的列名,多个列名使用逗号分隔"
if @domain.itIsNull then
return "algorithm::ORIGINAL"
end
columnMatched = @func.string.equalsIgnoreCaseAny(
@domain.column,
@func.string.split(#{columns}, ',')
)
if columnMatched and @domain.value matches '^1[3-9][0-9]{9}$' then
return "algorithm::FULL_MASK"
else
return "algorithm::ORIGINAL"
end
如果只想根据规则的生效范围决定是否脱敏,可以写成:
if @domain.hasRange then
return "algorithm::FULL_MASK"
else
return "algorithm::ORIGINAL"
end
验证并启用
完成脚本后,按以下顺序操作:
- 点击 提取参数,检查参数配置。
- 使用页面提供的校验功能确认脚本语法正确。
- 保存规则模版,并将其应用到目标安全规则。
- 配置规则参数和生效范围,再启用规则。
- 在 环境 页面为目标环境关联该安全规则。
- 先在非生产环境准备命中和不命中的样例,分别验证规则结果。
