利用OLS实现行级安全性 Step By Step

前言:
在Oracle9i中有一个组件称为Oracle Label Security,这个组件实现了基于自定义策略而对数据库中的表甚或是整个Schema提供行级安全性功能。实际上Oracle Label Security是在Oracle8.1.7中提出的,在9i版本中功能得到了大幅度增强。

本文通过一个简单的例子对Oracle Label Security的功能作初步的探讨,本文使用的环境是Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 for Windows 32 bit。

流程:
首先我们了解一下实现Oracle Label Security的大体流程。
通过Oracle提供的一系列存储过程,先创建一个policy,然后在policy中创建level,compartment,group,之后通过这些定义好的level,compartment,group再定义label,然后将policy绑定到某张表或者某个schema,最后再给相应的用户设置label。

其中牵涉到几个名词,解释一下:
Policy:就是安全策略,一个安全策略是level,compartment,group,label的集合。
Level:等级,这是最基础的安全控制等级,必须设置。
Compartment:分隔(这不是官方翻译),提供第二级的安全控制,是可选的。
Group:组(这不是官方翻译),提供第三级的安全控制,是可选的。
Label:标签,最终体现到每一行上的安全标签,必须设置。只有用户被赋予的标签和此行上的标签相同或者等级更高的时候,该行才能够被用户存取。

方法:
然后再来了解一下Oracle实现Label Security的大体方法。
在设置完Label以后,通过SQL Trace,我们可以看到用户发出的select语句执行计划有了变化,即使是全表扫描,也会多出一个filter的操作。
select *
from
documents

Rows Row Source Operation
——- —————————————————
5 FILTER (cr=7 pr=0 pw=0 time=184 us)
5 TABLE ACCESS FULL DOCUMENTS (cr=7 pr=0 pw=0 time=131 us)
通过查看raw trace file可以看到Oracle在parse这个SQL的时候,自动添加了一个条件:
begin :con := LBAC_RLS.READ_FILTER(:sn, : on); end;
而这个条件的具体操作在trace file中也有保存,此处就不多说了。总之,通过trace我们知道对于设置了label的表的select操作,Oracle自动添加了条件来保证用户只能选择到自己能够看到的记录。

那么,对于DML(insert,update,delete)操作呢?其实,Oracle是自动在表上添加了触发器,这一点我们可以在设置完label以后,通过dba_triggers视图验证,添加的是INSERT AFTER EACH ROW,UPDATE AFTER EACH ROW和DELETE BEFORE EACH ROW三个trigger,Oracle的很多功能在内部其实都是通过trigger完成,比如高级复制同样如此,但是Oracle一直声称这些trigger是内部实现的,所以比用户自定义的trigger效率要高。当然,内部trigger和自定义trigger在实现机制上是否真的不同,还有待验证。

目的:
下面我们的例子将实现这样的功能。

给DOCUMENTS表中的数据设置8个标签:
第一个标签是PUBLIC,表示该类记录所有人都可以读取。
第二个标签是HR部门的员工才可以读取的。
第三个标签是HR部门并且是WEST地区的员工才可以读取的。
第四个标签是HR部门并且是EAST地区的员工才可以读取的。
第五个标签是FIN部门的员工才可以读取的。
第六个标签是FIN部门并且是WEST地区的员工才可以读取的。
第七个标签是FIN部门并且是EAST地区的员工才可以读取的。
第八个标签是无论哪个部门,无论哪个地区,都可以读取。

这8个标签用于控制DOCUMENTS表中的所有记录,记录一共分成5种类型,这里我们简化为5条记录。
SQL> select * from kamus.documents;

DOCID DOCNAME
———- —————————————-
1 SHARE_WARE
2 INTERNAL_HR_WEST
3 INTERNAL_HR_EAST
4 INTERNAL_FIN_EAST
5 INTERNAL_FIN_WEST

如果一个用户被赋予了第一个标签,那么他只能读取DOCID=1的第一条记录。
如果一个用户被赋予了第二个标签,那么他能读取1,2,3这三条记录。
如果一个用户被赋予了第三个标签,那么他能读取1,2这两条记录。
如果一个用户被赋予了第四个标签,那么他能读取1,3这两条记录。
如果一个用户被赋予了第五个标签,那么他能读取1,4,5这三条记录。
如果一个用户被赋予了第六个标签,那么他能读取1,5这两条记录。
如果一个用户被赋予了第七个标签,那么他能读取1,4这两条记录。
如果一个用户被赋予了第八个标签,那么他能读取所有的5条记录。

