跳到主要内容

查询规则与脱敏规则编写

当内置规则不能完全覆盖团队规范时,可以在 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 类型,例如 SELECTCREATE_TABLEUPDATEDELETE
@domain.dsType数据源类型
@domain.envName环境名称
@domain.dsName数据源名称
@domain.userName当前用户名
@domain.userRole当前用户角色

不同对象类型还会提供自己的字段。例如,表规则可以使用 tablecommentcolumnshasPrimary;列规则可以使用 columntypeNametypeDescnullablelength;查询、更新和删除规则可以按类型使用 hasWherewhereColumnshasUnionsetColumns 等字段。实际可用字段以当前版本中相同对象类型的内置规则为准。

脱敏规则针对查询结果中的单元格执行,常用字段包括:

字段含义
@domain.catalogCatalog 名称
@domain.schemaSchema 名称
@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 类型可使用 boolintintegerfloatdecimalstringdatetimedatetime
default "值"参数默认值
enum ["值1", "值2"]限制可选值
hint "说明"在规则设置页展示的参数说明

规则参数以字符串形式传入。需要参与布尔或数值运算时,先使用 cast 转换:

cast(#{allow} as bool)
cast(#{maxCount} as int)
cast(@domain.length as int)

脚本写好后,点击 提取参数,确认参数名、类型、默认值、可选范围和说明是否正确。

编写条件

条件分支

条件分支以 if 开始、以 end 结束,可以使用 elseifelse

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'
逻辑andornot,也可写成 &&、`
集合innot in@domain.sqlType in ['UPDATE', 'DELETE']
正则matchesnot 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

验证并启用

完成脚本后,按以下顺序操作:

  1. 点击 提取参数,检查参数配置。
  2. 使用页面提供的校验功能确认脚本语法正确。
  3. 保存规则模版,并将其应用到目标安全规则。
  4. 配置规则参数和生效范围,再启用规则。
  5. 环境 页面为目标环境关联该安全规则。
  6. 先在非生产环境准备命中和不命中的样例,分别验证规则结果。
联系我们
微信二维码

扫码添加微信,获取技术支持