Date post: | 20-Jun-2015 |
Category: |
Technology |
Upload: | jeffrey-kemp |
View: | 1,252 times |
Download: | 1 times |
Apex and Virtual Private Database
Jeffrey Kemp
InSync Perth, Nov 2013
Why use VPD?
• Security
• Simplicity
• Flexibility
• No backdoors
Acronym Overload
• Virtual Private Database
• Row Level Security
• Fine-Grained Access Control
His
tory
8i VPD introduced; supports tables and views
9i 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
Requirements
• Enterprise Edition
• execute on DBMS_RLS
Disclaimer
not an expert
expertise
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"
eBud Data Model BUDGETS budget_id budget_owner budget_publicity
BUDGET_ENTRIES chart amount
COST_CENTRES cost_centre branch_code
USERS username role_list
Row-level security required
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');
Solution #2
V.P.D.
Image source: http://www.executiveinvestigationandsecurity.com/security/
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)
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;
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;
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;
Create an Application Context
CREATE CONTEXT EBUD_CTX USING VPD_PKG ACCESSED GLOBALLY;
Apex Setup 1. Authentication Scheme
2. (no step 2!)
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;
(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;
Create a Policy begin
DBMS_RLS.add_policy
( object_name => 'BUDGETS'
, policy_name => 'budgets_policy'
, policy_function => 'VPD_PKG.budgets_policy'
);
end;
/
Create a Policy begin
DBMS_RLS.add_policy
( object_name => 'BUDGETS'
, policy_name => 'budgets_policy'
, policy_function => 'VPD_PKG.budgets_policy'
, statement_types => 'SELECT'
);
end;
/
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)
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;
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;
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;
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;
Polic
y Fu
nct
ion
bo
dy
#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; END budgets_policy;
lots of different queries in shared pool
Hierarch
y Division
Directorate
Branch
Cost Centre
Cost Centre
Branch
Cost Centre
Directorate
Branch
Cost Centre
Cost Centre
"Co
st C
entr
e G
rou
ps"
eBud Data Model BUDGETS budget_id budget_owner budget_publicity
COST_CENTRES cost_centre branch_code
USER_COST_CENTRES USERS username role_list
USER_COST_CENTRE_GROUPS group_code
COST_CENTRE_GROUPS parent_group_code
hierarchy
Cost Centre Policy
Function
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;
END cost_centre_policy;
we can refer to the table via its alias
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
But…
The predicate may query another table that itself has an RLS policy.
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;
Policy Type parameter (10g+)
Re-Executed for each for all
statement DYNAMIC (default)
object STATIC SHARED_STATIC
context CONTEXT_SENSITIVE SHARED_CONTEXT_SENSITIVE
If in doubt, always start with the default - DYNAMIC
consider SHARED_... if your policy function is shared amongs multiple tables
The policy type parameter is just for performance optimisation.
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
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;
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
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';
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
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
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
Thank you jeffkemponoracle.com
Image source: http://www.toothpastefordinner.com/index.php?date=082609