行筛选和列掩码的常见模式

本页介绍实现 ABAC 行筛选器和列掩码策略的常见模式。

与 Cast 兼容的掩码函数

Azure Databricks 会自动将掩码函数的输出转换为与目标列的数据类型匹配。 参阅 列掩码的自动类型转换

以下模式可帮助你设计与强制转换兼容的掩码函数。

返回可转换类型

屏蔽某列时,请返回相同的数据类型或可转换为该数据类型的类型。 检查策略目标列的数据类型,并验证函数的每个分支是否返回兼容值。

-- Succeeds: Masks a DOUBLE column, returns DOUBLE in every branch
CREATE FUNCTION mask_salary(salary DOUBLE, user_role STRING)
RETURNS DOUBLE
RETURN CASE
  WHEN user_role IN ('admin', 'hr') THEN salary
  WHEN user_role = 'manager' THEN ROUND(salary / 1000) * 1000
  ELSE 0.0
END;

-- Fails: 'CONFIDENTIAL' cannot be cast to a DOUBLE column type
CREATE FUNCTION mask_salary_as_text(salary DOUBLE, user_role STRING)
RETURNS STRING
RETURN CASE
  WHEN user_role IN ('admin', 'hr') THEN CAST(salary AS STRING)
  ELSE 'CONFIDENTIAL'
END;

避免数值溢出

当掩码函数接受并返回比目标列更宽的数值类型时,结果会自动强制转换回列的类型。 如果返回的值超出较窄类型的范围,则强制转换溢出,并且查询在运行时失败。

-- The target column is TINYINT (max 127). The input is upcast to BIGINT
-- for the function. Adding 1000 produces a BIGINT result that overflows
-- when cast back to TINYINT.
CREATE FUNCTION mask_score(score BIGINT)
RETURNS BIGINT
RETURN score + 1000;

将 VARIANT 用于多种列数据类型

有关 多个列类型,请参阅基于 VARIANT 的掩码函数

测试类型转换兼容性

测试具有不同数据模式的掩码函数。

SELECT CAST(mask_salary(salary, 'admin') AS DOUBLE) FROM employees;
SELECT CAST(mask_salary(salary, 'manager') AS DOUBLE) FROM employees;
SELECT CAST(mask_salary(salary, 'viewer') AS DOUBLE) FROM employees;

适用于多种列类型的基于 VARIANT 的掩码函数

当需要屏蔽不同数据类型(例如,INTDOUBLEDECIMAL(10,2)DECIMAL(15,5)等)的列时,可以编写一个接受和返回VARIANT类型的单一掩码 UDF。 Azure Databricks 自动将列掩码函数的输出强制转换为与目标列的数据类型相匹配,并遵循 ANSI SQL 标准。

此方法可减少所需的 UDF 和策略数。 一个函数处理所有类型,而不是为每个列类型编写单独的掩码函数。

使用单个函数屏蔽多个数值类型

无需为每个数值精度创建单独的掩码函数,而可以使用 VARIANT 单个函数处理所有这些掩码函数:

CREATE FUNCTION mask_numeric(val VARIANT)
RETURNS VARIANT
DETERMINISTIC
RETURN 0::VARIANT;

此函数返回 0 作为 VARIANT,而 Azure Databricks 会自动转换为目标列的类型。 使用此函数的单个 ABAC 策略可以屏蔽INTDOUBLEDECIMAL列,而无需针对每个精度使用单独的函数。

如果想要在函数中显式保留类型,则可以对类型进行分支,并为每个使用以下命令 schema_of_variant()返回适当的掩码值:

-- Use VARIANT to accommodate different data types
CREATE FUNCTION flexible_mask(data VARIANT)
RETURNS VARIANT
RETURN CASE
  WHEN schema_of_variant(data) = 'INT' THEN 0::VARIANT
  WHEN schema_of_variant(data) = 'DATE' THEN DATE'1970-01-01'::VARIANT
  WHEN schema_of_variant(data) = 'DOUBLE' THEN 0.00::VARIANT
  ELSE NULL::VARIANT
END;

使用 VARIANT 屏蔽结构体列

对于 Databricks Runtime 18.1 及更高版本,还可以在 ABAC 策略中通过将结构列强制转换为 VARIANT 来屏蔽结构列。 对结构的形状进行分支以选择性地编辑字段:

注释

ABAC 列掩码策略仅支持结构强制转换为 VARIANT 以进行掩码操作。

以下示例用于 schema_of_variant() 标识两个不同的结构形状,并在每个结构中对敏感字段进行编辑:

CREATE FUNCTION flexible_mask(data VARIANT)
RETURNS VARIANT
RETURN CASE
WHEN schema_of_variant(data) = 'OBJECT<age: BIGINT, email: STRING>' THEN
  to_variant_object(named_struct('age', data:age, 'email', 'redacted'))
WHEN schema_of_variant(data) = 'OBJECT<id: BIGINT, ssn: STRING>' THEN
  to_variant_object(named_struct('id', data:id, 'ssn', 'xxx-xx-xxxx'))
ELSE NULL::VARIANT
END;

在标记敏感列之前阻止访问

常见的治理模式是基于数据是否已分类来控制访问。 可以使用默认限制性标记和策略来实现此方案,这些标记和策略根据分类状态强制实施不同级别的保护。

  1. 默认情况下,通过自动化或通过标记继承将标记 classification : unverified 应用于所有新对象,方法是在目录或架构级别应用标记,以便添加到目录或架构的任何新表都自动继承标记。
  2. 创建阻止访问标记 classification : unverified表的行筛选器策略。
  3. 创建一个列掩码策略,用于屏蔽标记不再存在的表中的 classification : unverified 敏感列。
  4. 数据专员完成分类后,更新标记。 阻止策略不再匹配,掩码策略生效。
