通过为角色分配安全视图并在多个条件下提供过滤器,在 Snowflake 上实现行级安全性 (RLS)

Achieve Row Level Security(RLS) on Snowflake by assigning Secure views to roles and provide filter on multiple conditions

假设我们有 table 个水果详细信息,

Country Fruit
USA Apple
India Mango
Italy Kiwi
Australia Guava

我们有 3 个角色,即
region1_role
region2_role
global_role.
我希望 region1_role 可以访问美国和澳大利亚数据,
region2_role 可以访问印度和意大利数据,
global_role 可以访问所有国家/地区的数据。

我们当然可以使用安全视图来实现这一点,但是仅使用一个安全视图就可以实现吗?

We can of course use Secure views to achieve this but could it be achieved using only one SECURE VIEW?

我不确定我是否正确理解你的问题,但是视图是一个视图,你可以编写任何你想要的逻辑:

CREATE OR REPLACE SECURE VIEW Fruits_v
COPY GRANTS
AS
SELECT *
FROM Fruits
WHERE (current_role(), region) IN ( ('region1_role', 'USA',
                                   ,('region1_role', 'Australia')
                                   ,('region2_role', 'Italy')
                                   ,('region2_role', 'India')
                                  )
  OR current_role() = 'global_role';

悬而未决的问题是关于维护这种方法。添加新角色时,您将需要更新视图定义,而不是向驱动器添加新行 table.


推荐阅读:Understanding Row-level Security和使用策略方法。

让我们从实现区域仅限于一个国家/地区的 RLS 开始。 让这个 table 驻留在里面
数据库:Fruits_DB
架构:Fruits_Schema
Table: Fruits

Country Fruit
USA Apple
India Mango
Italy Kiwi
Australia Guava

假设有 3 个角色 REGION1_ROLEREGION2_ROLEGLOBAL_ROLE
Master_role 是可以访问数据库中所有 Table 等的角色

// Create roles using Master_role
CREATE ROLE REGION1_ROLE;
CREATE ROLE REGION2_ROLE;
CREATE ROLE GLOBAL_ROLE;

// Grant access to Database and Schema to these roles
GRANT USAGE ON DATABASE Fruits_DB TO ROLE REGION1_ROLE;
GRANT USAGE ON SCHEMA Fruits_DB.Fruits_Schema TO ROLE REGION1_ROLE;

GRANT USAGE ON DATABASE Fruits_DB TO ROLE REGION2_ROLE;
GRANT USAGE ON SCHEMA Fruits_DB.Fruits_Schema TO ROLE REGION2_ROLE;

GRANT USAGE ON DATABASE Fruits_DB TO ROLE GLOBAL_ROLE;
GRANT USAGE ON SCHEMA Fruits_DB.Fruits_Schema TO ROLE GLOBAL_ROLE;

GLOBAL_REGION

创建安全视图
//Secure view DDL
CREATE SECURE VIEW GLOBAL_VIEW
AS
SELECT * FROM Fruits_DB.Fruits_Schema.Fruits;

// Grant Access to this View to Global_role
GRANT SELECT ON GLOBAL_VIEW TO ROLE GLOBAL_ROLE;

REGION1_ROLEREGION2_ROLE

创建安全视图
//Secure view DDL
CREATE SECURE VIEW REGIONAL_VIEW
AS
SELECT * FROM Fruits_DB.Fruits_Schema.Fruits
WHERE COUNTRY =
  CASE
    WHEN CURRENT_ROLE() = 'REGION1_ROLE' THEN 'USA'
    WHEN CURRENT_ROLE() = 'REGION2_ROLE' THEN 'INDIA'
  END;
// Note that Function CURRENT_ROLE() evaluates to the role this Function is being executed in

// Grant Access to this View to REGIONAL ROLES
GRANT SELECT ON REGIONAL_VIEW TO ROLE REGION1_ROLE;
GRANT SELECT ON REGIONAL_VIEW TO ROLE REGION2_ROLE;

上述安全视图提供了所需的 RLS,但存在 2 个限制:

  1. 必须创建 2 个视图而不是一个。
  2. 一个角色只能访问一个国家/地区原因CASE WHEN只有return一个值是限制的根本原因。
    我们也不能使用其他条件语句,因为它们也是 return 单个值。
CASE
  WHEN
  THEN
END

解决方案 1: 我们使用 Snowflake 的 JavaScript UDTF 让我们 return 多个国家。

CREATE OR REPLACE FUNCTION Fruits_DB.Fruits_Schema.ROLES_REGIONS(CURRENT_ROLE STRING, COUNTRY STRING)
RETURNS TABLE (COUNTRY VARCHAR)
LANGUAGE JAVASCRIPT
AS
$$
  {
    processRow: function f(row, rowWriter, context)  
    {
        var role = row.CURRENT_ROLE;
        
        if (role == 'GLOBAL_ROLE')
        {
            rowWriter.writeRow( {COUNTRY: row.COUNTRY});
   
        }
        else if (role == 'REGION1_ROLE')
        {
            while(this.count == 0)
            {
                rowWriter.writeRow( {COUNTRY: 'USA'});
                rowWriter.writeRow( {COUNTRY: 'AUSTRALIA'});
                this.count = 1;
            }
            
        }
        else if (role == 'REGION2_ROLE')
        {
            while(this.count == 0)
            {
                rowWriter.writeRow( {COUNTRY: 'INDIA'});
                rowWriter.writeRow( {COUNTRY: 'ITALY'});
                this.count = 1;
            }
            
        }
    },
    
    initialize: function(argumentInfo, context) 
    {
       this.count = 0;
    }
  }
  
$$;

在上面的 JavaScript UDTF 中,我们通过 CURRENT_ROLE()Fruits table 中的 Country 列并应用 If-Else 条件基于角色
函数 return 是一个列,其中包含特定于该角色的所有国家/地区
当 CURRENT_ROLE() 的计算结果为 GLOBAL_ROLE

时,它 return 传递给它的所有国家/地区

新的统一安全视图:

CREATE SECURE VIEW REGIONAL_VIEW
AS
SELECT * FROM Fruits_DB.Fruits_Schema.Fruits
WHERE COUNTRY IN
  ( SELECT DISTINCT COUNTRY 
    FROM 
    Fruits_DB.Fruits_Schema.Fruits,
    TABLE(Fruits_DB.Fruits_Schema.ROLES_REGIONS(CURRENT_ROLE(),COUNTRY::STRING))
  );
// We use DISTINCT to eliminate any duplicate countries if present

解决方案 2: 使用多对多关系 Table
这是一种简单但乏味的方法,因为它不是很容易维护

创建tableRoles_Relations

Role Country
REGION1_ROLE USA
REGION2_ROLE INDIA
REGION2_ROLE ITALY
REGION1_ROLE AUSTRALIA
GLOBAL_ROLE USA
GLOBAL_ROLE INDIA
GLOBAL_ROLE ITALY
GLOBAL_ROLE AUSTRALIA

创建安全视图:

// Secure view DDL
CREATE SECURE VIEW REGIONAL_VIEW
AS
SELECT * FROM Fruits_DB.Fruits_Schema.Fruits
WHERE COUNTRY IN
(
  SELECT COUNTRY FROM Roles_Relations 
  WHERE ROLE = CURRENT_ROLE()
);

当角色数量增加时,这种方法变得非常难以处理
这两种方法都不需要更新视图定义。更新视图的定义会撤销它对其他角色的访问权限,除非您使用 COPY GRANTS,并且更新 UDTF 或 Roles_Relations table 不需要一次又一次地授予角色视图

为什么不使用 UDF 而 entitlement/mapping table 呢?即将到来的行级访问将在 GA 时以同样的方式工作。我看到 Snowflake 的推荐读物。现在UDF可以用在更多的地方。 或者您可以在企业版中使用 Snowflake 的策略作为预览。