Synopsis. Oracle 10gR2 enhances and expands data security with new row-level security features that ensure a user can only view, add, or modify data based on specific virtual private database (VPD) rulesets. This article – the second in this series – discusses how these new features improve upon those in prior releases and demonstrates how to implement VPD in any Oracle 10gR2 database.
To continue the scenario from the previous article in this series, imagine that your CIO calls you into yet another clandestine meeting with the Director of Accounting at your company a few months after the first confidential gathering. “Good news!” says the Director. “The audit trails that you put in place with that FGA thing you talked about helped us catch our thief. We caught her red-handed when she created a bunch of fake credit memos for a real vendor. She admitted she’d updated the vendor’s credit card number to match her own card number and then issued several credit memos totaling just under $100,000.00. Your audit reports were absolutely essential evidence to our case. Well done!” Your CIO beams at you as well.
But now the Director’s brow once again furrows. “And that got me to thinking … how do we prevent this in the future? Even though we’re tightening our background check procedures, there’s nothing to stop anyone devious enough from doing this again, is there?” Your CIO chimes in, “Well, not without rewriting a lot of our custom application software for your team. Unless …” They both turn and look at you for another miracle. Yet again, Oracle 10g’s robust security features come to your rescue. You explain to the Director and CIO your plans to tighten database security without having to change any application code … and once again, there are smiles on their faces. “Outstanding! “ says your CIO. “I think we’ll keep you around.”
Virtual Private Database: An Overview
In the previous article, I demonstrated how to determine which users are accessing which data elements in an Oracle 10gR2 database with Fine-Grained Auditing (FGA), an extremely powerful set of features for tracking questionable or fraudulent transactions. While FGA can certainly assist me in tracking down security violations after they have happened, as the old proverb says, the horse has already left the barn, and I’m just closing the gate behind it. What I’d really like to do is prevent an application or user session from accessing and modifying sensitive data.
Some shops I’ve worked in have implemented complex security rules using specially constructed views to limit access to their database’s critical tables. While this is certainly a noble and elegant solution, it is also extremely time-consuming to plan, develop, test, and implement. Also, view-based security does have a significant disadvantage: If a user is sophisticated and curious (or malicious!) enough, somehow he will find a way to display the view and the restrictions it places upon the underlying base table(s). Ideally, I’d like to make sure that a user isn’t even aware that he’s been prohibited from accessing the data in the first place, as that may help quell any curiosity about where the data is stored in the database system
The introduction of the DBMS_RLS package in Oracle 9i offered an excellent alternative to the custom-written view implementation of security. As its name implies, DBMS_RLS allows a DBA to enforce row level security against specific tables in the database. Whenever a row is read, added, modified or deleted, Oracle applies fine grained access control (FGAC) rules that insure the row’s values met the strictures of that predefined security policy.
The security policy enforces these restrictions by adding a hidden predicate to each query or DML statement that attempts to access the data. For example, if a query attempts to access a row, and the security policy determined that the user had insufficient permission to access it, then Oracle filtered the row from the query’s result set. On the other hand, if a DML operation attempted to process the row, and the security policy showed that the user was limited from accessing the row, Oracle blocked the operation against the row. (I’ll explain more about how these predicates are constructed and applied to data when I demonstrate how to build application security context functions later in this article.)
However, the implementation of DBMS_RLS in releases prior to Oracle 10gR2 still had some major drawbacks:
Limited Scope Control. Fine grained access control rules could only be applied to one table at a time. If I needed to secure multiple tables, I had to create a separate security policy for each table. Also, the security policy lacked column-level specificity. For example, if I was only concerned with applying security rules against the CREDIT_LIMIT column of the AP.VENDORS table, I was forced to apply the security policy to the entire table regardless.
Finally, fine grained access control rules could not be applied directly against a view to secure it. For example, if I needed to restrict access to the AP.RV_INVOICE_DETAILS view, I would be forced to create a separate security policy for the AP.VENDORS and AP.INVOICES tables that underlie that view.
Repetitive Execution. In prior releases, Oracle was forced to evaluate the hidden predicate every time a user accessed the secured table. This meant that the predicate tended to be executed extremely frequently – even when the predicate didn’t change. For example, if I needed to restrict a user session from querying any entries in the AP.VENDORS table whose value for CREDIT_LIMIT exceeded $5000, and that credit limit restriction never changed for the duration of that user session, Oracle still parsed the predicate each time the query was executed. If that query was part of an OLTP application and tended to be evaluated hundreds of times per minute, a lot of parse time was essentially wasted.
Limited Predicate Size. Depending upon how complex the security restrictions needed to be applied to a query or DML operation, I often heard complaints from users because their session received an ORA-03113 error when Oracle attempted (and failed!) to parse a correspondingly complex predicate. Indeed, I’ve seen predicates that easily exceeded the maximum size of 4K.
Upgraded FGAC Security Features in Oracle 10gR2
The good news is that Oracle 10gR2 overcomes all of these limitations:
- A single security policy can now enforce restrictions on multiple tables. In addition, data that’s accessed via indexes and synonyms can be secured as well.
- Similar to this release’s enhancements for DBMS_FGA, DBMS_RLS can now define FGAC business rules that are evaluated only when one or more specific columns are accessed.
- Also, a security policy can be applied directly to a view without having to define policies to secure the underlying table(s) for that view.
- The maximum size of a security policy’s predicate has been expanded eightfold to 32K.
The most impressive set of enhancements, however, encompass the ability to tell Oracle how often a predicate should be evaluated. In the original release of DBMS_RLS, the predicate had to be evaluated every time the policy was enforced. This original mode is retained as the DYNAMIC policy type in Oracle 10gR2. Two additional security policy types are also available to control how often a predicate needs to be parsed: CONTEXT_SENSITIVE and STATIC.
Context-Sensitive Policies. The underlying security policy function for a CONTEXT_SENSITIVE security policy will only be executed under two conditions: when either the SQL statement is first parsed, or when the SQL statement is being executed and Oracle 10gR2 detects that the local application context has changed since the last time it was executed. This tends to eliminate an enormous amount of unnecessary statement parsing, especially when the predicate will not change dramatically between invocations of the statement.
Static Policies. Unlike a CONTEXT_SENSITIVE policy, the predicate of a STATIC policy is only evaluated when a user session is initiated, and it will never be re-evaluated as long as the session persists because the predicate is simply cached in the SGA. This type of policy is useful for predicates that will simply not change for the duration of the session. For example, if I need to restrict access to all deactivated entries in the AP.VENDORS table, the predicate that handles that limitation would be ACTIVE_IND <> ‘N’. This predicate would never need to change within the session; therefore, Oracle 10gR2 will parse this predicate only once, store it in the library cache, and never re-evaluate it. This obviously saves even more significant parsing time than does a CONTEXT_SENSITIVE security policy.
Shared Policies. In addition, Oracle 10gR2 offers a shared version of each of these policy types when it would be advantageous to apply the same security privileges across multiple database objects. These two policy types are SHARED_CONTEXT_SENSITIVE and SHARED_STATIC, and they work identically as their non-shared security policy counterparts.
Oracle recommends testing a VPD policy in DYNAMIC mode before attempting to activate the policy in either CONTEXT_SENSITIVE or STATIC mode. I heartily concur with this suggestion! In my experience, it helped me to alleviate a lot of frustration while still learning how to best use the different VPD policy types effectively.
Putting It All Together: A Demonstration
Now that I’ve hopefully explained the philosophy and theory behind VPD, it’s time to demonstrate its effectiveness. I’ll use the same Accounts Payable schema objects I created in the prior article, and I’ll apply the following business rules to the corresponding data using VPD security policies for three different types of users:
Accounts Payable Clerk (APCLERK)
- An Accounts Payable Clerk may not view a Vendor’s credit card number if the Vendor has a credit limit of $25,000 or above.
- An Accounts Payable Clerk is not permitted to create a new Vendor with a credit limit of $25,000 or above.
- An Accounts Payable Clerk is not permitted to upgrade the credit limit of an existing Vendor to above $25,000.
- An Accounts Payable Clerk is not permitted to access any Invoice marked as a Credit Memo.
- An Accounts Payable Clerk cannot change any Invoice from its normal status into a Credit Memo.
Accounts Payable Team Lead (APTLEAD)
- An Accounts Payable Team Lead may not view a Vendor’s credit card number if the Vendor has a credit limit of $150,000 or above.
- An Accounts Payable Team Lead is not permitted to create a new Vendor with a credit limit of $150,000 or above.
- An Accounts Payable Team Lead is not permitted to upgrade the credit limit of an existing Vendor to above $150,000.
- An Accounts Payable Team Lead, however, is not restricted from accessing any Invoice marked as a Credit Memo, and the Lead can also change a normal Invoice into a Credit Memo.
Accounts Payable Director (APDIR)
- The Accounts Payable Director has complete rights to change any data in the AP system.
Finally, what if a user falls into none of these groups? To play it extra-safe, I’ll make sure that my application security context package will construct and apply the appropriate predicates to keep an unapproved user session from seeing any credit card or credit limit information about a Vendor.
First, I’ll create three new users, APCLERK, APTLEAD, and APDIR, and assign them the appropriate system privileges, including the APP_SECURED role that was established in the prior article, as shown in Listing 2.1.
Next, I’ll construct a special package, AP.APP_SECURITY_CONTEXT, that implements the business rules described previously. (See Listing 2.2 for the code for this package.) The package comprises one procedure, SETUSERINFO, and two functions, CREDIT_LIMIT and CREDIT_MEMO. The procedure will be used to assign the appropriate values for each different type of user accessing the data in the Accounts Payable system, and the two functions will build the actual predicates to enforce the Accounts Payable security policies.
Listing 2.3 shows how to create a CONTEXT object in Oracle 10gR2. This context, VPD_CONTEXT, will provide a container for values assigned whenever a user session is established, and the AP.APP_SECURITY_CONTEXT package is defined as the only way to modify data inside the context. VPD_CONTEXT will be populated via the AP.ON_LOGON trigger whenever a new user session is established via a call to the SETUSERINFO procedure.
The code in Listing 2.4 establishes four security policies for the Accounts Payable system:
- Policy AP_CREDIT_CARD ensures that Vendor credit card numbers cannot be viewed during a SELECT operation if the Vendor’s credit limit is greater than the one specified for the user session and either the CREDIT_CARD or CREDIT_LIMIT columns are included in the query. The AP.APP_SECURITY_CONTEXT package’s CREDIT_LIMIT function is called to enforce this business rule.
- Policy AP_CREDIT_LIMIT ensures that a Vendor entry cannot be viewed, created, or updated if the Vendor’s credit limit is greater than the one specified for the user session. This policy is also enforced by the CREDIT_LIMIT function.
- Policy AP_CREDIT_MEMO enforces a restriction on creation of a new Credit Memo, and it’s enforced by the CREDIT_MEMO function. This policy also ensures that an existing Invoice can’t be transformed into a Credit Memo (i.e. by changing the Invoice’s value for INVOICE_TYPE from ‘C’ to ‘D’).
- Finally, policy AP_RPTG_READONLY ensures that no user accessing the reporting view named AP.RV_INVOICE_DETAILS can see any Vendor, Invoice, or Invoice Detail information for a Vendor whose credit limit is greater than the one specified for the user session. Again, the CREDIT_LIMIT function is called to enforce this business rule. Note that since all other users besides AP system users are sent a value of zero for their credit limit, this policy effectively limits those users from seeing any data if either the CREDIT_CARD or CREDIT_LIMIT columns are included in the query.
All the pieces are now in place, so it’s time to test the policies. In Listing 2.5 I’ve listed several queries that should prove if these security policies are working properly for SELECT statements, and in Listing 2.6 I’ve set up similar tests for INSERTs and UPDATEs.
To prove out my security policies, I first logged in as the AP Director user (APDIR) and executed all DML statements in these two sets of unit tests. As expected, the queries retrieved all data, and the DML statements completed successfully. Since this user should have full access to all data in the AP schema, this test turned out as expected. Listing 2.7 shows the results from the queries for the sake of later comparisons.
Next, I logged in as the AP Team Lead user (APTLEAD) and ran the same set of queries. Since there are no Vendors with a credit limit above $150,000, the results for all queries matched those in Listing 2.7, which was as expected. However, when I attempted to add a new Vendor entry with a credit limit above $150,000, or when I attempted to update an existing Vendor entry so that its credit limit exceeded $150,000, the VPD policy was triggered, and as expected, both of the operations were rejected. As shown in Listing 2.8, Oracle 10g raised an exception and returned an ORA-28115,Policy With Check Option Violation error message.
So far, so good! Now for the final set of unit tests. When I ran the same queries against the AP data for the AP Clerk user (APCLERK), I noticed significant differences. As I expected, data was returned only for those Vendors whose credit limit didn’t exceed $25,000. Moreover, when I attempted to add a new Vendor entry with a credit limit above $25,000, the VPD policy was triggered. However, note that for an attempted update of a Vendor whose credit limit already exceeds $25,000, the VPD policy still fires, but the predicate that’s generated simply prevents the DML from executing against any rows. These results are shown in Listing 2.9.
Even though Oracle 9i introduced the concept of Virtual Private Database (VPD), it took several enhancements in Oracle 10gR2 to improve significantly its breadth and flexibility. VPD now offers extremely granular row-level security enforcement via fine grained auditing control (FGAC), limited parsing of commonly-utilized predicates per each user session, and the creation of much larger, more complex predicates for security enforcement.
References and Additional Reading
Even though I’ve hopefully provided enough technical information in this article to encourage you to explore with these features, I also strongly suggest that you first review the corresponding detailed Oracle documentation before proceeding with any experiments. Actual implementation of these features should commence only after a crystal-clear understanding exists. Please note that I’ve drawn upon the following Oracle 10gR2 documentation for the deeper technical details of this article:
B14214-01 Oracle Database New Features Guide
B14231-01 Oracle Database Administrator’s Guide
B14258-01 PL/SQL Packages and Types Reference
B14266-01 Oracle Database Security Guide