存儲過程的細粒度訪問控制
譯文譯者 | 李睿
審校 | 重樓
概要
現代的企業數據庫具有全面的安全機制,可以對圖表和視圖中的數據實施細粒度訪問控制。
然而,當涉及到存儲過程時,訪問控制機制可能相對粗略:能夠執行存儲過程,或者不能。
本文將展示一種使用可編程代理的更靈活的方法,該方法允許基于所有可用信息(例如參數值、返回值等)精確控制存儲過程的調用。對于那些在管理對企業數據庫的訪問時需要更多粒度和靈活性的人來說,這種方法可能會引起他們的興趣。
細粒度訪問控制
大多數企業數據庫都提供了細粒度的安全機制,確保只有經過授權的用戶才能對數據進行訪問和修改,而且可以控制到具體的行和列級別。例如:
- Microsoft SQL Server具有行級安全性和列級授權
- IBM DB2具有行和列訪問控制以及相當精細的基于標簽的訪問控制
- Oracle具有細粒度訪問控制、Oracle標簽安全性和其他一些機制
在所有情況下,概念都是相同的:應該能夠在非常細粒度的級別上指定哪些操作可以由哪些用戶對哪些數據執行。
但是,當涉及到存儲過程時,所有數據庫都有一個簡單的訪問/不訪問機制。用戶可以執行給定的存儲過程,也可以不執行。
是否需要對存儲過程進行細粒度訪問控制?
許多組織大量使用存儲過程,有時甚至會完全阻止對圖表和視圖的直接訪問,所有數據訪問都必須通過存儲過程。
存儲過程本身通常必須提供復雜的訪問控制作為其實現的一部分,以確保調用有效,即使底層數據本身受到細粒度訪問控制的保護。這使得存儲過程更加復雜,調用成本更高,更改也更頻繁。
對許多人來說,這只是開展業務的成本,但在某些情況下,基于代理的方法在以下情況下很有用:
- 限制取決于難以從SQL訪問的外部數據
- 存儲過程不容易修改以反映安全需求,例如,因為它們是用戶無法控制的第三方包的一部分
- 用戶沒有訪問數據庫的特權
- 作為架構師,更傾向于用精細化訪問控制外部化,而不是將其嵌入到存儲過程中
- 需要更改一些參數值或將調用重定向到不同的存儲過程
即使這些方法都不適合,也可以尋求其他方法以開辟新的道路。
可以使用代理控制什么?
可編程數據庫代理可以控制數據庫服務器和客戶機之間的任何事項,但本文將重點討論存儲過程。
通過引入可編程代理,可以控制存儲過程調用的三個關鍵方面:
- 調用本身
- 傳遞給存儲過程的參數值
- 存儲過程返回的值或結果集
(1)控制調用
代理可以拒絕或修改存儲過程的調用。這可能有以下幾個原因:
- 參數值不可接受
- 場景不正確:調用來自意外的地址,代理檢測到意外的行為模式或任何其他因素
- 可以將調用重定向到不同的存儲過程,并且可以相應地調整參數
- 調用可以調用多個存儲過程,并合并結果
(2)控制參數值
代理還可以對客戶端傳遞的參數執行邏輯:
- 可記錄或記錄參數值
- 邏輯可以驗證參數是否具有可接受的值
- 邏輯可以修改這些參數的值
- 邏輯可以拒絕參數
(3)控制返回值和結果集
一旦執行了存儲過程,它可能會返回一些數據,這些數據可能是單獨的值,也可能是一個或多個結果集。
基于這些值或結果集,代理可以:
- 讓一切都流向客戶端
- 停止調用并向客戶端返回錯誤,例如,如果代理確定客戶端沒有被授權查看特定的數據塊
- 通過修改、隱藏、從結果集中刪除行和列值等方式修改值或結果集…
- 如果確定客戶端不應該有訪問權限,但也不應該知道它沒有訪問權限,則返回null值或空結果集
如何向數據庫添加代理?
向數據庫添加代理通常只需啟動一個或多個代理,并將客戶端引導到代理即可。因此,與通常的連接不同:
在中間添加代理,并開始在代理中添加需要的任何邏輯:
讓我們看一個簡單的例子。將使用SQL Server作為數據庫,使用Gallium Data作為代理。
給定一個簡單的存儲過程:
SQL
CREATE PROCEDURE DEMO.CREATE_PRODUCT (
IN NAME VARCHAR(50),
IN PRICE DECIMAL(10,2),
IN TYPEID INT)
希望實現以下要求:
(1)只有MGMT組中的用戶可以創建type為98或99的產品
(2)只有type > 100的產品價格才能超過5000美元
(3)type16和type17的產品實際上必須使用CREATE_SPECIAL_PRODUCT過程創建
使用代理中的RPC篩選器很容易滿足第一個要求:
JavaScript
let typeId = context.packet.parameters[2].value;
if (typeId === 98 || typeId === 99) {
let rs = context.mssqlutils.executeQuery("select is_member('MGMT') as res");
let isMember = rs.rows[0].res;
if ( ! isMember) {
context.result.errorMessage = "User is not a member of the MGMT group";
return;
}
}
如果需求沒有得到滿足,這將導致客戶端接收到錯誤提示。
第二個示例要求是一個簡單的擴展:
JavaScript
let price = context.packet.parameters[1].value;
if (price > 5000 && typeId <= 100) {
context.result.errorMessage = "Price is too high for this type of product";
return;
}
第三個示例要求更簡單:
JavaScript
if (typeId === 16 || typeId === 17) {
context.packet.procName = "CREATE_SPECIAL_PRODUCT";
}
在最后一個示例中,假設CREATE_SPECIAL_PRODUCT過程采用與CREATE_PRODUCT相同的參數,但是如果情況并非如此,其邏輯當然可以根據需要更改參數。
這些都是簡單的示例,但可以讓人們了解:使用可編程數據庫代理保護存儲過程相當簡單,如果不能(或不想)更改存儲過程,那么它尤其有用。
原文標題:Fine-Grained Access Control for Stored Procedures,作者:Max Tardiveau