Virtual Private Databases (VPD) allow multiple users to access a single schema whilst preventing them from accessing data that is not relevant to them. Although this type of access can be controlled by the application, access via other methods (SQL*Plus) would leave the data open to abuse. Setting up a VPD involves the following steps:
Setup Test Environment
Create an Application Context
Create Login Trigger
Create Security Policies
Apply Security Policies to Tables
Test VPD
What Next
Setup Test Environment
First we must create a user to act as the schema owner for this example. Obviously, you will perform the following tasks using your current schema owner:
CODE:
CONNECT sys/password@service AS SYSDBA;
CREATE USER schemaowner IDENTIFIED BY schemaowner
DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
GRANT connect, resource TO schemaowner;
CREATE USER user1 IDENTIFIED BY user1
DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
GRANT connect, resource TO user1;
CREATE USER user2 IDENTIFIED BY user2
DEFAULT TABLESPACE users TEMPORARY TABLESPACE temp;
GRANT connect, resource TO user2;
GRANT EXECUTE ON DBMS_RLS TO PUBLIC;
CONN schemaowner/schemaowner@service
CREATE TABLE users
(id NUMBER(10) NOT NULL,
ouser VARCHAR2(30) NOT NULL,
first_name VARCHAR2(50) NOT NULL,
last_name VARCHAR2(50) NOT NULL);
CREATE TABLE user_data
(column1 VARCHAR2(50) NOT NULL,
user_id NUMBER(10) NOT NULL);
INSERT INTO users VALUES (1,'USER1','User','One');
INSERT INTO users VALUES (2,'USER2','User','Two');
COMMIT;
GRANT SELECT, INSERT ON user_data TO user1, user2;
Create an Application Context
Grant CREATE ANY CONTEXT to the schema owner then create the context and context package:
CODE:
CONNECT sys/password@service AS SYSDBA;
GRANT create any context, create public synonym TO schemaowner;
CONNECT schemaowner/schemaowner@service;
CREATE CONTEXT SCHEMAOWNER USING SCHEMAOWNER.Context_Package;
CREATE OR REPLACE PACKAGE Context_Package AS
PROCEDURE Set_Context;
END;
/
Next we create the Context_Package body which will actually set the user context:
CODE:
CREATE OR REPLACE PACKAGE BODY Context_Package IS
PROCEDURE Set_Context IS
v_ouser VARCHAR2(30);
v_id NUMBER;
BEGIN
DBMS_Session.Set_Context('SCHEMAOWNER','SETUP','TRUE');
v_ouser := SYS_CONTEXT('USERENV','SESSION_USER');
BEGIN
SELECT id
INTO v_id
FROM users
WHERE ouser = v_ouser;
DBMS_Session.Set_Context('SCHEMAOWNER','USER_ID', v_id);
EXCEPTION
WHEN NO_DATA_FOUND THEN
DBMS_Session.Set_Context('SCHEMAOWNER','USER_ID', 0);
END;
DBMS_Session.Set_Context('SCHEMAOWNER','SETUP','FALSE');
END Set_Context;
END Context_Package;
/
SHOW ERRORS
Next we make sure that all users have access to the Context_Package:
CODE:
GRANT EXECUTE ON SCHEMAOWNER.Context_Package TO PUBLIC;
CREATE PUBLIC SYNONYM Context_Package FOR SCHEMAOWNER.Context_Package;
to be continue Create Login Trigger
Next we must create a trigger to fire after the user logs onto the database:
CODE:
CONNECT sys/password@service AS SYSDBA;
CREATE OR REPLACE TRIGGER SCHEMAOWNER.Set_Security_Context
AFTER LOGON ON DATABASE
BEGIN
SCHEMAOWNER.Context_Package.Set_Context;
END;
/
SHOW ERRORS
Create Security Policies:
In order for the context package to have any effect on the users interaction with the database, we need to define a Security_package for use with the security policy. This package will tell database how to treat any interactions with the specified table:
CODE:
CONNECT schemaowner/schemaowner@serivce;
CREATE OR REPLACE PACKAGE Security_Package AS
FUNCTION User_Data_Insert_Security(Owner VARCHAR2, Objname VARCHAR2)
return varchar2;
FUNCTION User_Data_Select_Security(Owner VARCHAR2, Objname VARCHAR2)
RETURN VARCHAR2;
END Security_Package;
/
Next we create the Security_Package body:
CODE:
CREATE OR REPLACE PACKAGE BODY Security_Package IS
FUNCTION User_Data_Select_Security(Owner VARCHAR2, Objname VARCHAR2) RETURN VARCHAR2 IS
Predicate VARCHAR2(2000);
BEGIN
Predicate:= '1=2';
IF (SYS_CONTEXT('USERENV', 'SESSION_USER');='SCHEMAOWNER')
THEN
Predicate:= NULL;
ELSE
Predicate:='USER_id=SYS_CONTEXT(''SCHEMAOWNER'', ''USER_ID'')';
RETURN Predicate;
END User_Data_Select_Security;
FUNCTION User_Data_Insert_Security(Owner VARCHAR2, Objname VARCHAR2) RETURN VARCHAR2 IS
Predicate VARCHAR2(2000);
BEGIN
Predicate := '1=2';
IF (SYS_CONTEXT('USERENV','SESSION_USER') = 'SCHEMAOWNER') THEN
Predicate := NULL;
ELSE
Predicate := 'USER_ID = SYS_CONTEXT(''SCHEMAOWNER'',''USER_ID'')';
END IF;
RETURN Predicate;
END User_Data_Insert_Security;
END Security_Package;
/
SHOW ERRORS
Next we make sure that all users have access to the Security_Package:
CODE:
GRANT EXECUTE ON SCHEMAOWNER.Security_Package TO PUBLIC;
CREATE PUBLIC SYNONYM Security_Package FOR SCHEMAOWNER.Security_Package;
Apply Security Policies to Tables
The DBMS_Rls package is used to apply the security policay, implemented by Security_Package, to the the relevant tables:
CODE:
BEGIN
DBMS_Rls.Add_Policy('SCHEMAOWNER', 'USER_DATA', 'USER_DATA_INSERT_POLICY',
'SCHEMAOWNER', 'SECURITY_PACKAGE.USER_DATA_INSERT_SECURITY',
'INSERT', TRUE);
DBMS_Rls.Add_Policy('SCHEMAOWNER', 'USER_DATA', 'USER_DATA_SELECT_POLICY',
'SCHEMAOWNER', 'SECURITY_PACKAGE.USER_DATA_SELECT_SECURITY',
'SELECT');
END;
/
Test VPD
Finally, test that the VPD is working correctly:
CODE:
CONNECT user1/user1@service;
INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 1', 1);
INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 2', 2);
COMMIT;
CONNECT user2/user2@service
INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 1', 1);
INSERT INTO schemaowner.user_data (column1, user_id) VALUES ('User 2', 2);
COMMIT;
CONNECT schemaowner/schemaowner@service
SELECT * FROM schemaowner.user_data;
CONNECT user1/user1@Service;
SELECT * FROM schemaowner.user_data;
CONNECT user2/user2@Service
SELECT * FROM schemaowner.user_data;
When connected to USER1, only the first insert will work.
When connected to USER2, only the second insert will work.
The failing inserts produce the error:
ORA-28115: policy with check option violation
Once the inserts are finished, there will be two rows in the table, as seen when connected as SCHEMAOWNER. When connected as USER1 or USER2, only the single row they inserted will be visible.
What Next
Once you're happy with the basic mechanism you can extend the Security_Package to cover all tables where restricted access is neccessary, remembering to apply all security policies to the relevant tables.
(END)
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表的全部内容,由于BANK拥有这个表,所以两条记录都显示出来了,因此判定子句为NULL--也就是说没有应用判定词。但是,当用户LARA执行同样的查询时,她看到的结果是:
select * from customers;
CUST_ID CUST_NAME
------- ---------
456 WIM PATEL
LARA只看到了CUST_ID 456,而看不到123,因为ACCESS_ POLICY表决定了她只被授权可以查看这一行。请注意该查询没有WHERE子句,但对表的查询结果被自动过滤,从而只显示被授权可以查看的行。
如果用户SCOTT执行同样的查询,那么他会得到与LARA查询不同的结果:
select * from customers;
CUST_ID CUST_NAME
------- ------------
123 Jay Kulkarni
456 Wim Patel
用户SCOTT看到了两行,因为如表ACCESS_POLICY所示,他得到的授权是可以看到这些行。当用户LARA执行该查询时,政策函数get_sel_cust_id返回判定词where cust_id in (456)。Lara最初的查询select * from customers被改写为
select * from
(select * from customers)
where cust_id in (456)
判定词被自动添加到用户最初的查询中。用户更新表时也是如此:
SQL> update bank.customers
2 set cust_name = 'PAT TERRY';
1 row updated.
请注意,在这个例子中,即使基本表中实际上有2行,但只更新了1行。政策(CUST_IUD_POLICY)将判定词where cust_id in (456)添加到更新语句。类似地,对表进行删除时,只有用户被授权可以删除的行才被删除。
试图插入包含着用户没有得到授权的1行数据,将产生出错消息。例如,在这个查询中,LARA试图向CUSTOMER表添加1条不在自己的权限范围内的账户记录:
SQL> insert into bank.customers
2 values (789,'KIM PARK');
insert into bank.customers
*
ERROR at line 1:
ORA-28115: policy with check option
violation
下一步
阅读更多有关
VPD Oracle9i提供的PL/SQL包和类型引用
Oracle隐私安全性审计
根据ACCESS_POLICY表,Scott拥有对账户789的SELECT权限--对于任何其他AM,在该表中没有列出其他权限。
政策与函数的联合使用确保了对表的特定记录进行经过授权的访问。不管如何访问表,是否通过应用程序或直接通过特定的查询工具(如SQL*Plus),都要应用这些规则。用户只能看到他们被授权访问的那些行。
政策可以用于多个表,而一个政策函数可以被任意数量的政策所用。代码清单6显示访问ACCOUNTS表的一个政策,它使用最初为使用CUSTOMERS表而创建的get_sel_cust_id函数。