本文分享自華為雲社群《GaussDB(DWS)安全管理之資料脫敏原理與使用方法介紹》,作者: VV一笑。
1. 前言
- 適用版本:8.2.0及以上版本
GaussDB (DWS)產品資料脫敏功能,是資料庫產品內化和夯實資料安全能力的重要技術突破。提供指定使用者範圍內列級敏感資料的脫敏功能,具有靈活、高效、透明、友好等優點,極大地增強產品的資料安全能力,實現敏感資料的可靠保護。
大資料時代的到來,顛覆了傳統業態的運作模式,激發出新的生產潛能。資料成為重要的生產要素,是資訊的載體,資料間的流動也潛藏著更高階維度的價值資訊。對於資料控制者和資料處理者而言,如何最大化資料流動的價值,是資料探勘的初衷和意義。然而,一系列資訊洩露事件的曝光,使得資料安全越來越受到廣泛的關注。各國各地區逐步建立健全和完善資料安全與隱私保護相關法律法規,提供使用者隱私保護的法律保障。如何加強技術層面的資料安全和隱私保護,對資料倉儲產品本身提出更多的功能要求,也是資料安全建設最行之有效的辦法。
GaussDB (DWS)產品8.1.1版本釋出資料脫敏特性,提供指定使用者範圍內列級敏感資料的脫敏功能,具有靈活、高效、透明、友好等優點,極大地增強產品的資料安全能力。
2. 資料脫敏概念
資料脫敏(Data Masking),顧名思義,是對於敏感資料進行遮蔽。任何洩露後可能會給社會或個人帶來嚴重危害的資料都屬於常見的敏感資料。個人身份資訊,如姓名、身份證號、住址、手機號、郵箱,企業不適合公開資訊,如營業執照號碼、稅務登記證、員工薪水,裝置資訊如IP地址、MAC地址,銀行卡號、受保護的健康資訊、智慧財產權等都屬於敏感資訊。對這些敏感資訊透過脫敏規則進行資料的變形,實現隱私資料的可靠保護。業界常見的脫敏規則有,替換、重排、加密、截斷、掩碼,使用者也可以根據期望的脫敏演算法自定義脫敏規則。
通常,良好的資料脫敏實施,需要遵循如下兩個原則,第一,儘可能地為脫敏後的應用,保留脫敏前的有意義資訊;第二,最大程度地防止駭客進行破解。
資料脫敏分為靜態資料脫敏和動態資料脫敏。靜態資料脫敏,是資料的“搬移並模擬替換”,是將資料抽取進行脫敏處理後,下發給下游環節,隨意取用和讀寫的,脫敏後資料與生產環境相隔離,滿足業務需求的同時保障生產資料庫的安全。而動態資料脫敏則是在訪問敏感資料的同時實時進行脫敏處理,可以為不同角色、不同許可權、不同資料型別執行不同的脫敏方案,從而確保返回的資料可用而安全。
2.1 DWS動態資料脫敏
GaussDB (DWS)的資料脫敏功能,摒棄業務應用層脫敏依賴性高、代價大等痛點,將資料脫敏內化為資料庫產品自身的安全能力,提供了一套完整、安全、靈活、透明、友好的資料脫敏解決方案,屬於動態資料脫敏。使用者識別敏感欄位後,基於目標欄位,繫結內建脫敏函式,即可建立脫敏策略。脫敏策略(Redaction Policy)與表物件是多對一的關係。一個脫敏策略包含表物件、生效條件、脫敏列-脫敏函式對三個關鍵要素,是該表物件上所有脫敏列的集合,不同欄位可以根據資料特徵採用不同的脫敏函式,根據生效條件、脫敏列-脫敏函式對的不同也可以在同一張表上設定不同的脫敏策略。當且僅當生效條件為真時,查詢語句才會觸發敏感資料的脫敏,而脫敏過程是內建在SQL引擎內部實現的,對生成環境使用者是透明不可見的。
三方代理脫敏工具 vs 數倉DWS脫敏引擎
- 三方代理工具是使用者與數倉叢集的中轉站,是底座之外的外掛脫敏工具,無法直接參與生成環境,複雜場景較難處理
- DWS則是基於數倉底座與儲存引擎、SQL引擎直接互動的脫敏引擎,在查詢執行過程中實時脫敏,脫敏結果直接返回給使用者
- 代理脫敏工具是靜態脫敏,DWS脫敏引擎是動態脫敏
DWS動態脫敏引擎的優勢
- 良好的底座協同。脫敏引擎貫穿於數倉底座的諸多環節,基於預置脫敏策略,參與SQL引擎的解析、重寫、最佳化與執行。脫敏過程使用者無感知。
- 策略可配置。客戶可結合自身業務場景識別敏感資料並對業務表的指定列靈活預置脫敏策略。
- 策略可擴充套件。產品內建脫敏函式,可以涵蓋大部分常見脫敏效果,支援使用者自定義脫敏函式。
- 資料可用性。資料庫內原始敏感資料參與運算,僅在出庫時刻(返回結果時)才會做脫敏處理。
-
資料訪問受控。脫敏策略生效條件的使用者均對原始敏感資料不可見。
-
全場景資料不洩露。底座互動,可減少敏感資料傳輸鏈路潛在的洩露風險,
更加安全可靠,且充分識別各種惡意套取潛在場景並有效防護。
3. 資料脫敏使用方法
動態資料脫敏,是在查詢語句執行過程中,根據生效條件是否滿足,實現實時的脫敏處理。生效條件,通常是針對當前使用者角色的判斷。敏感資料的可見範圍,即是針對不同使用者預設的。系統管理員,具有最高許可權,任何時刻對任何表的任何欄位都可見。確定受限制使用者角色,是建立脫敏策略的第一步。
敏感資訊依賴於實際業務場景和安全維度,以自然人為例,使用者個體的敏感欄位包括:姓名、身份證號、手機號、郵箱地址等等;在銀行系統,作為客戶,可能還涉及銀行卡號、過期時間、支付密碼等等;在公司系統,作為員工,可能還涉及薪資、教育背景等;在醫療系統,作為患者,可能還涉及就診資訊等等。所以,識別和梳理具體業務場景的敏感欄位,是建立脫敏策略的第二步。
產品內建一系列常見的脫敏函式介面,可以針對不同資料型別和資料特徵,指定引數,從而達到不一樣的脫敏效果。脫敏函式可採用如下三種內建介面,同時支援自定義脫敏函式。三種內建脫敏函式能夠涵蓋大部分場景的脫敏效果,不推薦使用自定義脫敏函式。
-
MASK_NONE:不作脫敏處理,僅內部測試用。
-
MASK_FULL:全脫敏成固定值。
-
MASK_PARTIAL:使用指定的脫敏字元對脫敏範圍內的內容做部分脫敏
不同脫敏列可以採用不同的脫敏函式。比如,手機號通常顯示後四位尾號,前面用"*"替換;金額統一顯示為固定值0,等等。確定脫敏列需要繫結的脫敏函式,是建立脫敏策略的第三步。
以某公司員工表emp,表的屬主使用者alice以及使用者matu、july為例,簡單介紹資料脫敏的使用過程。其中,表emp包含員工的姓名、手機號、郵箱、發薪卡號、薪資等隱私資料,使用者alice是人力資源經理,使用者matu和july是普通職員。
假設表、使用者及使用者對錶emp的檢視許可權均已就緒。
1.建立脫敏策略mask_emp,僅允許alice檢視員工所有資訊,matu和july對發薪卡號、薪資均不可見。欄位card_no是數值型別,採用MASK_FULL全脫敏成固定值0;欄位card_string是字元型別,採用MASK_PARTIAL按指定的輸入輸出格式對原始資料作部分脫敏;欄位salary是數值型別,採用數字9部分脫敏倒數第二位前的所有數位值。
postgres=# CREATE REDACTION POLICY mask_emp ON emp WHEN (current_user != 'alice') ADD COLUMN card_no WITH mask_full(card_no), ADD COLUMN card_string WITH mask_partial(card_string, 'VVVVFVVVVFVVVVFVVVV','VVVV-VVVV-VVVV-VVVV','#',1,12), ADD COLUMN salary WITH mask_partial(salary, '9', 1, length(salary) - 2);
切換到matu和july,檢視員工表emp。
postgres=> SET ROLE matu PASSWORD 'Gauss@123'; postgres=> SELECT * FROM emp; id | name | phone_no | card_no | card_string | email | salary | birthday ----+------+-------------+---------+---------------------+----------------------+------------+--------------------- 1 | anny | 13420002340 | 0 | ####-####-####-1234 | smithWu@163.com | 99999.9990 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 0 | ####-####-####-3456 | 66allen_mm@qq.com | 9999.9990 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 1992-11-06 00:00:00 (3 rows) postgres=> SET ROLE july PASSWORD 'Gauss@123'; postgres=> SELECT * FROM emp; id | name | phone_no | card_no | card_string | email | salary | birthday ----+------+-------------+---------+---------------------+----------------------+------------+--------------------- 1 | anny | 13420002340 | 0 | ####-####-####-1234 | smithWu@163.com | 99999.9990 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 0 | ####-####-####-3456 | 66allen_mm@qq.com | 9999.9990 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 1992-11-06 00:00:00 (3 rows)
2.由於工作調整,matu進入人力資源部參與公司招聘事宜,也對員工所有資訊可見,修改策略生效條件。
postgres=> ALTER REDACTION POLICY mask_emp ON emp WHEN(current_user NOT IN ('alice', 'matu'));
切換到使用者matu和july,重新檢視員工表emp。
postgres=> SET ROLE matu PASSWORD 'Gauss@123'; postgres=> SELECT * FROM emp; id | name | phone_no | card_no | card_string | email | salary | birthday ----+------+-------------+------------------+---------------------+----------------------+------------+--------------------- 1 | anny | 13420002340 | 1234123412341234 | 1234-1234-1234-1234 | smithWu@163.com | 10000.0000 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 3456345634563456 | 3456-3456-3456-3456 | 66allen_mm@qq.com | 9999.9900 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 1992-11-06 00:00:00 (3 rows) postgres=> SET ROLE july PASSWORD 'Gauss@123'; postgres=> SELECT * FROM emp; id | name | phone_no | card_no | card_string | email | salary | birthday ----+------+-------------+---------+---------------------+----------------------+------------+--------------------- 1 | anny | 13420002340 | 0 | ####-####-####-1234 | smithWu@163.com | 99999.9990 | 1999-10-02 00:00:00 2 | bob | 18299023211 | 0 | ####-####-####-3456 | 66allen_mm@qq.com | 9999.9990 | 1989-12-12 00:00:00 3 | cici | 15512231233 | | | jonesishere@sina.com | | 1992-11-06 00:00:00 (3 rows)
3.員工資訊phone_no、email和birthday也是隱私資料,更新脫敏策略mask_emp,新增三個脫敏列。
postgres=> ALTER REDACTION POLICY mask_emp ON emp ADD COLUMN phone_no WITH mask_partial(phone_no, '*', 4); postgres=> ALTER REDACTION POLICY mask_emp ON emp ADD COLUMN email WITH mask_partial(email, '*', 1, position('@' in email)); postgres=> ALTER REDACTION POLICY mask_emp ON emp ADD COLUMN birthday WITH mask_full(birthday);
切換到使用者july,檢視員工表emp。
postgres=> SET ROLE july PASSWORD 'Gauss@123'; postgres=> SELECT * FROM emp; id | name | phone_no | card_no | card_string | email | salary | birthday ----+------+-------------+---------+---------------------+----------------------+------------+--------------------- 1 | anny | 134******** | 0 | ####-####-####-1234 | ********163.com | 99999.9990 | 1970-01-01 00:00:00 2 | bob | 182******** | 0 | ####-####-####-3456 | ***********qq.com | 9999.9990 | 1970-01-01 00:00:00 3 | cici | 155******** | | | ************sina.com | | 1970-01-01 00:00:00 (3 rows)
4.考慮使用者互動的友好性,GaussDB (DWS) 提供系統檢視redaction_policies和redaction_columns,方便使用者直接檢視更多脫敏資訊。
postgres=> SELECT * FROM redaction_policies; object_schema | object_owner | object_name | policy_name | expression | enable | policy_description ---------------+--------------+-------------+-------------+-----------------------------------+--------+-------------------- public | alice | emp | mask_emp | ("current_user"() = 'july'::name) | t | (1 row) postgres=> SELECT object_name, column_name, function_info FROM redaction_columns; object_name | column_name | function_info -------------+-------------+------------------------------------------------------------------------------------------------------- emp | card_no | mask_full(card_no) emp | card_string | mask_partial(card_string, 'VVVVFVVVVFVVVVFVVVV'::text, 'VVVV-VVVV-VVVV-VVVV'::text, '#'::text, 1, 12) emp | email | mask_partial(email, '*'::text, 1, "position"(email, '@'::text)) emp | salary | mask_partial(salary, '9'::text, 1, (length((salary)::text) - 2)) emp | birthday | mask_full(birthday) emp | phone_no | mask_partial(phone_no, '*'::text, 4) (6 rows)
5.突然某一天,公司內部可共享員工資訊時,直接刪除表emp的脫敏策略mask_emp即可。
postgres=> DROP REDACTION POLICY mask_emp ON emp;
更多用法詳情,請參考GaussDB (DWS)產品文件。
4. 可算不可見的資料脫敏
在使用資料脫敏功能時,存在先對敏感資料進行加工計算在進行輸出的情況。對於這種情況,如果採用經過脫敏後的資料進行庫內計算,那麼在例如聚集函式、過濾條件等條件下經過脫敏的資料本身會對查詢結果產生影響,因此針對這一現象對資料脫敏引入了可算不可見功能。所謂可算不可見,就是在資料庫內使用原始的敏感資料參與加工計算,只在出庫時對敏感資料進行脫敏處理。想要使用可算不可見功能,需要設定開關enable_redactcol_computable=on。
目前支援將敏感資料直接參與加工計算的場景如下:
-
SELECT nullif(salary, 1) FROM emp;投影列表示式nullif
-
SELECT email LIKE ‘%.com’ FROM emp;投影列LIKE表示式
-
SELECT to_days(birth) FROM david.emp;投影列函式to_days
-
SELECT count(*) FROM emp;聚集函式
-
SELECT * FROM emp WHERE cardid IS NOT NULL; 過濾條件
-
SELECT name, avg(salary) * 12 FROM emp GROUP BY name; 分組條件(name是脫敏欄位)
-
SELECT (SELECT salary+10 FROM emp ORDER BY id LIMIT 1);子查詢位置投影列表示式
-
兩表使用敏感欄位作關聯條件
-
CTE表示式投影列
出庫時刻會觸發資料脫敏的場景如下:
-
表查詢
-
檢視查詢
-
DML RETURNING子句
-
COPY匯出
-
GDS外表匯出
-
遊標CURSOR… FETCH
-
儲存過程定義使用脫敏表,查詢儲存過程
4.1 脫敏策略的繼承
對於INSERT/UPDATE/MERGE INTO/CREATE TABLE AS語句,當子查詢是對某個敏感欄位的投影操作時,就會觸發脫敏繼承,從而使得包含了敏感資訊的新表上含有與源表相同的脫敏策略,避免了透過在新表中插入源表敏感資料,再查詢新表導致敏感資料洩露的問題。此外,脫敏策略的繼承屬於表維度的活動,繼承活動不關注子查詢部分當前會話或角色條件下脫敏策略是否生效。
繼承脫敏策略的第一步是進行敏感血緣分析,對於任何使用者執行DML語句,都遍歷子查詢部分源表及其目標投影列,一旦源表存在脫敏策略且目標投影列是脫敏欄位,則認為使用源表插入/更新目標表資料時,存在暴露源表敏感資料的風險。
對脫敏策略進行繼承時,首先從從遍歷標記的源表敏感資訊中,生成作用於目標表的脫敏策略資訊及脫敏欄位資訊。隨後系統內建生成策略建立語句並執行寫入系統表pg_redaction_policy/pg_redaction_column,對於內建建立的脫敏策略,統一命名為“inherited_rp”。最後再將系統表後設資料inherit標記欄位為true。
注意,如果INSERT執行會話/使用者滿足觸發條件,當帶有RETURNING子句列印插入結果時,返回結果會脫敏,日誌資訊“Parent redaction policies/columns”會記錄策略繼承的來源。
隨著脫敏策略繼承行為的引入,產生了一些脫敏策略衝突的場景。例如SELECT語句查詢目標列非原始敏感欄位,而是敏感欄位的複雜表示式,表示式先算後脫敏,此時如何界定脫敏行為?SETOP集合運算兩個子分支的對應同一目標列採用不同的脫敏效果,此時如何界定外層語句目標列的脫敏結果?多次INSERT/UPDATE操作的敏感血緣分析中,同一目標列的源表投影列採用的不一樣的脫敏效果,且源表策略的生效條件也可能不同,此時脫敏策略該如何繼承?
針對這些衝突場景,基於保護使用者任何敏感資料不致洩露優先於敏感資料脫敏效果不具有原始特徵的原則,當遇到脫敏效果衝突時,都提升為通用脫敏效果mask_full。mask_full是可覆蓋任何資料型別的全脫敏函式,只關注表示式返回值型別,可以保證脫敏資料不會洩露,但是會導致脫敏結果無法表徵原始資料特徵,使得脫敏結果的可讀性較差。此外,針對length、count等函式表示式,其計算結果不會暴露任何原始資料特徵及資訊,所以提供了ALTER FUNCTION … [NOT] MASKED語法,支援使用者手動配置不脫敏函式白名單。
4.2 防護惡意套取
已知某些敏感資訊,透過多次試探性匹配,反向佐證可見的使用者資訊,從而竊取使用者的隱私資料。藉助助等值判斷形式表示式的過濾條件或投影操作試探性匹配敏感資訊。這些透過已知常量值和等值/類等值判斷表示式來進行套取使用者隱私資料的行為成為惡意套取。
postgres=> SELECT name FROM emp WHERE name in('張三'); INFO: The result of target column {"name"} is masked. name ------ 張* (1 row)
如上述例子所示,儘管已經對使用者的name資訊進行了脫敏,但由於查詢條件是針對’張三’使用者,因此即使被脫敏為’張*’,我們還是可以很容易確定這裡的脫敏前資訊是‘張三’,從而導致張三使用者的資訊存在洩漏的風險。
針對這一問題,我們採用了“悲觀主義”模式,任何常量等值判斷都可能存在惡意套取的風險,都應當禁止,例項如下:
postgres=> SELECT name FROM emp WHERE name in('張三'); ERROR: Redaction column "name" cannot be referenced in equivalence conditions with const value. HINT: Please use EXPLAIN command to see more details.
禁止使用常量惡意套取的場景總結如下:
1.脫敏欄位的常量等值判斷表示式、複合表示式、等價表示式
2.假設name欄位是脫敏欄位且當前會話滿足策略觸發條件,則語句有如下(且不限於)特徵,存在惡意套取風險,禁止執行:
• name = '張三‘
• name = ‘張三’ OR name = ‘李四’
• name in (‘張三’, ‘李四’)
• CASE name WHEN ‘張三’ THEN true …
• CASE WHEN name in (‘張三’, ‘李四’) THEN …
• 高階包dbms_output.put_line
3.語句執行會報錯:ERROR: Redaction column “name” cannot be referenced in equivalence conditions with const value.
5. 資料脫敏實現原理
GaussDB (DWS)資料脫敏功能,基於SQL引擎既有的實現框架,在受限使用者執行查詢語句過程中,實現外部不感知的實時脫敏處理。關於其內部實現,如上圖所示。我們將脫敏策略(Redaction Policy)視為表物件上繫結的規則,在最佳化器查詢重寫階段,遍歷Query Tree中TargetList的每個TargetEntry,如若涉及基表的某個脫敏列,且當前脫敏規則生效(即滿足脫敏策略的生效條件且enable開啟狀態),則斷定此TargetEntry中涉及要脫敏的Var物件,此時,遍歷脫敏列系統表pg_redaction_column,查詢到對應脫敏列繫結的脫敏函式,將其替換成對應的FuncExpr即可。經過上述對Query Tree的重寫處理,最佳化器會自動生成新的執行計劃,執行器遵照新的計劃執行,查詢結果將對敏感資料做脫敏處理。
帶有資料脫敏的語句執行,相較於原始語句,增加了資料脫敏的邏輯處理,勢必會給查詢帶來額外的開銷。這部分開銷,主要受表的資料規模、查詢目標列涉及的脫敏列數、脫敏列採用的脫敏函式三方面因素影響。
針對簡單查詢語句,以tpch表customer為例,針對上述因素展開測試,如下圖所示。
圖(a)、(b)中基表customer根據欄位型別和特徵,既有采用MASK_FULL脫敏函式的,也有采用MASK_PARTIAL脫敏函式的。MASK_FULL對於任何長度和型別的原始資料,均只脫敏成固定值,所以,輸出結果相較於原始資料,差異很大。圖(a)顯示不同資料規模下,脫敏和非脫敏場景簡單查詢語句的執行耗時。實心圖示為非脫敏場景,空心圖示為被限制使用者,即脫敏場景。可見,資料規模越大,帶有脫敏的查詢耗時與原始語句差異越大。圖(b)顯示10x資料規模下查詢涉及脫敏列數不同對於語句執行效能的影響。涉及1列脫敏列時,帶有脫敏的查詢比原始語句慢,追溯發現,此列採用的是MASK_PARTIAL部分脫敏函式,查詢結果只是改變了結果的格式,結果內容的長度並未變化,符合“帶有脫敏的語句執行會有相應的效能劣化”的理論猜想。隨著查詢涉及脫敏列數的增加,我們發現一個奇怪的現象,脫敏場景反倒比原始語句執行更快。進一步追溯多列場景下脫敏列關聯的脫敏函式,發現,正是因為存在使用MASK_FULL全脫敏函式的脫敏列,導致輸出結果集部分相比原始資料節省很多時間開銷,從而多列查詢下帶有資料脫敏的簡單查詢反倒提速不少。
為了佐證上述猜測,我們調整脫敏函式,所有脫敏列均採用MASK_PARTIAL對原始資料做部分脫敏,從而能夠在脫敏結果上保留原始資料的外部可讀性。於是,如圖©所示,當脫敏列均關聯部分脫敏函式時,帶有資料脫敏的語句比原始語句劣化10%左右,理論上講,這種劣化是在可接受範圍的。上述測試僅針對簡單的查詢語句,當語句複雜到帶有聚集函式或複雜表示式運算時,可能這種效能劣化會更明顯。
6. 總結
GaussDB (DWS)產品資料脫敏功能,是資料庫產品內化和夯實資料安全能力的重要技術突破,主要涵蓋以下三個方面:
一套簡單、易用的資料脫敏策略語法;
一系列可覆蓋常見隱私資料脫敏效果的、靈活配置的內建脫敏函式;
一個完備、便捷的脫敏策略應用方案,使得原始語句在執行過程中可以實時、透明、高效地實現脫敏。
總而言之,此資料脫敏功能可以充分滿足客戶業務場景的資料脫敏訴求,支援常見隱私資料的脫敏效果,實現敏感資料的可靠保護。
【溫馨提示】使用過程中,如有疑問,歡迎隨時交流反饋。
點選關注,第一時間瞭解華為雲新鮮技術~