本页介绍实现 ABAC 行筛选器和列掩码策略的常见模式。
有关总体概念,请参阅 基于属性的访问控制(ABAC)的核心概念。
有关策略语法,请参阅 创建和管理行筛选器和列掩码策略。
如果你的环境使用 RBAC,请参阅将 RBAC 与 ABAC 配合使用 ,了解用户承担角色时标识功能的行为方式,以及将 RBAC 与 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 用于多种列数据类型
测试类型转换兼容性
测试具有不同数据模式的掩码函数。
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 的掩码函数
当需要屏蔽不同数据类型(例如,INT、DOUBLE、DECIMAL(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 策略可以屏蔽INT和DOUBLEDECIMAL列,而无需针对每个精度使用单独的函数。
如果想要在函数中显式保留类型,则可以对类型进行分支,并为每个使用以下命令 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;
在标记敏感列之前阻止访问
常见的治理模式是基于数据是否已分类来控制访问。 可以使用默认限制性标记和策略来实现此方案,这些标记和策略根据分类状态强制实施不同级别的保护。
- 默认情况下,通过自动化或通过标记继承将标记
classification : unverified应用于所有新对象,方法是在目录或架构级别应用标记,以便添加到目录或架构的任何新表都自动继承标记。 - 创建阻止访问标记
classification : unverified表的行筛选器策略。 - 创建一个列掩码策略,用于屏蔽标记不再存在的表中的
classification : unverified敏感列。 - 数据专员完成分类后,更新标记。 阻止策略不再匹配,掩码策略生效。
-- 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_country和bill_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);