-- Block access to unverified tables for all non-admin users
CREATE FUNCTION catalog.schema.block_all() RETURNS BOOLEAN
  RETURN FALSE;

CREATE POLICY block_unverified
ON CATALOG my_catalog
ROW FILTER catalog.schema.block_all
TO `account users` EXCEPT `data_admins`
FOR TABLES
WHEN has_tag_value('classification', 'unverified');

若要在对敏感数据进行分类后保护敏感数据,请定义一个列掩码策略, classification : unverified 该策略在标记不再存在时生效:

CREATE FUNCTION catalog.schema.mask_pii(val STRING)
RETURNS STRING
RETURN '***';

CREATE POLICY mask_reviewed_pii
ON CATALOG my_catalog
COLUMN MASK catalog.schema.mask_pii
TO `account users`
EXCEPT `data_admins`
FOR TABLES
WHEN NOT has_tag_value('classification', 'unverified')
MATCH COLUMNS (has_tag_value('pii', 'name') OR has_tag_value('pii', 'address')) AS m
ON COLUMN m;

不使用正则表达式的部分显示

使用字符串操作而不是正则表达式显示敏感值的一部分。 基于正则表达式的掩码扫描每一行的整个值,这在大型文本字段上非常昂贵(请参阅 避免大型文本字段上的正则表达式掩码)。

CREATE FUNCTION mask_ssn(ssn STRING, show_last INT) RETURNS STRING
DETERMINISTIC
  RETURN CONCAT('***-**-', RIGHT(ssn, show_last));

一致的哈希(确定性假名化)

一致的哈希(也称为确定性假名化)将敏感数据替换为多个表中相同的哈希值。 将 DETERMINISTIC 函数标记为告知引擎,该函数始终为相同的输入返回相同的结果,这有助于优化查询。 请参阅 “使用确定性、错误安全的表达式”。

以下函数一致地对字符串值进行哈希处理,并使用 version 参数来支持密钥轮换。 通过策略的version子句增量USING COLUMNS数字,以生成新的哈希,同时不影响使用以前版本的历史数据。 该函数在进行哈希处理之前将原始值与版本号连接在一起,因此具有相同版本的相同输入始终生成相同的哈希。

CREATE FUNCTION pseudonymize(val STRING, version INT) RETURNS STRING
DETERMINISTIC
  RETURN SHA2(CONCAT(val, CAST(version AS STRING)), 256);

使用仅列谓词的行筛选

使用仅引用表列的简单布尔逻辑筛选行。 仅限于列的谓词实现谓词下推,这允许引擎在扫描期间跳过不相关的数据(参见 了解受保护表的谓词下推)。

CREATE FUNCTION filter_by_region(region STRING, allowed STRING)
RETURNS BOOLEAN
DETERMINISTIC
  RETURN array_contains(split(allowed, ','), lower(region));

与将允许的区域作为常量传递的策略一起使用:

CREATE POLICY regional_access
ON CATALOG analytics
ROW FILTER filter_by_region
TO 'emea_team'
FOR TABLES
MATCH COLUMNS has_tag('region') AS rgn
USING COLUMNS (rgn, 'emea,apac');

当表具有表示相关属性的多个列(例如,ship_to_countrybill_to_country)时,你可以通过单独的标记条件匹配它们,并将两者传递给单个 UDF。 这可避免为每个列创建单独的策略。 策略最多可在子句中包含 MATCH COLUMNS 三个列表达式(请参阅 策略配额)。

CREATE FUNCTION filter_by_countries(ship_country STRING, bill_country STRING, allowed STRING)
RETURNS BOOLEAN
DETERMINISTIC
  RETURN array_contains(split(allowed, ','), lower(ship_country))
      OR array_contains(split(allowed, ','), lower(bill_country));

CREATE POLICY regional_orders
ON SCHEMA prod.orders
ROW FILTER filter_by_countries
TO analysts
FOR TABLES
WHEN has_tag_value('sensitivity', 'high')
MATCH COLUMNS
  has_tag('ship_country') AS ship,
  has_tag('bill_country') AS bill
USING COLUMNS (ship, bill, 'us,ca,mx');

分析师只能看到发货国家或计费国家位于其允许列表中的订单。

ABAC 策略 UDF 中的查找表

如果访问规则因每个用户而异,并且不能单独通过策略的 TO/EXCEPT 子句表示,则可以针对小型查阅表格检查访问权限。 尽可能使用 TO/EXCEPT ,因为它是针对主体的首选方法(请参阅 面向主体的方法)。 使查阅表格保持较小,以便优化器将子查询转换为广播哈希联接(请参阅 “保持查找表较小”。

CREATE TABLE access_rules (
  principal VARCHAR(255),
  priority VARCHAR(64)
);

INSERT INTO access_rules VALUES
  ('alice@company.com', '1-URGENT'),
  ('alice@company.com', '2-HIGH'),
  ('bob@company.com', '1-URGENT');

CREATE FUNCTION priority_allowed(o_priority STRING) RETURNS BOOLEAN
RETURN EXISTS (
  SELECT 1 FROM access_rules
  WHERE principal = session_user() AND priority = o_priority
);

CREATE POLICY priority_filter
ON CATALOG operations
ROW FILTER priority_allowed
TO `account users`
FOR TABLES
MATCH COLUMNS has_tag('priority') AS pri
USING COLUMNS (pri);