云原生數據倉庫AnalyticDB PostgreSQL版新增了動態數據脫敏(Dynamic Data Masking)功能,并提供多種脫敏函數,支持設置列級別的數據脫敏,且支持指定用戶匹配脫敏策略。數據脫敏能夠根據不同的用戶,應用不同的脫敏函數將原始數據以脫敏后的形式展示,從而實現對敏感隱私數據的保護,提高數據的安全性。本文介紹如何使用動態數據脫敏功能。
背景信息
數據脫敏是行之有效的數據庫隱私保護方案之一,可以在一定程度上限制非授權用戶對隱私數據的窺探。動態數據脫敏是一種通過定制化脫敏策略實現對隱私數據保護的技術,可以在不改變原始數據的前提下有效解決非特權用戶對敏感信息訪問的問題。
要使用動態脫敏功能,需要先對每個敏感列定義一個脫敏函數,AnalyticDB PostgreSQL版提供了常見的脫敏函數,可以涵蓋大部分脫敏場景,也支持用戶使用自定義的脫敏函數。將脫敏函數和敏感列進行綁定后,其他用戶查詢的結果就從原始值變成脫敏函數的輸出值。例如,電話號碼在數據庫中存儲的是明文13900001111
。特權用戶(例如,表的Owner、具備RDS_SUPERUSER權限的賬號)查詢后看到的是明文,而其他用戶則會看到脫敏后的結果,示例為139****1111
。
通過CREATE REDACTION POLICY
命令可以為一張表的某些敏感字段指定脫敏函數,同時定義一個表達式,決定何時進行脫敏。脫敏策略被創建后,可以使用ALTER REDACTION POLICY
對其進行修改,或者使用DROP REDACTION POLICY
刪除脫敏策略。您也可以通過系統視圖REDACTION_POLICIES
和REDACTION_COLUMNS
,更加方便地查看脫敏策略及脫敏列信息。
前提條件
小版本為v6.6及以上的AnalyticDB PostgreSQL 6.0版實例和小版本為v7.0.3及以上AnalyticDB PostgreSQL 7.0版實例支持動態數據脫敏。如何查看和升級內核小版本,請參見查看內核小版本和版本升級。
Serverless版本實例暫不支持。
使用約束
只有表的Owner和具備RDS_SUPERUSER權限的賬號具有創建、修改和刪除脫敏策略的權限。
無論定義任何脫敏策略,表的Owner和具備RDS_SUPERUSER權限的賬號總能看到明文數據。
脫敏數據支持通過
COPY TO
或INSERT INTO
導出,但由于脫敏的不可逆性,針對脫敏數據的二次運算無任何實際意義。僅支持在普通表創建動態數據脫敏策略,不支持為系統表、外表、臨時表、UNLOGGED表以及視圖、物化視圖創建脫敏策略。
表和脫敏策略一一對應。一個脫敏策略是表所有脫敏列的集合,可以給一張表的多個列指定不同的脫敏函數,且支持為不同列設置不同的脫敏規則。單個列只能指定一個脫敏函數。
建議只對必要的敏感列進行脫敏,脫敏后的數據參與計算無任何意義。
使用方法
CREATE REDACTION POLICY
該命令定義指定表的脫敏策略。
語法
CREATE REDACTION POLICY <policy_name> ON <table_name>
[ WHEN (<when_expression>) ]
[ ADD COLUMN <column_name> WITH <mask_function_name> ( [ argument [, ...] ] )] [, ... ];
參數說明
參數 | 說明 |
policy_name | 脫敏策略名稱。 |
table_name | 需要脫敏的表名。 |
WHEN (<when_expression>) | WHEN子句指定一個脫敏策略生效的表達式。僅當此表達式為真時,脫敏策略生效。 通常,采用WHEN子句來限定脫敏策略的生效用戶范圍,具有較嚴格的約束,約束如下:
|
column_name | 應用脫敏策略的表的列名。 |
mask_function_name | 脫敏函數名。 脫敏函數決定了如何處理脫敏列,脫敏函數的返回值類型必須和脫敏列類型一致。 |
argument | 脫敏函數的參數列表。 說明 AnalyticDB PostgreSQL版提供豐富的內置脫敏函數,也支持使用SQL、PL/PGSQL語言創建自定義脫敏函數。 |
示例
創建一張簡單的表,并使用自定義函數對其中一列進行脫敏,此脫敏規則只對bob生效。
-- 準備一張表。
CREATE TABLE test_mask(id INT, name TEXT);
-- 創建自定義函數。
CREATE OR REPLACE FUNCTION mask_text(input_text text) RETURNS text AS $$
BEGIN
RETURN REPEAT('*', CHAR_LENGTH(input_text));
END;
$$ LANGUAGE plpgsql IMMUTABLE;
-- 配置脫敏策略。
CREATE REDACTION POLICY test_mask_policy ON test_mask
WHEN (CURRENT_USER = 'bob')
ADD COLUMN name WITH mask_text(name);
也可以使用AnalyticDB PostgreSQL版內置的脫敏函數進行配置,這里WHEN表達式對所有用戶都生效,設置為true
。內置脫敏函數,請參見附錄:脫敏函數。
CREATE REDACTION POLICY test_mask_policy ON test_mask
WHEN (true)
ADD COLUMN name WITH mask_full_str(name);
ALTER REDACTION POLICY
該命令可以修改表的脫敏策略。
語法
修改脫敏策略生效表達式。
ALTER REDACTION POLICY <policy_name> ON <table_name> WHEN (<new_when_expression>);
使脫敏策略生效或失效。
ALTER REDACTION POLICY <policy_name> ON <table_name> ENABLE | DISABLE;
重命名脫敏策略名稱,脫敏策略不變。
ALTER REDACTION POLICY <policy_name> ON <table_name> RENAME TO <new_policy_name>;
修改脫敏列,包括新增、修改、刪除脫敏列。
ALTER REDACTION POLICY policy_name ON table_name Action;
其中,脫敏列操作Action可以是以下子句之一.
ADD COLUMN <column_name> WITH <function_name> ( arguments ) | ALTER COLUMN <column_name> WITH <function_name> ( arguments ) | DROP COLUMN <column_name>
參數說明
參數 | 說明 |
policy_name | 待修改的脫敏策略名稱。 |
table_name | 待修改脫敏策略的表名。 |
new_when_expression | 脫敏策略新的生效表達式。 |
ENABLE | DISABLE | 當前脫敏策略是否生效。
|
new_policy_name | 新的脫敏策略名稱。 |
column_name | 應用脫敏策略的表的列名。
|
function_name | 脫敏函數名。 |
argument | 脫敏函數的參數列表。 |
示例
修改脫敏策略生效表達式,使其對指定角色生效。
ALTER REDACTION POLICY mask_emp ON employees WHEN(current_user in ('alice', 'bob'));
修改脫敏策略生效表達式,使其對所有用戶均生效(只有表的Owner能夠看到明文數據)。
ALTER REDACTION POLICY mask_emp ON employees WHEN(1=1);
修改脫敏策略,使其失效或生效。
ALTER REDACTION POLICY mask_emp ON employees DISABLE; ALTER REDACTION POLICY mask_emp ON employees ENABLE;
重命名脫敏策略名稱。
ALTER REDACTION POLICY mask_emp ON employees RENAME TO new_mask_emp;
新增脫敏列。
ALTER REDACTION POLICY mask_emp_new ON emp ADD COLUMN name WITH mask_none(name);
修改脫敏列的脫敏函數。
ALTER REDACTION POLICY mask_emp_new ON emp ALTER COLUMN name WITH mask_none(name);
刪除已存在的脫敏列。
ALTER REDACTION POLICY mask_emp_new ON emp DROP COLUMN name;
DROP REDACTION POLICY
DROP REDACTION POLICY
命令刪除指定表的脫敏策略。
語法
DROP REDACTION POLICY [ IF EXISTS ] <policy_name> ON <table_name>;
參數
參數 | 說明 |
IF EXISTS | 如果待刪除的脫敏策略不存在,則返回NOTICE,而不是報錯。 |
policy_name | 待刪除的脫敏策略名稱。 |
table_name | 待刪除脫敏策略的表名。 |
脫敏用戶執行SQL
SELECT
動態脫敏對用戶透明,因此無需修改業務邏輯,配置好脫敏策略后,查詢中涉及到的敏感列都會自動進行脫敏。
例如,employees
的phone
列配置了脫敏策略,那么對敏感列的查詢SELECT phone FROM employees;
,如果觸發脫敏條件,則會被自動改寫成類似于SELECT mask_func(phone) FROM employees;
的查詢SQL。
同理,對脫敏列的計算(包括等值條件、JOIN等)也是對脫敏后的結果進行計算,因此不建議對脫敏列進行計算,沒有實際意義。當進行如下查詢時:
CREATE TABLE t1(phone TEXT);
SELECT employees.phone FROM employees JOIN t1 ON t1.phone = employees.phone;
SQL會被改寫成類似于如下的查詢:
-- 脫敏后的等價查詢。
SELECT mask_func(employees.phone) FROM employees JOIN t1 ON t1.phone = mask_func(employees.phone);
因此JOIN的結果會不符合預期,為了使計算正確,可以參見脫敏范圍設置參數。
INSERT/UPDATE/DELETE
通常而言,如果一張表對某用戶脫敏,則該用戶不具有INSERT,UPDATE和DELETE的權限,因為即使寫入了明文數據,查詢結果也是脫敏后的結果。因此,不建議給脫敏用戶賦予除SELECT之外的權限。
INSERT
INSERT插入明文數據,但查詢出來的仍是脫敏后的數據。
-- 假設employees表的phone列對當前用戶脫敏。 INSERT INTO employees (id, name, ssn, phone, birthday, salary, email) VALUES (1, 'Sally Sample', '020-78-9345', '13900001111', '1961-02-02', 51234.34, 'sally.sample@alibaba.com'); -- INSERT之后進行查詢。 postgres=> SELECT * FROM employees; id | name | ssn | phone | birthday | salary | email ----+--------------+-------------+-------------+------------+------------+-------------------------- 1 | Sally Sample | 020-78-9345 | 139****1111 | 1961-02-02 | $51,234.34 | sally.sample@alibaba.com (1 row)
如上所示,對于脫敏用戶即使寫入明文,查詢出來的
phone
列仍然是脫敏后的結果。UPDATE
UPDATE語句通常涉及到計算WHERE條件,但涉及脫敏列的計算會導致計算結果不正確,此時可以設置脫敏范圍,保證計算的正確性。
-- 假設employees表的phone列對當前用戶脫敏。 postgres=> UPDATE employees SET id = 2 WHERE phone = '13900001111'; UPDATE 0 -- 設置脫敏范圍。 postgres=> SET redaction.scope = 'query_except_equal'; SET -- 再次進行UPDATE。 postgres=> UPDATE employees SET id = 2 WHERE phone = '13900001111'; UPDATE 1
DELETE
DELETE和UPDATE類似,通常也涉及計算WHERE條件,因此脫敏用戶如果要執行的DELETE語句涉及脫敏列,也應該設置脫敏范圍。
-- 假設employees表的phone列對當前用戶脫敏。 postgres=> DELETE FROM employees WHERE phone = '13900001111'; DELETE 0 postgres=> SET redaction.scope = 'query_except_equal'; SET postgres=> DELETE FROM employees WHERE phone = '13900001111'; DELETE 1
查看脫敏策略
用戶可以通過系統視圖REDACTION_POLICIES和REDACTION_COLUMNS查看當前數據庫中的脫敏策略及脫敏列。
REDACTION_POLICIES視圖展示當前數據庫內所有脫敏策略。
REDACTION_POLICIES字段信息。
名稱
類型
描述
schema_name
name
脫敏表的模式名。
table_name
name
脫敏表的表名。
policy_name
name
脫敏策略名稱。
enable
boolean
脫敏策略狀態(開啟、關閉)。
expression
text
策略生效表達式。
REDACTION_COLUMNS視圖展示當前數據庫內所有脫敏列信息。
REDACTION_COLUMNS字段信息。
名稱
類型
描述
table_name
name
脫敏表的名稱。
column_name
name
脫敏列的名稱。
column_type
name
脫敏列的類型。
function_info
text
脫敏函數信息。
脫敏范圍
AnalyticDB PostgreSQL版支持兩種脫敏范圍:query
和query_except_equal
,可以通過配置redaction.scope
參數進行選擇。其中query
是默認級別,表示會將查詢語句任意位置中出現過的脫敏列都進行脫敏處理。
例如,查詢語句SELECT name, email, phone FROM employees WHERE email = 'sally.***@alibaba.com';
中email、phone是脫敏列,在使用脫敏功能后,原始的查詢語句會被改寫成等同于SELECT name, masked(email), masked(phone) FROM employees WHERE masked(email) = 'sally.***@alibaba.com';
的查詢。從脫敏用戶的視角看,脫敏列的行為類似于對敏感列進行過一次全改寫UPDATE employees SET email = masked(email);
。因此脫敏后的值不再適合參與計算,為了滿足部分用戶的計算需求,可以將脫敏范圍設置為query_except_equal
,計算中涉及到等值條件均不進行脫敏。設置方式如下:
SET redaction.scope = 'query_except_equal';
SET redaction.scope = 'query_except_equal';
是會話級別設置脫敏范圍。您也可以使用ALTER DATABASE xxx SET redaction.scope = 'query_except_equal';
進行數據庫級別設置脫敏范圍。
在query_except_equal
脫敏范圍下,WHERE子句中的email列將不會進行脫敏,所以經過脫敏改寫后,原始的查詢語句會被改寫成等同于SELECT name, masked(email), masked(phone) FROM employees WHERE email = 'sally.sample@alibaba.com';
的查詢。
使用示例
本文以公司的員工表employees為例,簡要介紹數據脫敏全流程。其中,表的Owner是alice及其他用戶bob和eve,表包含員工姓名、手機號、郵箱、SSN、薪資等隱私數據。
使用管理員用戶連接數據庫后,創建角色alice、bob、eve。
CREATE ROLE alice PASSWORD 'password'; CREATE ROLE bob PASSWORD 'password'; CREATE ROLE eve PASSWORD 'password';
賦予alice、bob和eve當前數據庫的模式權限。
GRANT ALL ON SCHEMA PUBLIC TO alice,bob,eve;
切換至角色alice,創建表employees并插入數據。
SET role alice; CREATE TABLE employees (id SERIAL PRIMARY KEY, name varchar(40) NOT NULL, ssn varchar(11) NOT NULL, phone varchar(11), birthday date, salary money, email varchar(100)); INSERT INTO employees (name, ssn, phone, birthday, salary, email) VALUES ( 'Sally Sample', '020-78-9345', '13900001111', '1961-02-02', 51234.34, 'sally.***@alibaba.com'), ( 'Jane Doe', '123-33-9345', '13900002222', '1963-02-14', 62500.00, 'jane.***@gmail.com'), ( 'Bill Foo', '123-89-9345', '13900003333','1963-02-14', 45350.00, 'william.***@163.com');
alice將表employees的讀取權限授予bob和eve。
GRANT SELECT ON employees to bob,eve;
創建脫敏策略mask_emp,僅alice可查看員工所有信息,bob和eve對員工手機號(phone列)、薪資(salary列)和郵箱(email列)數據不可見。需要創建3個脫敏函數:
phone:字符類型,采用內置脫敏函數mask_partial對中間4位脫敏成
*
。salary:數值類型,采用內置脫敏函數mask_full_num脫敏成0。
email:字符類型,采用內置脫敏函數mask_email對
@
之前的內容進行脫敏。CREATE REDACTION POLICY mask_emp ON employees WHEN (current_user IN ('bob', 'eve')) ADD COLUMN phone WITH mask_partial(phone, '****', 3, 4), ADD COLUMN salary WITH mask_full_num(salary), ADD COLUMN email WITH mask_email(email);
切換到bob和eve,查看員工表employees。
SET ROLE bob; SELECT * FROM employees; id | name | ssn | phone | birthday | salary | email ----+--------------+-------------+-------------+------------+--------+-------------------------- 2 | Jane Doe | 123-33-9345 | 139****1111 | 1963-02-14 | $0.00 | xxxxxxxx@gmail.com 3 | Bill Foo | 123-89-9345 | 139****2222 | 1963-02-14 | $0.00 | xxxxxxxxxxx@163.com 1 | Sally Sample | 020-78-9345 | 139****3333 | 1961-02-02 | $0.00 | xxxxxxxxxxxx@alibaba.com (3 rows) SET ROLE eve; SELECT * FROM employees; id | name | ssn | phone | birthday | salary | email ----+--------------+-------------+-------------+------------+--------+-------------------------- 1 | Sally Sample | 020-78-9345 | 139****1111 | 1961-02-02 | $0.00 | xxxxxxxxxxxx@alibaba.com 2 | Jane Doe | 123-33-9345 | 139****2222 | 1963-02-14 | $0.00 | xxxxxxxx@gmail.com 3 | Bill Foo | 123-89-9345 | 139****3333 | 1963-02-14 | $0.00 | xxxxxxxxxxx@163.com (3 rows)
若需要bob也具有員工所有信息的查看權限,僅eve角色不可見,可以修改策略生效范圍。
SET ROLE alice; ALTER REDACTION POLICY mask_emp ON employees WHEN(current_user = 'bob');
切換到bob和eve,重新查看員工表employees。
SET ROLE bob; SELECT * FROM employees; id | name | ssn | phone | birthday | salary | email ----+--------------+-------------+-------------+------------+------------+-------------------------- 2 | Jane Doe | 123-33-9345 | 13900001111 | 1963-02-14 | $62,500.00 | jane.***@gmail.com 3 | Bill Foo | 123-89-9345 | 13900002222 | 1963-02-14 | $45,350.00 | william.***@163.com 1 | Sally Sample | 020-78-9345 | 13900003333 | 1961-02-02 | $51,234.34 | sally.***@alibaba.com (3 rows) SET ROLE eve; SELECT * FROM employees; id | name | ssn | phone | birthday | salary | email ----+--------------+-------------+-------------+------------+--------+-------------------------- 1 | Sally Sample | 020-78-9345 | 139****1111 | 1961-02-02 | $0.00 | xxxxxxxxxxxx@alibaba.com 2 | Jane Doe | 123-33-9345 | 139****2222 | 1963-02-14 | $0.00 | xxxxxxxx@gmail.com 3 | Bill Foo | 123-89-9345 | 139****3333 | 1963-02-14 | $0.00 | xxxxxxxxxxx@163.com (3 rows)
通過視圖
REDACTION_POLICIES
和REDACTION_COLUMNS
查看當前脫敏策略mask_emp的詳細信息。SELECT * FROM redaction_policies; schema_name | table_name | policy_name | enable | expression -------------+------------+-------------+--------+---------------------------------- public | employees | mask_emp | t | ("current_user"() = 'eve'::name) (1 row) SELECT * FROM redaction_columns; table_name | column_name | column_type | function_info ------------+-------------+-------------+----------------------------------------- employees | phone | varchar | mask_partial(phone, '****'::text, 3, 4) employees | salary | money | mask_full_num(salary) employees | email | varchar | mask_email(email) (4 rows)
將字符類型的ssn列脫敏成
###-##-####
,除了使用正則表達式脫敏函數mask_regexp外,還可以通過自定義函數實現。此處采用SQL語言定義脫敏函數mask_ssn,創建脫敏列時,只需要自定義脫敏的函數名和參數列表。SET ROLE alice; CREATE FUNCTION mask_ssn() RETURNS varchar AS $$ SELECT '###-##-####'::varchar; $$ LANGUAGE SQL; ALTER REDACTION POLICY mask_emp ON employees ADD COLUMN ssn WITH mask_ssn(); SET ROLE eve; SELECT * FROM employees; id | name | ssn | phone | birthday | salary | email ----+--------------+-------------+-------------+------------+--------+-------------------------- 1 | Sally Sample | ###-##-#### | 139****1111 | 2001-01-01 | $0.00 | xxxxxxxxxxxx@alibaba.com 2 | Jane Doe | ###-##-#### | 139****2222 | 2001-01-01 | $0.00 | xxxxxxxx@gmail.com 3 | Bill Foo | ###-##-#### | 139****3333 | 2001-01-01 | $0.00 | xxxxxxxxxxx@163.com (3 rows)
當表內數據不需要脫敏時,可以刪除脫敏策略mask_emp。
SET ROLE alice; DROP REDACTION POLICY mask_emp ON employees;
附錄:脫敏函數
函數 | 描述 | 參數 | 返回值類型 |
mask_none(column anyelement) | 不作任何脫敏處理,僅內部測試用。 | 任意類型的列。 | 與入參column數據類型相同。 |
mask_tonull(column anyelement) | 全脫敏成NULL。 | 任意類型的列。 | NULL |
mask_full_num(column anyelement) | 數值類型全脫敏成0。 | 數值類型的列,支持INT、BIGINT、FLOAT、NUMERIC等。 | 與入參column數據類型相同。 |
mask_full_str(column anyelement) | 字符類型全脫敏成固定字符串 | 定長或變長的字符類型,支持TEXT、VARCHAR、CHAR等。 | 與入參column數據類型相同。 |
mask_full_time(column anyelement) | 時間類型全脫敏成 | TIME或TIME WITH TIME ZONE類型。 | 與入參column數據類型相同。 |
mask_full_timestamp(column anyelement) | 時間戳或日期類型全脫敏成 | DATE、TIMESTAMP或TIMESTAMP WITH TIME ZONE類型。 | 與入參column數據類型相同。 |
mask_full_bytea() | BYTEA類型全脫敏成固定BYTEA。 | 無 | BYTEA類型。 |
mask_email(column anyelement, letter char default 'x') | 脫敏電子郵箱。 |
| 與入參column數據類型相同。 |
mask_shuffle(column anyelement) | 返回原數據的亂序版本,長度和原數據相同。 | 定長或變長的字符類型,支持TEXT、VARCHAR、CHAR等。 | 與入參column數據類型相同。 |
mask_random(column anyelement) | 返回一個長度和原數據相同的隨機字符串。 | 定長或變長的字符類型,支持TEXT、VARCHAR、CHAR等。 | 與入參column數據類型相同。 |
mask_regexp(column anyelement, reg text, replace_text text, pos INTEGER default 0, reg_len INTEGER default -1) | 正則表達式匹配替換。 |
| 與入參column數據類型相同。 |
mask_partial(column anyelement, padding text, prefix INTEGER, suffix INTEGER) | 脫敏替換部分內容。 |
| 與入參column數據類型相同。 |
示例一:使用正則表達式將字符串中的數值類型脫敏成
#
。SELECT mask_regexp('本月營收為27412.45元'::text, '[\d+]', '#'); mask_regexp ---------------------- 本月營收為#####.##元 (1 row)
使用
mask_partial
函數將以下字符串中間的具體數字脫敏成#
。SELECT mask_partial('本月營收為27412.45元'::text, '###.##', 5, 1); mask_partial -------------------- 本月營收為###.##元 (1 row)