除了 SQL 標準的 權限系統 (可透過 GRANT 取得),表格可以有資料列安全策略,其會根據使用者限制哪些資料列可以由一般查詢傳回,或由資料修改指令插入、更新或刪除。此功能也稱為資料列層級安全性。預設情況下,表格沒有任何策略,因此如果使用者根據 SQL 權限系統具有表格的存取權限,則其中的所有資料列都可以同樣地用於查詢或更新。
當在表格上啟用資料列安全性(使用 ALTER TABLE ... ENABLE ROW LEVEL SECURITY)時,必須由資料列安全策略允許對表格的所有正常存取,才能選取或修改資料列。(但是,表格的擁有者通常不受資料列安全策略的約束。)如果表格沒有策略,則會使用預設拒絕策略,這表示沒有資料列可見或可修改。適用於整個表格的操作,例如 TRUNCATE
和 REFERENCES
,不受資料列安全性的約束。
資料列安全策略可以特定於指令、角色或兩者。可以指定策略以套用至 ALL
指令,或套用至 SELECT
、INSERT
、UPDATE
或 DELETE
。可以將多個角色指派給給定的策略,並且適用一般的角色成員資格和繼承規則。
若要指定根據策略哪些資料列可見或可修改,則需要一個傳回布林結果的運算式。在任何來自使用者查詢的條件或函數之前,將針對每個資料列評估此運算式。(此規則的唯一例外是 leakproof
函數,保證不會洩漏資訊;最佳化器可能會選擇在資料列安全性檢查之前套用此類函數。)運算式未傳回 true
的資料列將不會被處理。可以指定單獨的運算式,以提供對可見資料列和允許修改的資料列的獨立控制。策略運算式作為查詢的一部分執行,並具有執行查詢的使用者的權限,儘管可以使用安全性定義器函數來存取呼叫使用者無法使用的資料。
具有 BYPASSRLS
屬性的超級使用者和角色在存取表格時始終會略過資料列安全系統。表格擁有者通常也會略過資料列安全性,但是表格擁有者可以選擇使用 ALTER TABLE ... FORCE ROW LEVEL SECURITY 來受資料列安全性約束。
啟用和停用資料列安全性以及將策略新增至表格,始終只是表格擁有者的權限。
使用 CREATE POLICY 指令建立策略,使用 ALTER POLICY 指令修改策略,以及使用 DROP POLICY 指令捨棄策略。若要啟用和停用給定表格的資料列安全性,請使用 ALTER TABLE 指令。
每個策略都有一個名稱,並且可以為一個表格定義多個策略。由於策略是特定於表格的,因此表格的每個策略都必須具有唯一的名稱。不同的表格可能具有相同名稱的策略。
當多個策略適用於給定查詢時,會使用 OR
(對於允許性策略,這是預設值)或使用 AND
(對於限制性策略)來組合它們。這類似於給定角色具有其所屬所有角色的權限的規則。允許性與限制性策略將在下面進一步討論。
作為一個簡單的範例,以下說明如何在 account
關係上建立策略,以僅允許 managers
角色的成員存取資料列,並且僅存取其帳戶的資料列
CREATE TABLE accounts (manager text, company text, contact_email text); ALTER TABLE accounts ENABLE ROW LEVEL SECURITY; CREATE POLICY account_managers ON accounts TO managers USING (manager = current_user);
上面的策略隱含地提供了一個與其 USING
子句相同的 WITH CHECK
子句,因此該約束既適用於指令選取的資料列(因此管理者無法 SELECT
、UPDATE
或 DELETE
屬於不同管理者的現有資料列),也適用於指令修改的資料列(因此無法透過 INSERT
或 UPDATE
建立屬於不同管理者的資料列)。
如果未指定角色,或者使用了特殊的使用者名稱 PUBLIC
,則該策略適用於系統上的所有使用者。若要允許所有使用者僅存取 users
表格中自己的資料列,可以使用一個簡單的策略
CREATE POLICY user_policy ON users USING (user_name = current_user);
這與上一個範例類似。
若要對新增到表格的資料列使用與可見資料列不同的策略,則可以組合多個策略。這對策略將允許所有使用者檢視 users
表格中的所有資料列,但僅修改自己的資料列
CREATE POLICY user_sel_policy ON users FOR SELECT USING (true); CREATE POLICY user_mod_policy ON users USING (user_name = current_user);
在 SELECT
指令中,這兩個策略會使用 OR
進行組合,最終的效果是所有列都可以被選取。在其他指令類型中,只會套用第二個策略,因此效果與之前相同。
也可以使用 ALTER TABLE
指令來停用列安全性。停用列安全性並不會移除在資料表上定義的任何策略;它們只是會被忽略。然後,資料表中的所有列都是可見和可修改的,但仍受限於標準 SQL 權限系統。
以下是一個更大的範例,說明如何在生產環境中使用此功能。資料表 passwd
模擬 Unix 密碼檔案。
-- Simple passwd-file based example CREATE TABLE passwd ( user_name text UNIQUE NOT NULL, pwhash text, uid int PRIMARY KEY, gid int NOT NULL, real_name text NOT NULL, home_phone text, extra_info text, home_dir text NOT NULL, shell text NOT NULL ); CREATE ROLE admin; -- Administrator CREATE ROLE bob; -- Normal user CREATE ROLE alice; -- Normal user -- Populate the table INSERT INTO passwd VALUES ('admin','xxx',0,0,'Admin','111-222-3333',null,'/root','/bin/dash'); INSERT INTO passwd VALUES ('bob','xxx',1,1,'Bob','123-456-7890',null,'/home/bob','/bin/zsh'); INSERT INTO passwd VALUES ('alice','xxx',2,1,'Alice','098-765-4321',null,'/home/alice','/bin/zsh'); -- Be sure to enable row-level security on the table ALTER TABLE passwd ENABLE ROW LEVEL SECURITY; -- Create policies -- Administrator can see all rows and add any rows CREATE POLICY admin_all ON passwd TO admin USING (true) WITH CHECK (true); -- Normal users can view all rows CREATE POLICY all_view ON passwd FOR SELECT USING (true); -- Normal users can update their own records, but -- limit which shells a normal user is allowed to set CREATE POLICY user_mod ON passwd FOR UPDATE USING (current_user = user_name) WITH CHECK ( current_user = user_name AND shell IN ('/bin/bash','/bin/sh','/bin/dash','/bin/zsh','/bin/tcsh') ); -- Allow admin all normal rights GRANT SELECT, INSERT, UPDATE, DELETE ON passwd TO admin; -- Users only get select access on public columns GRANT SELECT (user_name, uid, gid, real_name, home_phone, extra_info, home_dir, shell) ON passwd TO public; -- Allow users to update certain columns GRANT UPDATE (pwhash, real_name, home_phone, extra_info, shell) ON passwd TO public;
與任何安全性設定一樣,測試並確保系統按預期運作非常重要。使用上面的範例,可以證明權限系統運作正常。
-- admin can view all rows and fields postgres=> set role admin; SET postgres=> table passwd; user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | shell -----------+--------+-----+-----+-----------+--------------+------------+-------------+----------- admin | xxx | 0 | 0 | Admin | 111-222-3333 | | /root | /bin/dash bob | xxx | 1 | 1 | Bob | 123-456-7890 | | /home/bob | /bin/zsh alice | xxx | 2 | 1 | Alice | 098-765-4321 | | /home/alice | /bin/zsh (3 rows) -- Test what Alice is able to do postgres=> set role alice; SET postgres=> table passwd; ERROR: permission denied for table passwd postgres=> select user_name,real_name,home_phone,extra_info,home_dir,shell from passwd; user_name | real_name | home_phone | extra_info | home_dir | shell -----------+-----------+--------------+------------+-------------+----------- admin | Admin | 111-222-3333 | | /root | /bin/dash bob | Bob | 123-456-7890 | | /home/bob | /bin/zsh alice | Alice | 098-765-4321 | | /home/alice | /bin/zsh (3 rows) postgres=> update passwd set user_name = 'joe'; ERROR: permission denied for table passwd -- Alice is allowed to change her own real_name, but no others postgres=> update passwd set real_name = 'Alice Doe'; UPDATE 1 postgres=> update passwd set real_name = 'John Doe' where user_name = 'admin'; UPDATE 0 postgres=> update passwd set shell = '/bin/xx'; ERROR: new row violates WITH CHECK OPTION for "passwd" postgres=> delete from passwd; ERROR: permission denied for table passwd postgres=> insert into passwd (user_name) values ('xxx'); ERROR: permission denied for table passwd -- Alice can change her own password; RLS silently prevents updating other rows postgres=> update passwd set pwhash = 'abc'; UPDATE 1
到目前為止,所有建構的策略都是寬鬆策略,這表示當套用多個策略時,它們會使用 「OR」 布林運算子進行組合。雖然可以建構寬鬆策略,使其僅允許存取預期情況下的列,但將寬鬆策略與限制性策略(記錄必須通過,並使用 「AND」 布林運算子進行組合)組合起來可能更簡單。以上面的範例為基礎,我們添加一個限制性策略,要求管理員必須通過本機 Unix Socket 連線才能存取 passwd
資料表的記錄。
CREATE POLICY admin_local_only ON passwd AS RESTRICTIVE TO admin USING (pg_catalog.inet_client_addr() IS NULL);
然後,我們可以發現,由於限制性策略,透過網路連線的管理員將不會看到任何記錄。
=> SELECT current_user; current_user -------------- admin (1 row) => select inet_client_addr(); inet_client_addr ------------------ 127.0.0.1 (1 row) => TABLE passwd; user_name | pwhash | uid | gid | real_name | home_phone | extra_info | home_dir | shell -----------+--------+-----+-----+-----------+------------+------------+----------+------- (0 rows) => UPDATE passwd set pwhash = NULL; UPDATE 0
參照完整性檢查,例如唯一性或主鍵約束和外鍵參照,總是會繞過列安全性,以確保資料完整性得到維護。在開發結構描述和列層級策略時,必須謹慎,以避免透過此類參照完整性檢查洩漏 「隱蔽通道」 的資訊。
在某些情況下,務必確定列安全性未被套用。例如,在進行備份時,如果列安全性靜默地導致某些列從備份中省略,那將是災難性的。在這種情況下,您可以將 row_security 組態參數設定為 off
。這本身並不會繞過列安全性;它的作用是,如果任何查詢的結果會被策略過濾,則會拋出錯誤。然後可以調查並修復錯誤的原因。
在上面的範例中,策略運算式僅考慮要存取或更新的列中的目前值。這是最簡單且效能最佳的情況;如果可能,最好設計以這種方式運作的列安全性應用程式。如果需要查詢其他列或其他資料表才能做出策略決策,可以使用子 SELECT
,或包含 SELECT
的函數在策略運算式中完成。但是請注意,如果不小心,此類存取可能會產生競爭條件,從而導致資訊洩漏。例如,考慮以下資料表設計
-- definition of privilege groups CREATE TABLE groups (group_id int PRIMARY KEY, group_name text NOT NULL); INSERT INTO groups VALUES (1, 'low'), (2, 'medium'), (5, 'high'); GRANT ALL ON groups TO alice; -- alice is the administrator GRANT SELECT ON groups TO public; -- definition of users' privilege levels CREATE TABLE users (user_name text PRIMARY KEY, group_id int NOT NULL REFERENCES groups); INSERT INTO users VALUES ('alice', 5), ('bob', 2), ('mallory', 2); GRANT ALL ON users TO alice; GRANT SELECT ON users TO public; -- table holding the information to be protected CREATE TABLE information (info text, group_id int NOT NULL REFERENCES groups); INSERT INTO information VALUES ('barely secret', 1), ('slightly secret', 2), ('very secret', 5); ALTER TABLE information ENABLE ROW LEVEL SECURITY; -- a row should be visible to/updatable by users whose security group_id is -- greater than or equal to the row's group_id CREATE POLICY fp_s ON information FOR SELECT USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user)); CREATE POLICY fp_u ON information FOR UPDATE USING (group_id <= (SELECT group_id FROM users WHERE user_name = current_user)); -- we rely only on RLS to protect the information table GRANT ALL ON information TO public;
現在假設 alice
想要更改 「稍微隱藏的」 資訊,但認為不應該讓 mallory
信任該列的新內容,因此她執行了
BEGIN; UPDATE users SET group_id = 1 WHERE user_name = 'mallory'; UPDATE information SET info = 'secret from mallory' WHERE group_id = 2; COMMIT;
這看起來很安全;在 mallory
應該能夠看到 「對 mallory 保密的」 字串的時間點上,沒有任何漏洞。但是,這裡存在競爭條件。如果 mallory
同時執行(例如)
SELECT * FROM information WHERE group_id = 2 FOR UPDATE;
並且她的交易處於 READ COMMITTED
模式,她有可能看到 「對 mallory 保密的」。如果她的交易在 alice
的交易之後立即到達 information
列,就會發生這種情況。它會阻塞,等待 alice
的交易提交,然後由於 FOR UPDATE
子句,而提取更新後的列內容。但是,它 不會 為隱式 SELECT
從 users
中提取更新後的列,因為該子 SELECT
沒有 FOR UPDATE
;相反,users
列是使用在查詢開始時拍攝的快照讀取的。因此,策略運算式會測試 mallory
的權限等級的舊值,並允許她查看更新後的列。
解決此問題的方法有很多。一個簡單的答案是在列安全性策略的子 SELECT
中使用 SELECT ... FOR SHARE
。但是,這需要在受影響的使用者上授予 UPDATE
權限(此處為 users
),這可能是不希望的。(但是,可以套用另一個列安全性策略來防止他們實際行使該權限;或者子 SELECT
可以嵌入到安全性定義器函數中。)此外,在參考資料表上大量並行使用列共用鎖定可能會導致效能問題,尤其是在頻繁更新它的情況下。另一種解決方案是,如果參考資料表的更新不頻繁,則在更新它時,對參考資料表採用 ACCESS EXCLUSIVE
鎖定,以便沒有並行交易可以檢查舊的列值。或者,可以在提交參考資料表的更新之後,並在進行依賴於新安全性情況的變更之前,等待所有並行交易結束。
有關更多詳細資訊,請參閱 CREATE POLICY 和 ALTER TABLE。
如果您在文件中發現任何不正確、與您對特定功能的體驗不符或需要進一步澄清的內容,請使用此表單報告文件問題。