The classic VPD use case is to set one or more session context variables when an end user logs into an application, and then use those context variables in a VPD policy that determines t
Trang 1Virtual Private Database
VPD is one of the best ways to push data security down to the lowest possible level It’s easy
to envision scenarios in which all the data security was built into the application layer When another technology is introduced that needs to access the same data, the semantics of the security policy has to be replicated to the new technology Obviously, this is difficult to maintain, prone
to errors, and easy to subvert, because all a nefarious individual has to do to access the data is connect directly to the database, effectively bypassing the security
VPD is a critically important solution to protect the data at the source The classic VPD use case is to set one or more session context variables when an end user logs into an application, and then use those context variables in a VPD policy that determines the rows to which a user has access This is a fairly straightforward task in client-server environments, where the database session of an end user persists as long as the user is logged into the application As discussed in Chapter 11, APEX database sessions persist only as long as it takes to process a page request, which is typically less than a second This is yet another area where the difference between nonpersistent and persistent sessions causes a lot of confusion for developers
Fortunately, you an integrate VPD with APEX in many ways One option is to use the Virtual Private Database attribute of an APEX application to call a procedure that sets session context variables This technique works particularly well with legacy VPD applications that are already using session context variables Another option is to reference APEX items in the VPD policy,
which we will refer to as an item-based policy A third option is to use Oracle Database Global
Application Context variables, which were specifically designed for use with stateless applications Global Application Contexts have a significant drawback in that they offer no built-in way to purge expired sessions As such, they are not a good solution for APEX and VPD, and are not covered in this book
APEX Item-based Policy
The easiest approach to implement is to use APEX items As you might expect, this approach has both advantages and disadvantages
Following are the advantages of using APEX items:
APEX items are persistent for the life of the end user’s APEX session If you set the value
of an item when a user logs into the application, that value will still be there as long as the user is using the application
Because the items are persistent, their values need to be set only once—for example, when a user authenticates This is especially beneficial if the code used to determine
a user’s authorization rights is expensive
The syntax used to set an APEX item is exceptionally easy
APEX items are purged at regular intervals so a developer doesn’t need to worry about cleanup procedures
The disadvantage of using APEX items is that APEX items can be used only with APEX
applications If the VPD policy is to be used in a heterogeneous environment, it will need
separate logic for APEX and any other technology that needs to access the data
■
■
■
■
Trang 2In the following example, we will create a simple table to store users and their roles When a user logs into our APEX application, we will store the role in an APEX item We will then reference the value of this APEX item in a function used for a VPD policy If the user’s role is ADMINISTRATOR,
he will see all rows in the table If his role is READ, he will see only his own row All others, such
as a user that access this table from a reporting tool, will not see any rows
SYS@AOS> grant create any context to sec_admin;
SYS@AOS> grant execute on sys.dbms_rls to sec_admin;
Execute the following DDL as schema SEC_ADMIN
create table sec_admin.user_app_roles(
id char(32),
user_name varchar2(255),
role_name varchar2(255),
constraint user_app_roles_pk primary key (id),
constraint user_app_roles_uq unique(user_name,role_name),
constraint user_app_roles_ck check (role_name in ('ADMINISTRATOR', 'READER')) )
/
create or replace
trigger bi_user_app_roles before insert on user_app_roles
for each row
begin
:new.id := sys_guid();
:new.user_name := upper(:new.user_name);
:new.role_name := upper(:new.role_name);
end;
/
insert into user_app_roles (user_name,role_name)
values ('DGRANT','ADMINISTRATOR');
insert into user_app_roles (user_name,role_name)
values ('JWHALEN','READER');
commit;
grant select on sec_admin.user_app_roles to sec_user;
create or replace function sec_admin.employees_apex_item_fn (
p_schema in varchar2 default null,
p_object in varchar2 default null)
return varchar2
as
l_return varchar2(255) := '1 = 2'; by default, this will return no rows l_role varchar2(255);
begin
A few basic tests to see if it looks like an APEX Session
Trang 3and regexp_instr(sys_context('userenv','module'),
'^APEX:APPLICATION[[:space:]][0-9]+$') > 0 then
ROLE_NAME is an APEX Application Level Item Its value
is set using an APEX Application Process.
if v('ROLE_NAME') = 'ADMINISTRATOR' then
l_return := '1 = 1'; all rows
elsIf v('ROLE_NAME') = 'READER' then
l_return := 'email = v('' APP_USER'')'; only their own row else
l_return := '1 = 2'; no rows
end if;
end if;
return l_return;
end;
/
The following 2 drop statements will drop the policies used in both
examples from this section to make sure we have a clean slate.
begin
dbms_rls.drop_policy (object_schema => 'DATA_OWNER',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEES_APEX_ITEM');
end;
/
begin
dbms_rls.drop_policy (object_schema => 'DATA_OWNER',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEES_CONTEXT');
end;
/
begin
dbms_rls.add_policy
(object_schema => 'DATA_OWNER',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEES_APEX_ITEM',
policy_function => 'EMPLOYEES_APEX_ITEM_FN');
end;
/
This function EMPLOYEES_APEX_ITEM_FN references an APEX item named ROLE_NAME.
Figure 12-11 shows the process that sets this item
Session Context-based Policy
The next example illustrates the use of a session context variable This technique is a more traditional approach to VPD and has an advantage in that it is applicable to other technologies such as Oracle Forms The main disadvantage of this technique when used with APEX is that the function used to set a user’s context is called on every page view If this function is expensive, it
could have a negative impact on performance However, if you are running Oracle Database 11g
Trang 4or later, you can use the Function Result Cache feature to cache the results and greatly improve performance
create or replace context sec_admin.employees_context using set_employees_context /
create or replace procedure sec_admin.set_employees_context
as
begin
dbms_session.set_context('EMPLOYEES_CONTEXT','ROLE_NAME', null);
for c1 in (select role_name
from sec_admin.user_app_roles
where user_name = sys_context('userenv','client_info')) loop
dbms_session.set_context('EMPLOYEES_CONTEXT','ROLE_NAME', c1.role_name); end loop;
end;
FIGURE 12-11 Application level, before header process to set the role
Trang 5grant execute on sec_admin.set_employees_context to sec_user;
create or replace function sec_admin.employees_context_fn (
p_schema in varchar2 default null,
p_object in varchar2 default null)
return varchar2
as
l_return varchar2(255) := '1 = 2'; by default, this will return no rows l_role varchar2(255);
begin
if sys_context('EMPLOYEES_CONTEXT','ROLE_NAME') = 'ADMINISTRATOR' then l_return := '1 = 1'; all rows
elsIf sys_context('EMPLOYEES_CONTEXT','ROLE_NAME') = 'READER' then
l_return := 'email = sys_context(''userenv'',''client_info'')'; only their own row
else
l_return := '1 = 2'; no rows
end if;
return l_return;
end;
/
The following 2 drop statements will drop the policies used in both
examples from this section to make sure we have a clean slate.
begin
dbms_rls.drop_policy (object_schema => 'DATA_OWNER',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEES_APEX_ITEM');
end;
/
begin
dbms_rls.drop_policy (object_schema => 'DATA_OWNER',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEES_CONTEXT');
end;
/
begin
dbms_rls.add_policy
(object_schema => 'DATA_OWNER',
object_name => 'EMPLOYEES',
policy_name => 'EMPLOYEES_CONTEXT',
policy_function => 'EMPLOYEES_CONTEXT_FN');
end;
/
APEX Components
Keeping in mind that each APEX page view is actually a new database session, you need to set this context with each page view APEX provides an application-level attribute designed for this exact purpose (shown in Figure 12-12), as it occurs very early in the APEX rendering code This is to ensure that the context is set before any application code that might need the context
is executed
Trang 6Fine-grained Auditing
The balance between security and productivity is often tough to find Too much security, and people have a hard time doing their jobs Too little, and you expose your system to a security breach In many cases, developers are tasked with securing an existing or packaged application, which may limit their ability to change the underlying code or architecture FGA is a great candidate for these applications because it’s a relatively transparent, bolt-on solution to add
a layer of defense to your data
FGA allows a developer to construct an audit policy in PL/SQL and then apply that policy
to database tables Policies have access to all database environment variables of the end user’s
session, such as SYS_CONTEXT variables These environment variables allow you to define what
the signature of an application user should look like, and then trigger audit events for parameters
that fall outside of that signature For example, a FGA policy might check MODULE, ACTION, IP
ADDRESS, and time of day If any of these parameters fall outside those defined by your policy,
the audit event is triggered
As its name implies, this feature is only a way to audit events as they occur, but it won’t actually prevent access to the data Consequently, FGA should be used in conjunction with other security measures that actually protect the data Think of FGA as the silent alarm of an Oracle database As a silent alarm, it provides an interesting deterrent to valid users of an application Unlike techniques that hide data or raise an error, the psychological effect of this invisible tripwire can leave employees with the impression that if they start snooping around where they shouldn’t, they could initiate an investigation into their actions Think about this in the context of a bank robbery If a thief sets off a loud siren at night, he’ll simply run away However, the fear of triggering a silent alarm and walking out of the bank with bags full of money to a waiting
crowd of police officers provides a completely different psychological deterrent
FGA and APEX
APEX is exceptionally well suited to FGA As discussed in Chapter 11, APEX sets the environment
variables MODULE and ACTION to the APEX application number and page number To get an
FIGURE 12-12 VPD application attribute
Trang 7idea of the environment variables available to you in an APEX session, navigate to the APEX SQL Workshop | SQL Commands section and run the following query (shown in Figure 12-13):
SELECT sys_context('USERENV', 'MODULE') module,
sys_context('USERENV', 'ACTION') action ,
sys_context('USERENV', 'CLIENT_IDENTIFIER') CLIENT_IDENTIFIER,
sys_context('USERENV', 'CLIENT_INFO') CLIENT_INFO,
v('APP_USER') app_user,
sys_context('USERENV', 'CURRENT_SCHEMA') CURRENT_SCHEMA,
sys_context('USERENV', 'SESSION_USER') SESSION_USER,
sys_context('USERENV', 'IP_ADDRESS') IP_ADDRESS
FROM dual;
In APEX version 3.2, the SQL Workshop is called Application 4500 and the SQL Commands feature is on page 1200 These numbers will obviously be different when you run the same query
in your actual application, but they provide a nice example since the APEX development
environment is also a collection of APEX applications Notice that CURRENT_SCHEMA is the
parsing schema for the query, as indicated by the select-list control in the upper-right corner of the page This variable represents the parsing schema of your application SESSION_USER will always
be the schema that the DAD uses to connect to the database Typically this is APEX_PUBLIC_
FIGURE 12-13 Context variable query in the SQL Workshop
Trang 8USER for environments using Oracle HTTP Server or ANONYMOUS for environments using the
Embedded PL/SQL Gateway The IP_ADDRESS environment variable is the IP address of the
client Most people think this will be the IP address of the end user’s machine, but in an APEX environment, the database client is actually the Oracle HTTP server This concept is useful in the context of a FGA policy designed to allow only APEX access to the data as the IP address should always be the same However, if someone connects to the database directly with a tool such as SQL*Plus, that session’s IP address will be the IP address of the person’s PC, not the HTTP server
FGA Example 1
Now that you have a good idea of what FGA is, let’s work through a few examples that get progressively more complex For the following examples, we’ll use two schemas: SEC_USER and SEC_ADMIN SEC_USER will own the objects we want to audit, and SEC_ADMIN will own the functions and policies used in our auditing examples Before we can create any policies, we must connect as a DBA and grant SEC_ADMIN execute privileges on the DBMS_FGA package While we’re there, lets also grant select on the sys.dba_fga_audit_trail view to SEC_ADMIN
$ sqlplus / as sysdba
SQL> grant execute on dbms_fga to SEC_ADMIN;
Grant succeeded.
SQL> grant select on sys.dba_fga_audit_trail to sec_admin;
Grant succeeded.
The first example uses a function called IS_APEX_SESSION_ONE that simply checks to make sure the SESSION_USER environment variable is either APEX_PUBLIC_USER or ANONYMOUS, the two most common schemas used for APEX sessions If the SESSION_USER is considered valid,
the function returns a 1 (one), or else it returns a 0 (zero) Connect as the SEC_ADMIN user and
create the following function:
create or replace function is_apex_session_one
return number
authid definer
as
begin
if sys_context('userenv','session_user') in ('ANONYMOUS','APEX_PUBLIC_USER') then return 1;
else
return 0;
end if;
end is_apex_session_one;
/
Right now, this function is just a traditional function and is in no way associated with a FGA policy or a table Execute the following code as the SEC_ADMIN user to create the policy that will tie the previous function to the table we want to audit:
begin
dbmS_FGA.add_policy
(object_schema => 'SEC_USER',
Trang 9policy_name => 'IS_FROM_APEX_POLICY',
audit_condition => 'SEC_ADMIN.IS_APEX_SESSION_ONE = 0',
audit_column => null,
statement_types => 'INSERT,UPDATE,DELETE,SELECT',
enable => true);
end;
/
In simple terms, an audit policy named IS_FROM_APEX_POLICY has been applied to the
SEC_USER.EMPLOYEES table This policy is enforced on all columns and all INSERT, UPDATE,
DELETE, and SELECT statements An audit event is triggered whenever the function IS_APEX_
SESSION_ONE returns a 0 (zero).
Since the goal of this policy is to audit any queries not originating from APEX, let’s first test this by querying the EMPLOYEES table from the APEX SQL Workshop The SQL Workshop is itself
an APEX application and thus falls within the allowed parameters of our policy
Executed in the Application Express SQL Workshop
select * from employees;
By querying the DBA_FGA_AUDIT_TRAIL, we can determine whether or not the previous query triggered an audit event:
SQL> select timestamp,db_user, client_id, object_schema,object_name,
policy_name, scn, sql_text from sys.dba_fga_audit_trail;
no rows selected
As expected, no audit event was logged Now, let’s run the same query once again from
SQL*Plus:
SQL> select * from employees;
Now connect as a privileged user and query the Audit Trail table:
SQL> select timestamp,db_user, policy_name, scn, sql_text from sys.dba_fga_audit_trail;
TIMESTAMP DB_USER POLICY_NAME SCN SQL_TEXT
- - - -
-08-MAR-09 SEC_USER IS_FROM_APEX_POLICY 4638186 select * from employees
FGA Example 2
For our next FGA example, we’ll leverage more session context information to narrow the
allowed parameters of the audit condition a bit This time, we will use MODULE to check that the query is coming from a specific APEX application We can also use the SYS_CONTEXT
function to determine the IP address of the client that issued the query In this case, the expected client is our HTTP server; any other IP address indicates the query might be coming from another client such as a SQL*Plus connection from an unauthorized workstation
create or replace
function is_apex_session_two
return number
authid definer
as
Trang 10if sys_context('userenv','session_user') in ('ANONYMOUS','APEX_PUBLIC_USER') and sys_context('userenv','module') = 'APEX:APPLICATION 123'
and sys_context('userenv','ip_address') = '192.168.1.123'
then
return 1;
else
return 0;
end if;
end is_apex_session_two;
/
Before creating a new audit policy to associate this function with a table, we will drop the policy created in the preceding example:
BEGIN
DBMS_FGA.DROP_POLICY
(object_schema => 'SEC_USER',
object_name => 'EMPLOYEES',
policy_name => 'IS_FROM_APEX_POLICY');
END;
/
Now we can create a new policy for this function:
begin
dbmS_FGA.add_policy
(object_schema => 'SEC_USER',
object_name => 'EMPLOYEES',
policy_name => 'IS_FROM_APEX_POLICY_TWO',
audit_condition => 'SEC_ADMIN.IS_APEX_SESSION_TWO = 0',
audit_column => null,
statement_types => 'INSERT,UPDATE,DELETE,SELECT',
enable => true);
end;
/
As you will recall, the SQL Workshop is Application 4500 and our policy is checking to make sure the query is coming from Application 123 Querying the EMPLOYEES table from the SQL Workshop should trigger an audit event:
Executed in the Application Express SQL Workshop
select * from employees where 1 = 1;
I added the predicate 1 = 1 to show that the full SQL text is captured in the audit trail Now query
the audit trail to verify that a new event was logged:
SQL> select timestamp,db_user, client_id, scn, sql_text from sys.dba_fga_audit_trail;
DB_USER CLIENT_ID SCN SQL_TEXT
- - -
-SEC_USER 4638186 select * from employees