This document discusses using Virtual Private Database (VPD) to implement row-level security in an Oracle database. It begins with an overview of VPD and its history. It then presents a case study of implementing VPD for an eBud budgeting application to restrict data access based on user roles. The solution uses application contexts and policy functions to dynamically modify SQL statements. Various enhancements to the policy functions over time improve flexibility and performance. Recommendations are provided for best practices in developing and optimizing VPD policies.
5. VPD introduced; supports tables and views
9i
History
8i
global application contexts
support for synonyms
policy groups
10g
column-level privacy
column masking
static policies
shared policies
11g
integrated into Enterprise Manager
12c
improved security for expdp
fine-grained context-sensitive policies
9. Case Study: eBud
• Budgeting solution for a large government
department
• Groups of users: “Super Admins”, “Finance”,
“Managers”
• Super Admin: "access all areas"
• Finance: "access to most areas"
• Managers: "limited access"
11. Solution #1
Query:
SELECT budget_id, name
FROM
budgets_vw
WHERE budget_id = :b1;
View:
CREATE VIEW budgets_vw AS
SELECT *
FROM
budgets
WHERE budget_owner = v('APP_USER');
13. Row Level Security
The query you asked for:
SELECT budget_id, name FROM budgets
WHERE budget_id = :b1;
What we executed:
SELECT budget_id, name FROM budgets
WHERE budget_id = :b1
AND budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER');
(not exactly, but this gives the general idea)
14. Package spec
PACKAGE vpd_pkg IS
PROCEDURE new_session;
FUNCTION budgets_policy
( object_schema IN VARCHAR2
, object_name
IN VARCHAR2
) RETURN VARCHAR2;
END vpd_pkg;
15. Initialise an Apex Session
PROCEDURE new_session IS
BEGIN
set_context('APP_USER', v('APP_USER'));
set_context('SUPERADMIN', is_superadmin);
set_context('FINANCE', is_finance_user);
END new_session;
16. Set Context
PROCEDURE set_context
( i_attr IN VARCHAR2
, i_value IN VARCHAR2
) IS
BEGIN
DBMS_SESSION.set_context
( namespace => 'EBUD_CTX'
, attribute => i_attr
, value
=> i_value
, client_id => v('APP_USER') || ':' || v('SESSION')
);
END set_context;
17. Create an Application Context
CREATE CONTEXT EBUD_CTX
USING VPD_PKG
ACCESSED GLOBALLY;
20. Policy Function body #1
FUNCTION budgets_policy
( object_schema IN VARCHAR2
, object_name
IN VARCHAR2
) RETURN VARCHAR2 IS
BEGIN
RETURN q'[
budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
]';
END budgets_policy;
21. (old quote syntax)
FUNCTION budgets_policy
( object_schema IN VARCHAR2
, object_name
IN VARCHAR2
) RETURN VARCHAR2 IS
BEGIN
RETURN '
budget_owner = SYS_CONTEXT(''EBUD_CTX'',''APP_USER'')
';
END budgets_policy;
24. DBMS_RLS.add_policy
•
•
•
•
•
•
object_schema (NULL for current user)
object_name (table or view)
policy_name
function_schema (NULL for current user)
policy_function
statement_types
(default is SELECT, INSERT, UPDATE, DELETE)
• policy_type
• (other optional parameters)
25. How it works
Query:
SELECT budget_id, name FROM budgets
WHERE budget_id = :b1;
Parser calls function:
budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
Executed:
SELECT budget_id, name FROM
( SELECT * FROM budgets budgets
WHERE budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
)
WHERE budget_id = :b1;
26. Policy Function body #2
FUNCTION budgets_policy
(object_schema IN VARCHAR2
,object_name
IN VARCHAR2
) RETURN VARCHAR2 IS
BEGIN
RETURN q'[
budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
OR budget_publicity = 'PUBLIC'
]';
END budgets_policy;
27. Policy Function body #3
FUNCTION budgets_policy
(object_schema IN VARCHAR2
,object_name
IN VARCHAR2
) RETURN VARCHAR2 IS
BEGIN
RETURN q'[
budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
OR budget_publicity = 'PUBLIC'
OR (budget_publicity = 'FINANCE'
AND SYS_CONTEXT('EBUD_CTX','FINANCE') = 'Y')
OR SYS_CONTEXT('EBUD_CTX','SUPERADMIN') = 'Y'
]';
END budgets_policy;
28. Policy Function body #4
FUNCTION budgets_policy
(object_schema IN VARCHAR2
,object_name
IN VARCHAR2
) RETURN VARCHAR2 IS
o_predicate VARCHAR2(4000);
BEGIN
IF SYS_CONTEXT('EBUD_CTX','SUPERADMIN') = 'Y' THEN
o_predicate := '';
ELSE
o_predicate := q'[
budget_publicity = 'PUBLIC'
OR (budget_publicity = 'FINANCE'
AND SYS_CONTEXT('EBUD_CTX','FINANCE') = 'Y')
OR budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
]';
END IF;
RETURN o_predicate;
END budgets_policy;
29. Policy Function body #5
FUNCTION budgets_policy
(object_schema IN VARCHAR2
,object_name
IN VARCHAR2
) RETURN VARCHAR2 IS
o_predicate VARCHAR2(4000);
BEGIN
IF SYS_CONTEXT('EBUD_CTX','SUPERADMIN') = 'Y' THEN
o_predicate := '';
ELSIF SYS_CONTEXT('EBUD_CTX','FINANCE') = 'Y' THEN
o_predicate := q'[
budget_publicity IN ('PUBLIC','FINANCE')
OR budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
]';
ELSE
o_predicate := q'[
budget_publicity = 'PUBLIC'
OR budget_owner = SYS_CONTEXT('EBUD_CTX','APP_USER')
]';
END IF;
RETURN o_predicate;
lots of different queries in shared pool
END budgets_policy;
32. FUNCTION cost_centre_policy (object_schema IN VARCHAR2, object_name IN VARCHAR2) RETURN VARCHAR2 IS
BEGIN
IF SYS_CONTEXT('EBUD_CTX','FINANCE') = 'Y' THEN
RETURN '';
ELSE
RETURN q'[
EXISTS (
SELECT null
FROM
user_cost_centres ucc
WHERE ucc.username = SYS_CONTEXT('EBUD_CTX','APP_USER')
AND
ucc.cost_centre = cost_centres.cost_centre
)
OR EXISTS (
SELECT null
FROM
all_budget_branches_vw b
JOIN
user_cost_centre_groups uccg
ON
uccg.group_code IN
(b.branch_code, b.directorate_code, b.division_code)
WHERE uccg.username = SYS_CONTEXT('EBUD_CTX','APP_USER')
AND
b.budget_id = cost_centres.budget_id
AND
b.branch_code = cost_centres.branch_code
)
]';
END IF;
we can refer to the table via its alias
END cost_centre_policy;
Cost
Centre
Policy
Function
33. Warning
Predicate MUST NOT
query the table to which
it is meant to be applied
- not even via a view
Image source: http://en.wikipedia.org/wiki/Drawing_Hands
35. Budget Entry Policy Function
FUNCTION budget_entry_policy (object_schema IN VARCHAR2, object_name IN VARCHAR2)
RETURN VARCHAR2 IS
BEGIN
IF SYS_CONTEXT('EBUD_CTX','FINANCE') = 'Y' THEN
RETURN '';
ELSE
RETURN q'[
EXISTS (
SELECT null
FROM
cost_centres cc
WHERE cc.cost_centre = budget_entries.cost_centre
AND
cc.budget_id = budget_entries.budget_id
)
]';
END IF;
END budget_entry_policy;
36. Policy Type parameter (10g+)
Re-Executed
statement
for each
for all
DYNAMIC (default)
object
STATIC
SHARED_STATIC
context
CONTEXT_SENSITIVE
SHARED_CONTEXT_SENSITIVE
consider SHARED_... if your policy function
is shared amongs multiple tables
If in doubt, always start with the default - DYNAMIC
The policy type parameter is just for performance optimisation.
37. Improved in 12c
Fine-grained Context Sensitive policies
– new parameters for DBMS_RLS.add_policy:
namespace and attribute
– new procedure DBMS_RLS.add_policy_context
– improved performance
38. Bypassing VPD
• Not enforced for DIRECT path export
• Grant EXEMPT ACCESS POLICY
• Return NULL for object owner:
IF object_schema = USER THEN
RETURN '';
END IF;
39. Errors
• ORA-28112: failed to execute policy function
– the policy function raised an exception
• "Invalid SQL statement"
– may be a syntax error in the generated SQL
• ORA-28115: policy with check option violation
– policy has been applied to Insert, Update or Delete operations
• ORA-28133: full table access is restricted by fine-grained
security
– policy has been applied to Index operation
40. Tuning
• Set client_identifier to APP_USER:SESSION then
call the policy function
• or, query v$vpd_policy to get the predicate(s)
applied to the query
• or, get the final exact SQL statement from the
trace file
ALTER SESSION SET EVENTS '10730 trace name context
forever, level 12';
41. Recommendations
• Use q'{ syntax for predicates }'
• Understand how Apex Sessions work
• Use context for variables
– avoid injecting literals
– avoid calls to v() etc.
• Keep predicates simple
42. More Information
Read the Oracle Docs for:
– using policy groups
– automated policy creation in DDL triggers
– integration with Oracle Label Security
– data dictionary views
– Oracle Data Redaction
43. Oracle Docs
Oracle Database Security Guide:
Using Oracle Virtual Private Database to
Control Data Access http://bit.ly/16Iq5EQ
Oracle Database PL/SQL Packages and Types Reference:
DBMS_RLS
http://bit.ly/1abI46V