因为是初次探讨Label Security功能,所以可能在标签设置上还有更加简化的方法,也希望大家能够在看了本文以后思考一下,看看是否能更加简单地实现相同的功能。

步骤:
我们需要用到3个用户,一个是拥有DOCUMENTS表的KAMUS用户,一个是用于设置OLS策略的LBACSYS用户,另外一个是不受OLS策略制约的SYS用户(用来方便的插入和更新测试数据)。
1. 安装Oracle Label Security
在安装数据库软件的时候必须保证选择了Oracle Label Security组件,否则所有功能都无法使用。如果当时没有选择,可以重新运行Universal Installer进行安装。
LBACSYS用户可以利用$ORACLE_HOME/rdbms/admin/catols.sql创建。
SQL> CONN / AS SYSDBA;
SQL> @?/rdbms/admin/catols

在这个脚本的最后会自动关闭数据库,所以请不要在生产库上直接测试。

再次打开数据库,就可以使用LBACSYS用户登录了,默认密码就是lbacsys,如果投放在生产环境中,请立刻修改默认密码。

2. 创建安全策略
SQL> CONN lbacsys/lbacsys
SQL> EXEC sa_sysdba.create_policy(‘DOC_POLICY’, ‘DOC_LABEL’);

第一个参数DOC_POLICY是策略的名称,第二个参数DOC_LABEL是即将添加到DOCUMENTS表中的用于存储标签的字段名,这个字段将在后面apply table policy的时候自动完成,所以不必预先添加。

可以从DBA_SA_POLICIES视图中查询安全策略的情况。

3. 定义Level
SQL> EXEC sa_components.create_level(‘DOC_POLICY’, 1000,’PUBLIC’, ‘Public Level’);
SQL> EXEC sa_components.create_level(‘DOC_POLICY’, 2000,’INTERNAL’, ‘Internal Level’);

第一个参数是上一步创建的安全策略的名字。第二个参数是Level的等级,数字越大表示权限越高,比如此处具有INTERNAL等级的就可以同时查看有PUBLIC等级的数据。第三个参数是Level的短名,随便定义。第四个参数是Level的长名,只是起到一个说明的作用,随便定义。

可以从DBA_SA_LEVELS视图中查询安全等级的情况。

4. 定义Compartment。
本步操作是可选项,仅仅使用上一步中的Level就已经可以控制数据的行级安全性了,但是如果要实现更加复杂的控制,就可能需要定义Compartment和下一步的Group。

SQL> EXEC sa_components.create_compartment(‘DOC_POLICY’, 200,’FIN’, ‘FINANCE’);
SQL> EXEC sa_components.create_compartment(‘DOC_POLICY’, 100,’HR’, ‘HUMAN_R’);

参数依次是安全策略名,Compartment数字,短名,长名,此处的Compartment数字不涉及到权限的高低,仅仅是一个标识符而已。
可以从DBA_SA_COMPARTMENTS视图中查询安全间隔的情况。

5. 定义Group。
SQL> EXEC sa_components.create_group(‘DOC_POLICY’, 10,’ALL’, ‘ALL_REGIONS’);
SQL> EXEC sa_components.create_group(‘DOC_POLICY’, 20,’WEST’, ‘WEST_REGIONS’);
SQL> EXEC sa_components.create_group(‘DOC_POLICY’, 30,’EAST’, ‘EAST_REGIONS’);

参数依次是安全策略名,Group数字,短名,长名,此处的Group数字不涉及到权限的高低,仅仅是一个标识符而已。

可以从DBA_SA_GROUPS视图中查询安全组的情况。

注意,create_group存储过程有第5个参数,是parent_name,通过这个参数可以实现几乎无限制的多层权限控制,本文就不再深入了。

6. 创建Label。
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘10000’, ‘PUBLIC’, TRUE);
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘30000’, ‘INTERNAL:HR:WEST,EAST’, TRUE);
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘35000’, ‘INTERNAL:HR:WEST’, TRUE);
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘36000’, ‘INTERNAL:HR:EAST’, TRUE);
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘40000’, ‘INTERNAL:FIN:WEST,EAST’, TRUE);
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘45000’, ‘INTERNAL:FIN:WEST’, TRUE);
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘46000’, ‘INTERNAL:FIN:EAST’, TRUE);
SQL> EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘50000’, ‘INTERNAL:FIN,HR:EAST,WEST’, TRUE);

创建了上文提到的8个label。

