Flink SQL 的數據脫敏解決方案

Flink SQL 的數據脫敏解決方案,支持面向用戶級別的數據脫敏訪問控制,即特定用戶只能訪問到脫敏後的數據。此方案是實時領域Flink的解決思路,類似於離線數倉 Hive 中 Ranger Column Masking 方案。

一、基礎知識

1.1 數據脫敏

數據脫敏(Data Masking)是一種數據安全技術,用於保護敏感數據,以防止未經授權的訪問。該技術通過將敏感數據替換爲虛假數據或不可識別的數據來實現。例如可以使用數據脫敏技術將信用卡號碼、社會安全號碼等敏感信息替換爲隨機生成的數字或字母,以保護這些信息的隱私和安全。

1.2 業務流程

下面用訂單表orders的兩行數據來舉例,示例數據如下:

1.2.1 設置脫敏策略

管理員配置用戶、表、字段、脫敏條件,例如下面的配置。

1.2.2 用戶訪問數據

當用戶在Flink上查詢orders表的數據時,會在底層結合該用戶的脫敏條件重新生成 SQL,即讓數據脫敏生效。
當用戶 A 和用戶 B 在執行下面相同的 SQL 時,會看到不同的結果數據。

SELECT * FROM orders

用戶A查看到的結果數據如下customer_name字段的數據被全部掩蓋掉。

用戶 B 查看到的結果數據如下customer_name字段的數據只會顯示前 4 位,剩下的用 x 代替。

二、Hive 數據脫敏解決方案

