Apex and Virtual Private Database
Upcoming SlideShare
Loading in...5
×
 

Apex and Virtual Private Database

on

  • 1,087 views

Oracle Application Express and Oracle Row-Level Security (RLS) (aka Virtual Private Database) work very well together. Using RLS you can have one database serve different groups of users while ...

Oracle Application Express and Oracle Row-Level Security (RLS) (aka Virtual Private Database) work very well together. Using RLS you can have one database serve different groups of users while virtually guaranteeing that no-one will be able to view or update data they aren't supposed to. That was the sales pitch. This presentation will be a case study on one small Apex application with complex security requirements. The author started with a complex solution using views which performed poorly and didn't satisfy all the user's requirements. When he switched to a solution using RLS, the application became significantly simpler and faster, and all user requirements were met.

Statistics

Views

Total Views
1,087
Views on SlideShare
1,083
Embed Views
4

Actions

Likes
1
Downloads
13
Comments
0

2 Embeds 4

http://jeffkemponoracle.com 3
http://www.linkedin.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Apex and Virtual Private Database Apex and Virtual Private Database Presentation Transcript

  • 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
  • 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
  • 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 COST_CENTRES cost_centre branch_code BUDGET_ENTRIES chart amount 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 , policy_name , policy_function , statement_types ); end; / => => => => 'BUDGETS' 'budgets_policy' 'VPD_PKG.budgets_policy' 'SELECT'
  • 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;
  • 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;
  • Directorate Branch Cost Centre Directorate Branch Cost Centre Cost Centre Branch Cost Centre Cost Centre Hierarchy "Cost Centre Groups" Division
  • eBud Data Model BUDGETS budget_id budget_owner budget_publicity USER_COST_CENTRES COST_CENTRES cost_centre branch_code USERS username role_list COST_CENTRE_GROUPS parent_group_code USER_COST_CENTRE_GROUPS group_code hierarchy
  • 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
  • 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 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.
  • 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