本节摘要:RBAC 的粒度是"整张表",业务需求常常细到"某列对某些人脱敏、某行对某些租户隐形"。治理三件套补上这个粒度:标签(Tag)给对象贴业务语义,掩码策略(Masking Policy)按角色动态改写列值,行级策略(Row Access Policy)按会话上下文过滤行。本节以"手机号对运营脱敏、多租户行隔离"两个真实需求为主线,给出从打标到验证的完整 SQL。
接着 7.1 的角色树往下走。假设 core_dw.marts.customer 表里有一列 phone,业务提出两件事:一是运营团队的报表要展示客户手机号,但完整号码不能离开风控团队视野;二是这张表同时存多个租户的数据,任何租户的查询只能看见自己的行。这两件事的共同点:权限已经放行(表都能查),差异发生在"看见什么"——这正是策略层与 RBAC 层的分工边界。
先建角色与标签的底座:
-- 业务语义的载体:标签(可挂在库/表/列上,可用于策略判断与资产盘点) CREATE TAG pii_level ALLOWED_VALUES 'high', 'medium', 'none'; ALTER TABLE core_dw.marts.customer MODIFY COLUMN phone SET TAG pii_level = 'high'; -- 看看全库里哪些列贴了 high:治理盘点的基础查询 SELECT TAG_NAME, TAG_VALUE, OBJECT_DATABASE, OBJECT_SCHEMA, OBJECT_NAME, COLUMN_NAME FROM SNOWFLAKE.ACCOUNT_USAGE.TAG_REFERENCES WHERE TAG_VALUE = 'high';
标签本身不改变任何行为,它是"语义登记簿"——价值在于被策略引用、被资产盘点扫描。合规团队问"哪些列存了高敏数据"时,答案是这条查询,而不是翻文档。
掩码策略是一段"在查询返回前执行的改写函数",它收到列值与当前上下文(角色、会话变量),返回改写后的值:
-- 定义掩码策略:风控角色看原文,其他角色看脱敏值 CREATE OR REPLACE MASKING POLICY mask_phone AS (val STRING) RETURNS STRING -> CASE WHEN CURRENT_ROLE() IN ('RISK_ADMIN') THEN val ELSE CONCAT(LEFT(val, 3), '****', RIGHT(val, 4)) END; -- 挂到列上 ALTER TABLE core_dw.marts.customer MODIFY COLUMN phone SET MASKING POLICY mask_phone; -- 验证:用不同角色各查一次 SELECT phone FROM core_dw.marts.customer LIMIT 1; -- RISK_ADMIN 角色看到:13812345678 -- dw_reader 角色看到:138****5678
三个工程要点。透明生效:业务 SQL 一字不改,改写发生在结果返回前——这是它与"维护两张表"方案的本质区别,不存在"忘了走脱敏视图"的人为漏洞。可组合条件:策略体内可以用 CURRENT_ROLE()、INVOKER_ROLE()、会话变量、映射表做判断,复杂规则(按部门、按区域)用映射表承载。有性能与授权代价:策略是每次查询执行的函数,规则越复杂开销越大;策略引用的映射表要授权给策略所有者,否则查询报错。
进阶用法是标签驱动掩码:把策略挂在标签上而非逐列挂载——所有贴了 pii_level='high' 的列自动继承同一套掩码,新列打上标签即受保护,治理规则从"逐列维护"升级为"按语义批量下发"。
行级策略是定义在表上的过滤函数,输出布尔值决定每行是否可见:
-- 映射表:谁能看哪个租户 CREATE TABLE tenant_access ( user_name STRING, tenant_id STRING ); -- 行级策略:当前用户在映射表中允许的租户行才可见 CREATE OR REPLACE ROW ACCESS POLICY tenant_isolation AS (tid STRING) RETURNS BOOLEAN -> CURRENT_ROLE() = 'RISK_ADMIN' -- 管理角色看全量 OR EXISTS ( SELECT 1 FROM tenant_access t WHERE t.user_name = CURRENT_USER() AND t.tenant_id = tid ); -- 绑定到表 ALTER TABLE core_dw.marts.customer ADD ROW ACCESS POLICY tenant_isolation WITH COLUMN (tenant_id); -- 验证:alice 只映射了租户 T100 SELECT COUNT(*) FROM core_dw.marts.customer; -- alice 看到:只有 T100 的行;RISK_ADMIN 看到:全量
与"每个租户一张视图"的传统方案对比:策略方案是一张物理表 + 一段集中策略,新租户只需在映射表插一行,不用创建任何视图对象;权限审计也收敛到策略与映射表两处。代价是每次查询多一个过滤谓词——好在它参与优化器,与普通 WHERE 条件一样可能吃剪枝红利。
三个工具在同一张表上可以叠加,叠加时的生效顺序值得记住:行级策略先滤行,掩码策略再改列值,RBAC 决定整条查询能否执行。排错时按这个顺序检查,多数"为什么我看到的还是不对"的问题都能在第二层找到答案。
两个边界提醒。⚠️ 掩码策略不是匿名化:138****5678 在统计口径下仍是可关联的标识,把它当"合规脱敏"用于对外数据发布是误用——对外发布请走 5.1 的安全视图加真正的匿名化处理。⚠️ 策略的定义与解绑需要专门权限,把"谁可以改策略"收进 SECURITYADMIN 的变更流程——策略本身也是被治理的对象。
💡 关键直觉:策略层的本质是把安全规则从"对象副本"搬回"对象本体"。过去靠维护多个视图、多张表实现的细粒度控制,现在是一段跟着表走的函数——规则集中,行为透明,审计有据。
安全的三道防线全部布完。最后一章回到贯穿全册的裁判:钱——成本模型与优化实战。