在離線數倉工具 Hive 領域,由於發展多年已有 Ranger 來支持字段數據的脫敏控制,詳見參考文獻[[1]](https://docs.cloudera.com/HDPDocuments/HDP3/HDP-3.1.0/authorization-ranger/content/dynamic_resource_based_column_masking_in_hive_with_ranger_policies.html)
下圖是在 Ranger 裏配置 Hive 表數據脫敏條件的頁面,供參考。

但由於 Flink 實時數倉領域發展相對較短,Ranger 還不支持 Flink SQL,以及依賴 Ranger 的話會導致系統部署和運維過重,因此開始自研實時數倉的數據脫敏解決工具。當然本文中的核心思想也適用於 Ranger 中,可以基於此較快開發出 ranger-flink 插件。

三、Flink SQL 數據脫敏解決方案

3.1 解決方案

3.1.1 Flink SQL 執行流程

可以參考作者文章 [[FlinkSQL字段血緣解決方案及源碼]](https://github.com/HamaWhiteGG/flink-sql-lineage/blob/main/README_CN.md),本文根據 Flink 1.16 修正和簡化後的執行流程如下圖所示。

CalciteParser.parse()處理後會得到一個 SqlNode 類型的抽象語法樹,本文會針對此抽象語法樹來組裝脫敏條件後來生成新的 AST,以實現數據脫敏控制。

3.1.2 Calcite 對象繼承關係

下面章節要用到 Calcite 中的 SqlNode、SqlCall、SqlIdentifier、SqlJoin、SqlBasicCall 和 SqlSelect 等類,此處進行簡單介紹以及展示它們間繼承關係,以便讀者閱讀本文源碼。

序號 介紹
1 SqlNode A SqlNode is a SQL parse tree.
2 SqlCall A SqlCall is a call to an SqlOperator operator.
3 SqlIdentifier A SqlIdentifier is an identifier, possibly compound.
4 SqlJoin Parse tree node representing a JOIN clause.
5 SqlBasicCall Implementation of SqlCall that keeps its operands in an array.
6 SqlSelect A SqlSelect is a node of a parse tree which represents a select statement, the parent class is SqlCall

3.1.3 解決思路

針對輸入的 Flink SQL,在CalciteParser.parse()進行語法解析後生成抽象語法樹(Abstract Syntax Tree,簡稱 AST)後,採用自定義Calcite SqlBasicVisitor的方法遍歷AST中的所有SqlSelect,獲取到裏面的每個輸入表。如果輸入表中字段有配置脫敏條件,則針對輸入表生成子查詢語句,並把脫敏字段改寫成CAST(脫敏函數(字段名) AS 字段類型) AS 字段名,再通過CalciteParser.parseExpression()把子查詢轉換成 SqlSelect,並用此 SqlSelect 替換原 AST 中的輸入表來生成新的 AST,最後得到新的 SQL 來繼續執行。

3.2 詳細方案

3.2.1 解析輸入表

通過對Flink SQL 語法的分析和研究,最終出現輸入表的只包含以下兩種情況:

  1. SELECT 語句的 FROM 子句,如果是子查詢,則遞歸繼續遍歷。
  2. SELECT ... JOIN 語句的 Left 和 Right 子句,如果是多表 JOIN,則遞歸查詢遍歷。

因此,下面的主要步驟會根據 FROM 子句的類型來尋找輸入表。

3.2.2 主要步驟

主要通過 Calcite 提供的訪問者模式自定義 DataMaskVisitor 來實現,遍歷 AST 中所有的 SqlSelect 對象用子查詢替換裏面的輸入表。

下面詳細描述替換輸入表的步驟,整體流程如下圖所示。

  1. 遍歷 AST 中 SELECT 語句。
  2. 判斷是否自定義的 SELECT 語句(由下面步驟 10 生成),是則跳轉到步驟 11,否則繼續步驟 3。
  3. 判斷 SELECT 語句中的 FROM 類型,按照不同類型對應執行下面的步驟 4、5、6 和 11。
  4. 如果 FROM 是 SqlJoin 類型,則分別遍歷其左 Left 和 Right 右節點,即執行當前步驟 4 和步驟 7。由於可能是三張表及以上的 Join,因此進行遞歸處理,即針對其左 Left 節點跳回到步驟 3。
  5. 如果 FROM 是 SqlIdentifier 類型,則表示是表。但是輸入 SQL 中沒有定義表的別名,則用表名作爲別名。跳轉到步驟 8。
  6. 如果 FROM 是 SqlBasicCall 類型,則表示帶別名。但需要判斷是否來自子查詢,是則跳轉到步驟 11 繼續遍歷AST,後續步驟 1 會對子查詢中的 SELECT 語句進行處理。否則跳轉到步驟 8。
  7. 遞歸處理 Join 的右節點,即跳回到步驟3。
  8. 遍歷表中的每個字段,如果某個字段有定義脫敏條件,則把改字段改寫成格式CAST(脫敏函數(字段名) AS 字段類型) AS 字段名,否則用原字段名。
  9. 針對步驟 8 處理後的字段,構建子查詢語句,形如 (SELECT 字段名1, 字段名2, CAST(脫敏函數(字段名3) AS 字段類型) AS 字段名3、字段名4 FROM 表名) AS 表別名
  10. 對步驟 9 的子查詢調用CalciteParser.parseExpression()進行解析,生成自定義 SELECT 語句,並替換掉原 FROM。
  11. 繼續遍歷 AST,找到裏面的 SELECT 語句進行處理,跳回到步驟 1。

3.2.3 Hive及Ranger兼容性

在 Ranger 中,默認的脫敏策略的如下所示。通過調研發現 Ranger 的大部分脫敏策略是通過調用 Hive 自帶或自定義的系統函數實現的。

序號 策略名 策略說明 Hive系統函數
1 Redact 用x屏蔽字母字符,用n屏蔽數字字符 mask
2 Partial mask: show last 4 僅顯示最後四個字符,其他用x代替 mask_show_last_n
3 Partial mask: show first 4 僅顯示前四個字符,其他用x代替 mask_show_first_n
4 Hash 用值的哈希值替換原值 mask_hash
5 Nullify 用NULL值替換原值 Ranger自身實現
6 Unmasked 原樣顯示 Ranger自身實現
7 Date: show only year 僅顯示日期字符串的年份 mask
8 Custom Hive UDF來自定義策略  

由於 Flink 支持 Hive Catalog,在 Flink 能調用 Hive 系統函數。 因此,本方案也支持在 Flink SQL 配置 Ranger 的脫敏策略。

四、用例測試

用例測試數據來自於 CDC Connectors for Apache Flink[[4]](https://ververica.github.io/flink-cdc-connectors/master/content/%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/mysql-postgres-tutorial-zh.html)官網
本文給orders表增加一個 region 字段,同時增加'connector'='print'類型的 print_sink 表,其字段和orders表的一樣。

下載源碼後,可通過 Maven 運行單元測試。

$ cd flink-sql-security
$ mvn test

詳細測試用例可查看源碼中的單測RewriteDataMaskTestExecuteDataMaskTest,下面只描述兩個案例。

4.1 測試 SELECT

4.1.1 輸入 SQL

用戶 A 執行下述 SQL:

SELECT order_id, customer_name, product_id, region FROM orders

4.1.2 根據脫敏條件重新生成SQL

  1. 輸入 SQL 是一個簡單 SELECT 語句,其 FROM 類型是SqlIdentifier,由於沒有定義別名,用表名orders作爲別名。
  2. 由於用戶A針對字段customer_name定義脫敏條件 MASK(對應函數是脫敏函數是mask),該字段在流程圖中的步驟 8 中被改寫爲CAST(mask(customer_name) AS STRING) AS customer_name,其餘字段未定義脫敏條件則保持不變。
  3. 然後在步驟 9 的操作中,表名orders被改寫成如下子查詢,子查詢兩側用括號()進行包裹,並且用 AS 別名來增加表別名。
(SELECT
     order_id,
     order_date,
     CAST(mask(customer_name) AS STRING) AS customer_name,
     product_id,
     price,
     order_status,
     region
FROM 
    orders
) AS orders

4.1.3 輸出 SQL 和運行結果

最終執行的改寫後SQL如下所示,這樣用戶A查詢到的顧客姓名customer_name字段都是掩蓋後的數據。

SELECT
    order_id,
    customer_name,
    product_id,
    region
FROM (
    SELECT 
         order_id,
         order_date,
         CAST(mask(customer_name) AS STRING) AS customer_name,
         product_id,
         price,
         order_status,
         region
    FROM 
         orders
     ) AS orders

4.2 測試 INSERT-SELECT

4.2.1 輸入 SQL

用戶 A 執行下述 SQL:

INSERT INTO print_sink SELECT * FROM orders

4.2.2 根據脫敏條件重新生成 SQL

通過自定義 Calcite DataMaskVisitor 訪問生成的 AST,能找到對應的 SELECT 語句是SELECT order_id, customer_name, product_id, region FROM orders

針對此 SELECT 語句的改寫邏輯同上,不再闡述。

4.2.3 輸出 SQL 和運行結果

最終執行的改寫後 SQL 如下所示,注意插入到print_sink表的customer_name字段是掩蓋後的數據。

INSERT INTO print_sink (
    SELECT 
        * 
    FROM (
        SELECT 
            order_id, 
            order_date, 
            CAST(mask(customer_name) AS STRING) AS customer_name, 
            product_id, 
            price, 
            order_status, 
            region 
        FROM 
            orders
    ) AS orders
)

五、參考文獻

  1. Apache Ranger Column Masking in Hive
  2. FlinkSQL字段血緣解決方案及源碼
  3. 從SQL語句中解析出源表和結果表
  4. 基於Flink CDC構建MySQL和Postgres的Streaming ETL
  5. HiveQL—數據脫敏函數

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章