参数依次是安全策略名,Label Tag,Label值,是否为data label。
其中Label Tag必须是不同于系统中任何策略number的数字。
Label值是最关键的地方,通过组合前面几步中定义的level,compartment,group来设置标签,各个级别之间用冒号(:)隔开,一个级别中的多个元素用逗号(,)隔开。
是否为data label是一个布尔值,只有为TRUE的时候,这个标签才可以用于控制表数据的安全性。

可以从DBA_SA_LABELS视图中查询安全标签的情况。

7. 将策略赋予表。。
SQL> EXEC sa_policy_admin.apply_table_policy –
> ( policy_name => ‘DOC_POLICY’ –
> , schema_name => ‘KAMUS’ –
> , table_name => ‘DOCUMENTS’ –
> , table_options => ‘LABEL_DEFAULT, READ_CONTROL,WRITE_CONTROL,HIDE’);

前三个参数表示我们将DOC_POLICY策略附加到KAMUS用户的DOCUMENTS表上,执行这步操作的时候,Oracle会自动将第二步中定义的列添加到表中,如果这步执行成功,我们立刻用KAMUS用户检索DOCUMENTS表,会发现一条记录都没有了,这说明Label Security已经起作用了。

第四个参数用于设定策略如何控制表的安全性。LABEL_DEFAULT表示如果以后一个用户新增数据的时候没有指定Label那么将会使用该用户的default session label(这个default值在下面一步的用户Label设定中定义),READ_CONTROL,WRITE_CONTROL表示对于表的读写操作都受到安全策略的制约,HIDE表示不在desc表结构的时候显示DOC_LABEL列名,如果想要显示就省略HIDE字样。

注意,一旦apply策略完成,那么要修改table_options的值,比如想把HIDE去掉,那么就必须先用sa_policy_admin.remove_table_policy函数删除policy定义,然后重新apply。

8. 将Label赋予用户。
使用sa_user_admin.set_user_labels存储过程来将label赋予用户,这个存储过程有不少参数,但是必须输入的只有policy_name,user_name,max_read_label三项,其它参数如果省略的话,都有默认值。
比如def_label参数(用户新增数据的时候没有指定Label时的默认Label)如果没有设定,那么默认为跟max_read_label相同。

我们通过给KAMUS用户赋予不同的Label,来完成测试的目的。每次用LBACSYS用户设置完KAMUS用户的label,KAMUS用户都必须重新登录一次,设置才会生效。

–如果一个用户被赋予了第一个标签,那么他只能读取DOCID=1的第一条记录。
SQL> EXEC sa_user_admin.set_user_labels(policy_name=> ‘DOC_POLICY’,user_name =>’KAMUS’,max_read_label =>’PUBLIC’);

SQL> conn kamus/kamus
Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0
Connected as kamus

SQL> select * from documents;

DOCID DOCNAME DOC_LABEL
———- —————————————- ———–
1 SHARE_WARE 10000

–如果一个用户被赋予了第二个标签,那么他能读取1,2,3这三条记录。
SQL> EXEC sa_user_admin.set_user_labels(policy_name=> ‘DOC_POLICY’,user_name =>’KAMUS’,max_read_label =>’INTERNAL:HR:EAST,WEST’);

SQL> conn kamus/kamus
Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0
Connected as kamus

SQL> select * from documents;

DOCID DOCNAME
———- —————————————-
1 SHARE_WARE
2 INTERNAL_HR_WEST
3 INTERNAL_HR_EAST

–如果一个用户被赋予了第三个标签,那么他能读取1,2这两条记录。
SQL> EXEC sa_user_admin.set_user_labels(policy_name=> ‘DOC_POLICY’,user_name =>’KAMUS’,max_read_label =>’INTERNAL:HR:WEST’);

SQL> conn kamus/kamus
Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0
Connected as kamus

SQL> select * from documents;

DOCID DOCNAME
———- —————————————-
1 SHARE_WARE
2 INTERNAL_HR_WEST

–如果 …… 此处省略中间的测试,大同小异。

–如果一个用户被赋予了第八个标签,那么他能读取所有的5条记录。
SQL> EXEC sa_user_admin.set_user_labels(policy_name=> ‘DOC_POLICY’,user_name =>’KAMUS’,max_read_label =>’INTERNAL:FIN,HR:EAST,WEST’);

SQL> conn kamus/kamus
Connected to Oracle Database 10g Enterprise Edition Release 10.2.0.1.0
Connected as kamus

SQL> select * from documents;

