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