Oracle的行级安全性为用户提供了他们自己的虚拟专有数据库。
鉴于隐私法,如美国的HIPAA(健康保险移植和责任法案)、Gramm-Leach-Bliley法案、Sarbanes-Oxley法案以及欧共体的安全港法律(Safe Harbour Law),确保适当的信息隐私是当今众多企业迫切关心的一个问题。其他隐私指令,如Visa的持卡人信息安全计划(Cardholder Information Security Program,CISP)也要求企业确保对信息的访问是得到严格控制的。
Oracle一直都提供授权(或拒绝)用户访问数据库对象的能力,但是这些访问权限是在对象级别上定义的--是对于整张表,而不是对于表中特定的行而定义的。虽然对于许多应用程序来说这种方法已经足够了,但涉及金融、健康或其他类型的个人信息的应用程序通常需要对访问和授权进行更加独立的控制。
Oracle8i中引入的Oracle行级安全性(row-level security,RLS)特性提供了细粒度的访问控制--细粒度意味着是在行一级上进行控制。行级安全性不是向对表有任何访问权限的用户打开整张表,而是将访问限定到表中特定的行。其结果就是每个用户看到完全不同的数据集--只能看到那些该用户被授权可以查看的数据--所有这些功能有时被称为的Oracle虚拟专有数据库(或称为VPD)特性。
使用Oracle的VPD功能不仅确保了企业能够构建安全的数据库来执行隐私政策,而且提供了应用程序开发的一个更加可管理的方法,因为虽然基于VPD的政策限制了对数据库表的访问,但在需要的时候可以很容易地对此做出修改,而无需修改应用程序代码。
例如,假设银行的账户经理(AM)向高净值账户持有者提供个人客户支持。AM使用定制的银行应用程序来帮助他们检查客户的余额、存款或提取的款项,以及对贷款要求做出决定。银行的政策曾经允许所有AM可以访问所有账户持有人的信息,但在最近,对该政策做了改变。现在,分配给AM一个特定的客户集,他们只需访问只与这些客户有关的信息。政策的变化必须反映在应用程序中,该应用程序现在向每个AM显示所有客户的信息,而不只是关于分配给AM的那些客户的信息。
为了使应用程序符合新的隐私政策,银行有三种选择:
修改应用程序代码,使所有SQL语句都包含一个判定词(WHERE子句)。然而这种选择不能保证在应用程序之外执行隐私政策,而且如果将来政策又有变化,则必须再一次修改代码,所以从长远考虑这不是一个好方法。
保持应用程序不动,用一些必要的判定词创建表的一些视图,并用与表名一样的名字为这些视图创建同义词。从应用程序不变更和安全性的角度来看这种方法比较好,但可能难于管理,因为有大量潜在的视图需要跟踪和管理。
创建可动态生成判定词的政策函数来为每个AM创建一个VPD,通过利用内置的行级安全性(DBMS_RLS)来设置政策,这些函数随后可以用于所有对象,而不必考虑它们如何被访问。
最后一种选择提供了最佳安全性,它不增加管理负担,并能确保信息的安全隐私,这种方法就是所有账户经理根据他们自己的证书,可查看表的不同视图。
本文说明如何建立VPD安全性模型。我们将借助前面提到的银行例子通过创建函数、制定政策、然后测试结果来描述整个建立过程。(请注意,为使例子简单,没有完整定义该表。)
示例应用的基本设置
下面简单地给出示例银行应用的基本假设:
一个BANK模式拥有两个构成该应用的关键表--一个CUSTOMERS(客户)表:
姓名
空?
类型
---------
--------
-----------
CUST_ID
非空
数字CUST_NAME
非空
字符型变量2(20)
以及一个ACCOUNTS(账户)表:
姓名
空?
类型
-------
--------
-----------
ACCT_NO
非空
数字CUST_ID
非空
数字余额
数字(15,2)
代码清单1包含用于创建和填充这两个基本示例表的SQL脚本。
用户的SECMAN(安全性经理)拥有一个ACCESS_POLICY表,它识别AM以及他们各自客户的账户:
姓名
空?
类型-----------
---------
-----------AM_NAME
非空
字符型变量2(20)CUST_ID
非空
数字ACCESS_TYPE
非空
字符(1)
AM_NAME字段存储账户经理的用户ID;CUST_ID用于识别客户;ACCESS_TYPE定义指定的访问权限--S(SELECT--查询)、I(INSERT--插入数据)、D(DELETE--删除数据)以及U(UPDATE--更新数据)。ACCESS_POLICY表中有这样一些记录:
AM_NAME
CUST_ID
ACCESS_TYPE
-------
-------
-----------
SCOTT
123
S
SCOTT
123
I
SCOTT
123
D
SCOTT
123
U
SCOTT
456
S
SCOTT
789
S
LARA
456
I
LARA
456
D
LARA
456
U
LARA
456
S
正如你所看到的,客户123的AM SCOTT拥有所有权限--S、I、D和U--客户456的AM LARA也具有这些权限。还有,由于SCOTT可以批准LARA的客户456的账户结余,所以他拥有对客户456的S权限。
第一步 创建政策函数
无论AM何时要查看客户的账户信息,他都必须动态地应用银行的访问规则(包含在ACCESS_POLICY表中)。第一步是创建政策函数,用于返回要应用到表上的适当判定词。和对ACCESS_POLICY表的处理一样,出于安全考虑,政策函数也由用户的SECMAN创建和控制。最好是使隐私规则与它们将要应用到的表分开。
让我们仔细看一看政策函数get_sel_cust_id,如代码清单2― in detail:
准确接收两个参数:模式名称(p_schema in varchar2)和表名(p_table in varchar2)
只返回一个字符串值--return varchar2 as l_retstr varchar2(2000),它包含将要添加到表的每个查询中的判定词
使用一个游标例程--for cust_ rec in--来获得值的列表,因为每个用户可能有表中列出的几个cust_id
返回一个结构化的字符串l_retstr,无论用户何时试图访问基本表都将它用做一个判定词
该函数返回判定词where cust_id in,随后是用户(am_name = USER)能够看到的客户账户名单(由逗号分隔)。
请注意在进入构建用户cust_id名单的循环之前,代码清单2中的代码先检查该用户是否为表的所有者BANK,若是则返回NULL,如下所示:
if (p_schema = user) then
1_retstr := null;
构建完该函数后,你需要通过测试一些示例数据来确保它返回相应的判定词。以SECMAN身份连接数据库,向ACCESS_POLICY表中插入一些记录,授予SECMAN在几个示例账户上的读权限,如下所示:
insert into access_policy values ('SECMAN',123,'S');
insert into access_policy values ('SECMAN',456,'S');
insert into access_policy values ('SECMAN',789,'S');
下面来执行该函数:
select get_sel_cust_id
('BANK','CUSTOMERS') from dual;
该函数返回一个将被用作判定词的字符串,如下面的示例输出所示:
GET_SEL_CUST_ID('BANK','CUSTOMERS')
------------------------
CUST_ID IN (123,456,789)
你需要为其他类型的访问创建类似的函数。为简单起见,为所有其他访问类型创建一个单一的函数--UPDATE, DELETE, INSERT--如代码清单3所示。但是注意,对于现实世界中的应用,每种访问类型应该有自己的单独定义的函数,以确保相应的隐私。
代码清单3中的政策函数与代码清单2中的基本上是一样的,唯一的区别是通过使用ACCESS_CONTROL表中的信息进一步限定了判定词:
and access_type in ('I', 'U', 'D')
创建政策函数仅仅是第一步。现在你需要通过定义在你的系统中控制该函数的使用政策来确保这个函数将被使用。
第二步 定义政策
政策是用Oracle提供的DBMS_RLS包定义的。要知道政策本身并不是任何用户(schema)拥有的数据库对象,它们是逻辑结构。拥有对DBMS_RLS包的执行权限的用户可以修改或删除由其他用户创建的政策。对DBMS_RLS的执行权限应该受到严谨的控制。
在下面的例子中,用户SECMAN被(SYS)授予对DBMS_RLS包的执行权限:
grant execute on dbms_rls to secman;
代码清单4创建了一个关于模式BANK的CUSTOMERS表的政策,命名为CUST_SEL_POLIC。该政策将模式SECMAN拥有的函数GET_SEL_CUST_ID(见代码清单2)所返回的判定词应用到对该表的所有SELECT操作语句。
同样,对其他访问类型该表应遵循不同的政策,如代码清单5所示。该政策应用于对CUSTOMERS表的INSERT、UPDATE和DELETE操作语句。
该政策与SELECT政策几乎一样,但它包含如下一项检查以确保即使执行完UPDATE该政策仍然得到遵循:
update_check = TRUE
除非遵守该政策,否则数据不能被添加到该表中。
第三步 测试构建的模块
既然已经构建了这些模块,那么我们就来看一下它们是如何工作的。以用户BANK身份连接数据库,执行一条简单的select * from customers语句;查询结果显示如下:
CUST_ID
CUST_NAME
-------
------------
123
Jay Kulkarni
456
Wim Patel
这两条记录是CUSTOMERS表的全