DOCID DOCNAME
———- —————————————-
1 SHARE_WARE
2 INTERNAL_HR_WEST
3 INTERNAL_HR_EAST
4 INTERNAL_FIN_EAST
5 INTERNAL_FIN_WEST

至此,这个Oracle Label Security 的实验基本上是完成了,达到了我们预先计划的目标。

其它:
Label Security对于exp同样具有安全控制作用,但是并不全面。常规路径的EXP受到了安全控制。

D:\Temp>exp kamus/kamus tables=documents file=d.dmp

Export: Release 10.2.0.1.0 – Production on 星期四 1月 11 22:08:48 2007

Copyright (c) 1982, 2005, Oracle. All rights reserved.

连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 – Production
With the Partitioning, Oracle Label Security, OLAP and Data Mining options
已导出 ZHS16GBK 字符集和 AL16UTF16 NCHAR 字符集

即将导出指定的表通过常规路径…
EXP-00079: 表 “DOCUMENTS” 中的数据是被保护的。常规路径只能导出部分表。
. . 正在导出表 DOCUMENTS导出了 2 行
EXP-00091: 正在导出有问题的统计信息。
导出成功终止, 但出现警告。

但是只要加上direct=y这样用直接路径导出,就跳过了Label Security的控制。

D:\Temp>exp kamus/kamus tables=documents file=d.dmp direct=y

Export: Release 10.2.0.1.0 – Production on 星期四 1月 11 22:08:53 2007

Copyright (c) 1982, 2005, Oracle. All rights reserved.

连接到: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 – Production
With the Partitioning, Oracle Label Security, OLAP and Data Mining options
已导出 ZHS16GBK 字符集和 AL16UTF16 NCHAR 字符集

即将导出指定的表通过直接路径…
. . 正在导出表 DOCUMENTS导出了 5 行
成功终止导出, 没有出现警告。

而关于性能方面,如果表中数据量很大,那么出于性能考虑,可能需要在Label列上(本例中的DOC_LABEL)添加合适的索引,根据cardinality的多少,选择B-Tree或者bitmap索引。

提问:
大家可能注意到在第5步定义Group的时候,我定义了3个组,其中ALL组一直没有用到,那么现在的问题就是:
新创建一个Label:
EXEC sa_label_admin.create_label(‘DOC_POLICY’, ‘20000’, ‘INTERNAL:HR:ALL’, TRUE);
新插入一条数据
insert into kamus.documents values(6,’INTERNAL_HR_ALL’,20000);
而此时KAMUS用户的max_read_label是最高等级的’INTERNAL:FIN,HR:EAST,WEST’
那么select * from documents;会有几行数据返回?

注意:插入数据,可以通过直接指定Label Tag数值或者使用CHAR_TO_LABEL函数,比如上面的insert语句等值于:
insert into kamus.documents values(6,’INTERNAL_HR_ALL’, CHAR_TO_LABEL(‘DOC_POLICY’,’ INTERNAL:HR:ALL’));

结论:
通过Oracle Label Security策略可以很方便地在数据库这个层面上完成对于应用数据的安全性保护,个人认为跟基于VPD和SYS_CONTEXT的行级安全保护方法相比,OLS在分层控制方面更有效,设置更简单,而后者则因为手动编写VPD策略函数,所以具有更大的灵活性。但是无论是使用OLS还是VPD策略函数,都无法防止SYSDBA用户查看业务数据,针对此问题,Oracle的解决方案是Oracle Data Vault。

后续:
Oracle数据库安全领域有很多我还没有接触到的东西需要学习,下面一篇文章我将会介绍Oracle10g的新增组件Oracle Data Vault ,敬请期待。

5 thoughts on “利用OLS实现行级安全性 Step By Step

  1. kamus says:

    所有的security功能在国内都用的很少。
    如果说data vault,那恐怕还没有人用,因为是10gR2刚刚推出的选件,目前还只有Windows 32bit,Linux和Solaris平台的版本。

  2. dido says:

    OLS有国内使用的案例吗?
    我平常接触的系统里,并没有每个实际用户都在数据库里有对应的帐号。

  3. dido says:

    今天拜读了你以前关于DG测试的文章,你提到“Bug实在是太多,性能又低,虽说可以同时Apply + Readonly,但这个优点在它的不稳定性面前实在是起不了什么决定性作用了。”
    BUG在文章里列举了,那么性能到底有多低呢?

Leave a Reply

Your email address will not be published. Required fields are marked *