-- Fine-Grained Access Control
细粒度访问控制可以构建一个在细粒度级别强制执行安全策略的应用程序;就像幻灯片上讲的,它是限制行访问的,使用谓词的,需要从一个函数返回值的,与一个表或者视图关联的,强制自动执行的;
FGAC可以用于限制行/列的访问,用途如下:
1.客户只能看到自己的账户;
2.医生只能看到自己病人的记录;
3.经理只能看到他手下员工的记录;
4.店员可以看到客户消费的记录,但是看不到信用卡号码;
使用FGAC,需要使用一个安全策略函数返回一个谓词(即一个WHERE条件),把函数附加到表或者视图之后,谓词就可以控制表或者视图中的记录访问了;当用户在对象上执行一个SQL语句(SELECT/INSERT/UPDATE/DELETE)时,Oracle会动态的修改SQL语句,并且对用户和应用程序是透明的,所以用户只能适当的记录;
使用FGAC,可以做:
1.可以为增删改查操作指定不同的策略;
2.可以只在需要的对象上添加安全策略;
3.可以在一个对象上添加多个策略;
4.可以组策略使策略可以应用到程序;
5.可以限制用户访问的记录数,而对应用程序透明;
6.可以限制敏感列的访问;
7.可以查看所有的列,但是隐藏掉敏感列;
这所有的功能都是通过DBMS_RLS包来实现的;
幻灯片中,两个用户执行了相同的语句,但是经过内部的改写查询到了只属于自己的记录;
-- Benefits
安全性:FGAC是附加在表或者视图上的,也就意味着不论用户使用什么方式访问数据,都会被强制应用;(FGAC和Oracle Label Security,如果用户使用直接路径导出的话会被绕过)
简单性:对于表或者视图只需要应用一次;
可扩展行:比如可以对SELECT操作设定一个策略,对INSERT操作设定一个策略,对UPDATE/DELETE操作设置一个策略;
高性能:可以根据策略的访问频率,把活跃的策略cache到shared pool中;
--VPD
VPD是FGAC和安全上下文的结合,它使用FGAC来限制行和列的访问,使用安全上下文来高效的提供用于设置策略谓词的信息;FGAC技术是VPD和OLS的基础,用于构建块;
不使用应用上下文的FGAC需要重复执行子查询和反复评估策略,使用应用上下文后,使策略谓词保持不变,从而降低甚至消除了重复执行子查询和反复评估策略的消耗;
-- Example of the VPD
假设一个订单录入(Order Entry)应用,需要满足两个基本的原则:
1.客户只能查看自己的订单;
2.销售代表只能查看属于他们客户的订单;
基于这个原则需要有两个基于应用上下文谓词,一个是用于客户的,一个适用于员工(即销售人员)的:
当客户和销售代表都是用SELECT * FROM oe.orders查询时,分别被改写为:
SELECT * FROM oe.orders WHERE customer_id = sys_context('oeapp','cust_id');
SELECT * FROM oe.orders WHERE sales_rep_id = sys_context('oeapp','emp_id');
-- How Fine-Grained Access Control Works
FGAC的核心就是动态修改SQL语句;
-- Tools
sys_context:返回上下文属性的值;
dbms_session:维护应用程序上下文,列举应用程序上下文,维护全局上下文的标示符;
dbms_rls:用来实现VPD,维护应用程序上下文,管理策略和组策略;
Oracle Policy Manager:
1.基于dbms_rls的;
2.图形化的界面,更容易使用和管理;
3.主要用于管理应用上下文,VPD和OLS;
--opm
它不是随着Oracle EM dbconsole一起安装的,需要单独安装;一般不会使用,完全可以用脚本替代;
启动的方式:oracle>oemapp opm;
--dbms_rls
dbms_rls过程会在操作前提交当前的DML事务;然而如果它是DDL或者触发器的一部分的话则不会提交当前的事务;
尽管不是所有的过程的参数都相同,但是大部分的参数含义都是一样的:
object_schema:需要添加策略的对象所属的schema名称,如果是NULL就是当前schema;
object_name:需要添加策略的对象名;
policy_name:策略名称,要唯一;
function_schema:被调用函数所属的schema名称;
policy_function:为策略生成谓词的函数,可以加包名;
statement_types:添加策略的语句类型(SELECT/INSERT/UPDATE/DELETE),可以是组合类型;
update_check:只对INSERT/UPDATE操作有效,默认是FALSE,设置为TRUE表示INSERT/UPDATE之后检查值是否违反策略;
enable:策略是否生效,默认是TRUE;
static_policy:默认是FALSE,设置为TRUE表示对于静态策略会产生相同的谓词,除了具有sys和exempt access policy权限的用户;
policy_type:策略类型;
long_predicate:默认是FALSE,表示返回的谓词最多4k字节,设置为TRUE可以达到32K;
sec_relevant_cols:只对基于列的VPD有效,用来保护查询中包含敏感信息的列,多列用逗号或者空格分开;默认是所有列;
sec_relevant_cols_opt:只对SELECT有效,用来设置sec_relevant_cols中的列的显示方式;默认是NULL,表示不显示这些列;设置为dbms_rls.all_rows表示显示这些列,但是是以加密的方式;
--Column-Level VPD
假设使用了基于列的VPD,认为员工表的salary和commission_pct列是敏感信息;
第一句只访问了用户名,没有涉及敏感信息,则SQL不会被改写;
下面两句,都涉及到了敏感信息,所以SQL会被改写;
--EXAMPLE
幻灯片例子是创建了一个hr_policy的策略,针对HR.EMPLOYEES表,调用的函数是HR.HRSEC,针对SELECT,INSERT操作有效,敏感列是salary和commission_pct,设置为dbms_rls.ALL_ROWS表示列都显示,但是显示的为NULL;
-- Policy Types
策略的类型决定了策略函数被重新评估的频率;策略函数的执行非常消耗系统资源,如果能减少执行次数,则可以显著的提高数据库的性能;
9i时候只有动态类型:表示每执行一次DML语句都要调用一下策略函数;
10g中添加了静态和上下文敏感的策略类型:这两中类型不用每次都调用策略函数,并且可以在多个数据库对象中共享;
共享的策略允许在多个对象上指定相同的策略;
-- Static Policies
策略函数只被评估一次;
策略的谓词结果被缓存到SGA中;
对于每一个用户每一个访问对象的语句都使用相同的谓词;
设置非常简单,只需要设置policy_type=>dbms_rls.STATIC即可;
注意:尽管语句每次都是用相同的策略谓词,但是每次语句返回的结果并不一定相同,因为谓词会根据应用上下文的属性或者函数来过滤数据,比如sysdate;
-- Context-Sensitive Policies
策略函数对每个会话执行一次,结果被缓存到PGA中;
-- Exceptions to FGAC Policies
FGAC是VPD和OLS的基础,这些例外对VPD和OLS同样适用;
直接路径导出,只有SYSDBA, EXPORT_FULL_DATABASE, EXEMPT ACCESS POLICY权限的用户可以执行直接路径导出;
具有sysdba和EXEMPTACCESS POLICY权限的用户;
-- Implementing a VPD
1.创建一个PL/SQL的包,用于设置上下文;
2.创建一个应用上下文,与第一步创建的包关联,确保上下文不能被修改;
3.写一个函数返回谓词,使用第二步创建的上下文;
4.创建一个策略,关联函数和一个表;
CREATE OR REPLACE PACKAGE pkg_oe_security IS
PROCEDURE set_cust_id;
FUNCTION cust_order(object_schema VARCHAR2,
object_name VARCHAR2) RETURN VARCHAR2;
END;
CREATE OR REPLACE PACKAGE BODY pkg_oe_security IS
PROCEDURE set_cust_id IS
v_cust_id NUMBER;
BEGIN
SELECTcustomer_id
INTOv_cust_id
FROMoe.customers
WHERE upper(cust_first_name)|| '_' || upper(cust_last_name) = sys_context('USERENV', 'SESSION_USER');
dbms_session.set_context('oeapp','cust_id', v_cust_id);
EXCEPTION
WHENno_data_found THEN
NULL;
END;
FUNCTION cust_order(object_schema VARCHAR2,
object_name VARCHAR2) RETURN VARCHAR2 IS
BEGIN
RETURN'customer_id = sys_context(''oeapp'', ''cust_id'')';
END cust_order;
END;
CREATE CONTEXT oeapp USING pkg_oe_security;
BEGIN
dbms_rls.add_policy(object_schema => 'oe',
object_name => 'orders',
policy_name => 'oe_policy',
function_schema=> 'sys',
policy_function => 'pkg_oe_security.cust_order',
statement_types => 'select');
END;
创建用户,并授权;
conn / as sysdba
SELECT * FROM oe.customers WHERE customer_id = 153;
CREATE USER divine_sheen IDENTIFIED BY oracle;
GRANT CONNECT TO divine_sheen;
GRANT EXECUTE ON pkg_oe_security TO divine_sheen;
GRANT SELECT ON oe.orders TO divine_sheen;
CREATE OR REPLACE TRIGGER tgr_oe_security_logon
AFTER logon ON DATABASE
BEGIN
pkg_oe_security.set_cust_id();
END;
conn divine_sheen/oracle
SELECT sys_context('oeapp', 'cust_id') FROM dual;
SELECT count(*) FROM oe.orders;
conn / as sysdba
SELECT * FROM oe.orders WHERE customer_id = 153;
-- 函数的格式;
FUNCTION policy_function (object_schema IN VARCHAR2, object_name IN VARCHAR2)RETURN VARCHAR2
object_schema:是对象所属的schema;
object_name:对象名;
测试函数:SELECT pkg_oe_security.cust_order('a', 'b') FROMdual;
-- 附加,基于列的VPD
CREATE OR REPLACE FUNCTION hr.fn_protect_salary(object_schema INVARCHAR2,
object_name IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
IF object_schema != USER THEN
RETURN '1=2';
END IF;
RETURN '';
END;
BEGIN
dbms_rls.add_policy(object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'hr_employees_fgac',
function_schema=> 'hr',
policy_function => 'fn_protect_salary',
statement_types => 'select',
enable => TRUE,
sec_relevant_cols => 'salary',
sec_relevant_cols_opt => NULL);
END;
conn / as sysdba
ALTER USER hr IDENTIFIED BY oracle ACCOUNT UNLOCK;
conn hr/oracle
SELECT employee_id, salary FROM hr.employees WHERE rownum < 10;
conn / as sysdba
CREATE USER fgac IDENTIFIED BY oracle;
GRANT DBA TO fgac;
conn fgac/oracle
SELECT employee_id, salary FROM hr.employees WHERE rownum < 10; -- 无记录返回;
BEGIN
dbms_rls.drop_policy(object_schema => 'HR',object_name => 'EMPLOYEES', policy_name => 'hr_employees_fgac');
END;
BEGIN
dbms_rls.add_policy(object_schema => 'HR',
object_name => 'EMPLOYEES',
policy_name => 'hr_employees_fgac',
function_schema=> 'hr',
policy_function => 'fn_protect_salary',
statement_types => 'select',
enable => TRUE,
sec_relevant_cols => 'salary',
sec_relevant_cols_opt => dbms_rls.all_rows);
END;
conn fgac/oracle
SELECT employee_id, salary FROM hr.employees WHERE rownum < 10; -- 敏感列返回空值;
-- Partitioned Fine-Grained AccessControl
应用驱动的安全策略:在9i时,还没有分区细粒度控制,定义在表/视图上的所有策略会被应用到所有的SQL语句,比如对商品表中的一种商品写的谓词可能会被用到其它的商品上去;这就需要开发人员去沟通,协调每个策略怎么制定,比较麻烦;10g之后,可以使用分区细粒度控制来实现应用驱动的安全策略;
根据激活的驱动上下文的不同可以应用不同的策略:驱动上下文的概念,它也是一个上下文,里面多一个属性来确定某一个组来应用哪种策略;
策略可以被独立开发:同类的策略放在一个组中;
默认的策略总是被应用;之后会讲到默认策略;
幻灯片上的例子是说:左边是公司A的订单录入员在对orders表访问,通过订单录入系统来设置驱动上下文(比如根据员工编号设置上下文),他只能自己录入的订单;右边是公司B,它们是供货商,通过供货商系统来设置驱动想下文(比如产品编号),他们只能查看自己供应的商品的工单;虽然查询语句都是select * from orders,但是返回的结果不同;
-- Group Policies
1.首先确定默认的策略;
2.为每一个表设置一个驱动的上下文:创建上下文,创建上下文相关的函数,使上下文成为驱动上下文;
3.为每一个应用创建一个组策略;
4.添加每一个策略到合适的组;
-- Default Policy Group;
默认的所有的策略都属于sys_default组策略;
每一个对象都有一个默认的组,查看dba_policy视图即可;如果有一个策略关联了两个表的话,那么针对每个表都会有一个默认策略;
-- Making the Context a DrivingContext
使用add_policy_context过程使得一个context变成driving context;
-- 例子;
CREATE CONTEXT app_driver USING pkg_apps_cxt;
CREATE CONTEXT oeapp USING pkg_apps_cxt;
CREATE OR REPLACE PACKAGE pkg_apps_cxt IS
PROCEDURE set_driver(policy_group VARCHAR2);
PROCEDURE set_cust_id;
END pkg_apps_cxt;
CREATE OR REPLACE PACKAGE BODY pkg_apps_cxt IS
PROCEDURE set_driver(policy_group VARCHAR2) IS
BEGIN
dbms_session.set_context('app_driver','active_app', policy_group);
END set_driver;
PROCEDURE set_cust_id IS
BEGIN
dbms_session.set_context('oeapp','cust_id', 100);
set_driver('OE_GRP');-- set the driver
EXCEPTION
WHEN no_data_foundTHEN
set_driver('XX'); -- set the driver
END set_cust_id;
END pkg_apps_cxt;
BEGIN
dbms_rls.add_policy_context('OE', 'ORDERS','APP_DRIVER', 'ACTIVE_APP');
END;
BEGIN
dbms_rls.create_policy_group(object_schema=> 'OE', object_name => 'ORDERS', policy_group => 'OE_GRP');
END;
BEGIN
dbms_rls.add_grouped_policy(object_schema =>'oe',
object_name => 'orders',
policy_group => 'oe_grp',
policy_name => 'oe_security',
function_schema=> 'sys',
policy_function => 'pkg_apps_cxt.set_cust_id');
END;
conn / as sysdba
exec pkg_apps_cxt.set_cust_id;
SELECT sys_context('app_driver', 'ACTIVE_APP') FROM dual;
SELECT sys_context('oeapp', 'cust_id') FROM dual;
-- Preformance
1.把索引的列放到谓词中:因为要使用谓词访问表/视图,所以在这些列上加索引可以提高效率;
2.谓词中不要使用子查询:替代的方法,先执行子查询,把结果放到上下文中,然后在谓词中使用上下文;之前实验时就是这种方式,先取到客户id放到上下文中;
3.不要在谓词中使用字面值:跟正常的sql一样,字面值会产生硬结析,尽量使用绑定变量;
4.尽量使用静态策略:这个的好处就是减少策略被调用的次数;
-- Export and Import
1.导入对象时,如果想还原表/视图上的策略,那么必须要有dbms_rls的执行权限;
2.导出对象时,如果表/视图上有细粒度审计策略,那么只能导出它可以可以访问的记录;
3.只有sys用户,export_full_database角色和有exempt access policy权限的用户可以执行直接路径导出;
-- Policy Views
SELECT * FROM dba_policies;所有的security policy的视图;
SELECT * FROM dba_policy_contexts;所有的driving contexts的视图;
SELECT * FROM dba_policy_groups;所有的policy group的视图;
SELECT * FROM v$vpd_policy;所有的细粒度审计策略和相关的谓词;
SELECT * FROM dba_context;数据库中定义的上下文;
-- Checking for Policies Applied toSQL
SELECT DISTINCT policy, predicate, sql_text FROM v$vpd_policy p, v$sql sWHERE s.child_address = p.address;
v$vpd_policy:包含所有应用到SQL语句上的策略;