PostgreSQL9.5新特性之行級安全性及其應用級解決方案
PostgreSQL在9.5版本中,新增了 行級安全性策略 特性(RLS),該特性在數據安全體系提供了在傳統的授權安全體系之外更細粒度的控制。對應的,Oracle在很久之前提供了類似的 VPD(Virtual Private Database) 技術,該技術在Oracle10g時代就已經成熟。在SQL Server 2016中,也提供了類似的行級安全特性。如今PostgreSQL在本次發布的大版本中也提供了該新特性,本文將對該技術做詳細地介紹,然后提出對應的應用級解決方案。
1.行級安全性策略
1.1.行級安全策略概述
在之前版本的數據安全技術中,是通過GRANT/REVOKE指令實現的,這兩個指令提供了對象級的安全限制,針對表,還提供了列級的安全限制。但是很多場景中,往往希望不同的用戶訪問同一個表能看到不同的數據,也就是行級安全的需求,這個特性在9.5中提供了支持。該版本中,在正常的SQL查詢和數據更新之外,可以附加額外的行級安全策略,可以限制查詢返回以及數據操作的結果。默認的話,表沒有任何安全策略限制。
所有對數據的操作,包括數據查詢和數據更新,都受到策略的限制,如果沒有配置安全策略,所有的數據查詢和更新都會禁止,但是對全表進行操作的命令,比如 TRUNCATE 和REFERENCES不受影響。
行級安全策略可以加在命令上,也可以加在角色上,也可以兩者都加。命令可以是ALL , SELECT , INSERT , UPDATE 和DELETE,同一個策略也可以賦予多個角色。但是表的所有者,超級用戶(postgres)以及加上了BYPASSRLS屬性的角色不受安全性的限制。如果應用想忽略行級安全性機制的限制,也可以將 row_security 設置為off。
啟用行級安全的表如何對數據的查詢和更新進行控制呢?這是通過一個返回布爾值的表達式實現的。這個表達式優先于查詢條件和查詢內的函數,這個規則唯一的例外是leakproof函數。這里需要兩個表達式,分別對數據的查詢和更新進行獨立地控制。
只有所有者才具有啟用/禁用行級安全性,給表添加策略的權限。
CREATE POLICY , ALTER POLICY , DROP POLICY 命令分別用于策略的創建、修改和刪除, ALTER TABLE 可以用于行級安全性的啟用/禁用。
每個策略都有一個名字,每個表可以定義多個策略,因為策略是針對表的,所以表內的多個策略名字必須唯一,但是不同的表可以有同名的策略,當表有多個策略時,多個策略之間是OR的關系。
1.2.相關示例
啟用行級安全性
要開啟表的行級安全性,需要使用ALTER TABLE命令,如下:
CREATE TABLE user (id text, name text, email text,manager text); ALTER TABLE user ENABLE ROW LEVEL SECURITY;
ALTER TABLE也可以用于禁用行級安全性,但是禁用之后并不刪除相應的策略。
創建策略
如果希望只有管理者才能看到對應的用戶:
CREATE POLICY user_manager ON user TO managers USING (manager = current_user);
如果未指定角色,那么默認為PUBLIC,即針對系統內的所有角色,如果希望系統內每個用戶只能看到自己的數據,只需要創建一個下面的簡單策略即可:
CREATE POLICY user_policy ON users USING (id = current_user);
如果要限制對數據的更新操作,可以添加WITH CHECK語句,下面的策略允許所有人看到所有的數據,但是只能修改自己的數據:
CREATE POLICY user_policy ON user USING (true) WITH CHECK (id = current_user);
關于創建策略的更詳細的說明,可以參照CREATE POLICY命令的 手冊 。
2.應用級解決方案
從上述可以看出,PostgreSQL的行級安全性是針對登錄到數據庫的各個具有不同權限的用戶的,這對于數據庫的設計者來說沒有問題,因為他們工作于數據庫層。而在實際場景中我們是工作于應用層的,我們希望對應用中的同一條SQL語句,能進行不同的權限控制,這里面就涉及三個問題:
- 應用是通過一個共享的賬戶登錄數據庫的。
- 應用的規則可能很復雜。
- 還有一個就是可能需要很多的動態參數。
下面就針對上述三個問題,拿出應用級的解決方案。
2.1.三級賬戶體系
在現實中,應用的開發為了方便,通常的做法是,先用超級用戶(postgres)創建一個登錄用戶角色,然后用新創建的登錄用戶角色登錄,再創建和登錄用戶名同名的數據庫,然后應用也會用這個登錄用戶連接數據庫。
因為行級安全性對于表的所有者以及超級用戶等無效,因此原來的開發模型就不再適用,應用就需要通過單獨的賬戶進行登錄,這樣就形成了三級賬戶體系:
- 超級用戶(postgres):作為數據庫系統的管理者,擁有整個數據庫系統的所有權限;
- 數據庫所有者:該賬戶作為數據庫的管理者,擁有整個數據庫的所有權限;
- 應用所有者:該賬戶默認只具有登錄數據庫的權限,其他的操作都需要相關授權;
應用通過應用所有者賬戶連接數據庫,比如要對某個數據庫的public模式內的所有表進行增刪改查操作,則需要進行如下的授權:
假定登錄用戶為 u1_public :
GRANT ALL ON ALL TABLES IN SCHEMA public TO u1_public;
其他對象的權限授權也同理。
2.2.策略函數
不管是USING表達式還是WITH CHECK表達式,都要求表達式的返回值是布爾值,但是對于表達式本身沒有限制,因此對于一些復雜的場景,是可以寫策略函數的,比如:
ALTER POLICY user_policy ON user USING(p());
這個是合法的,只要p函數的返回值是布爾就可以。
這個p函數內部顯然可以寫復雜的邏輯,但是這個p函數暫時看不能傳遞參數,而且該函數只能返回布爾值也對該函數的發揮空間有了限制,不如Oracle的策略函數返回值是一個字符串的WHERE子句靈活,因為無法用于一些動態場景中。
2.3.動態參數
最后一個問題,就是動態參數,就是具體的策略表達式或者策略函數依賴于應用操作者本身的一些具體的、事務級的參數,比如用戶的id,用戶所屬的組織機構id等,甚至一些用戶在界面上進行選擇或者輸入的數據。這個問題在Oracle中是通過數據庫的上下文對象實現的,而在PostgreSQL中沒有這樣的對象。那么怎么辦呢?
PostgreSQL的強大之處就在這里!這里我們要引入兩個概念,一個是 定制選項 ,一個是系統管理函數中的 配置設定函數 。
1. 定制選項:
任何數據庫,也包括其他的很多復雜軟件,都有很多的配置參數,PostgreSQL也一樣。在PostgreSQL中,有很多的內置參數,定義在postgresql.conf 中。
我們知道,PostgreSQL支持擴展,這些擴展可能也需要一些參數,那么在PostgreSQL中如何定義這些參數呢?他是通過定制選項提供這個功能。
定制選項由兩部分組成,首先是擴展名,然后是一個.,然后是屬性名,比如rls.userid。因為定制選項可能在擴展還沒有加載之前就需要進行設定,因此PostgreSQL允許這些變量以占位符的形式存在直到擴展模塊加載之前都不起任何作用,當擴展模塊加載后才會賦予這些變量實際的含義。
了解了這一點,我們發現可以利用這個特性來進行動態參數的傳遞。
另外要提示一點,在PostgreSQL9.2版本之前,這個定制選項中的擴展名需要在postgres.conf文件中進行定義,比如custom_variable_classes=rls,而在9.2版本中取消了這一限制,這就給我們提供了更大的方便。
2. 配置設定函數:
知道了PostgreSQL支持動態參數而且知道了動態參數的定義規則之后,下一步就需要知道如何對這些參數進行事務級的賦值/取值了,這時我們就需要利用配置設定函數了。
PostgreSQL中對于參數的設定,提供了三種方式,一個是SET命令,一個是對于內置參數的ALTER SYSTEM命令,再一個就是配置設定函數current_setting和set_config,而這兩個函數正是我們需要的,我們看下這兩個函數的定義:
名稱 |
返回值 |
描述 |
current_setting(setting_name) |
text |
獲取設定的當前值 |
set_config(setting_name,new_value,is_local) |
text |
設置參數然后返回新值 |
這里需要特別關注的就是set_config函數的第三個參數is_local,如果該參數為true,那么該參數只在當前事務有效,如果為false,則對當前會話有效。在SET命令中,也有和這個相對應的LOCAL/SESSION參數。
了解了這兩個特性之后,我們就有了對應的應用層解決方案,需要兩個步驟:
- 定義并傳遞參數:
可以在事務開啟之后,進行相應的SQL操作之前進行,比如調用如下的SQL:
SELECT set_config('rls.userid', 'xiaoming', true);
- 策略表達式或者策略函數中獲取參數:
假定對于前述的user表,我們希望應用中登錄的用戶只能查詢/更新自己的數據,那么對應的策略如下:
CREATE POLICY user_policy ON user USING (id =current_setting ('rls.userid')) WITH CHECK (id=current_setting ('rls.userid'));
來自: http://www.infoq.com/cn/articles/postgresql-9-5-new-characteristic-rls