Skip to Main Content
  • Questions
  • Database redaction with PL/SQL invoker rights

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Mark.

Asked: September 01, 2017 - 9:16 am UTC

Last updated: October 11, 2017 - 1:47 pm UTC

Version: 12.2.0.1

Viewed 1000+ times

You Asked

Dear Oracle Masters,

I am trying to create a proof of concept application architecture based upon the thick-database paradigm incorporating invoker rights, code based access control and redaction in Oracle 12cR2.

Background
===========

I have the following application schemas that make up the thick-database paradigm shell:

* A "DATA" schema that contains all quota consuming objects. This schema is locked upon normal application operation.

* A "CODE" schema that contains just PL/SQL package code that contains the business logic and accesses the data in the "DATA" schema. This schema is locked upon normal application operation.

* A "API" schema that contains PL/SQL packages that are APIs that can be called from outside of the database. Each package in this schema has a single subprogram designed to call a single subprogram in the "CODE" schema and take care of transaction control. This schema is locked upon normal application operation.

* A number of "USER" schemas that is a 1:1 with actual users of the application. These schemas own no objects and are used purely to call PL/SQL packages in the "API" schema. Each user will have their own execute grants to gain access to the PL/SQL packages in the "API" schema depending on their needs.

For each of the PL/SQL packages in the "API" schema, there is a dedicated role created that contains the necessary privileges for the code to be able to run successfully. This role is then granted to the package. When the user invokes the API package (if they have execute rights to do so), their privileges are elevated by CBAC to perform the desired work.

All of this works well whilst ensuring least privilege is maintained both at the user and API level.

My question
===========

If I wanted to introduce data redaction using DBMS_REDACT, how would this work with invoker rights?

I successfully created a simple redaction policy on a column called SALARY on a table called EMPLOYEES in the "DATA" schema. The policy expression enabled the policy for all schemas apart from the "DATA" and "CODE" schemas - this was done using the SYS_CONTEXT built-in function. The reason for the "CODE" schema being excluded from redaction was that some of the PL/SQL code within it may legitimately need to retrieve the SALARY information to perform some 3GL logic on it.

Given that the invoker of the PL/SQL package in the "API" schema is always the application user, the redaction policy would be active for the PL/SQL code running in the "API", "CODE" and "DATA" schemas which will cause bugs if the SALARY value is used in PL/SQL processing.

I appreciate that this is quite complex and I hope I have explained it in enough detail.

Many thanks in advance,
Mark.





and Chris said...

Perhaps I'm missing something. But I'm struggling to understand why you want to use invoker's rights with this method.

If you create your procedures with invoker's rights, they need select privileges on the base tables:

grant create session, create table, unlimited tablespace to 
  datau identified by datau;
grant create session, create procedure to 
  codeu identified by codeu;
grant create session, create procedure to 
  appu identified by appu;
  
create table datau.t (
  t_id          int not null primary key,
  email_address varchar2(320) not null
);

insert into datau.t values (1, 'chris@chris.com');

grant select on datau.t to codeu;

create or replace function codeu.def_rights ( id int ) 
  return datau.t.email_address%type
  authid definer as
  email datau.t.email_address%type;
begin
  
  select email_address into email
  from datau.t where t_id = id;

  return email;
  
end def_rights;
/

create or replace function codeu.inv_rights ( id int ) 
  return datau.t.email_address%type 
  authid current_user as
  email datau.t.email_address%type;
begin
  
  select email_address into email
  from datau.t where t_id = id;
    
  return email;
  
end inv_rights;
/

grant execute on codeu.inv_rights to appu;
grant execute on codeu.def_rights to appu;

conn appu/appu

select codeu.inv_rights(1) from dual ;

ORA-00942: table or view does not exist

select codeu.def_rights(1) from dual ; 

CODEU.DEF_RIGHTS(1)  
chris@chris.com  


Which kind of defeats the point of creating this hard shell!

If you can select from the tables, what's to stop people bypassing your code and querying them directly?

So why exactly do you want to use invoker's rights?

In any case, you're right: if you're using the SESSION_USER sys context to enable the redaction policy, then your code will see the redacted data. Not the original:

BEGIN
  DBMS_REDACT.add_policy(
    object_schema => 'datau',
    object_name   => 't',
    column_name   => 'email_address',
    policy_name   => 'redact_email_addr',
    function_type => dbms_redact.regexp,
    regexp_pattern => dbms_redact.re_pattern_email_address,
    regexp_replace_string => dbms_redact.RE_REDACT_EMAIL_ENTIRE,
    regexp_position => dbms_redact.re_beginning,
    regexp_occurrence => dbms_redact.re_all,
    expression    => q'|SYS_CONTEXT('USERENV','SESSION_USER') not in ('CODEU', 'DATAU')|'
  );
END;
/

create or replace function codeu.def_rights ( id int ) 
  return datau.t.email_address%type
  authid definer as
  email datau.t.email_address%type;
begin
  
  select email_address into email
  from datau.t where t_id = id;
  
  if email = 'chris@chris.com' then
    dbms_output.put_line('Hi Chris!');
  else
    dbms_output.put_line('Who are you?!');
  end if;

  return email;
  
end def_rights;
/

conn codeu/codeu

select def_rights(1) from dual ;

DEF_RIGHTS(1)    
chris@chris.com  

Hi Chris!

conn appu/appu

select codeu.def_rights(1) from dual ;

CODEU.DEF_RIGHTS(1)  
xxxx@xxxxx.com       

Who are you?!


You could look into pushing as much of your processing into SQL as possible. But only the join and where clauses. Data you manipulate in the select is still redacted:

create or replace function codeu.def_rights ( id int ) 
  return datau.t.email_address%type
  authid definer as
  email datau.t.email_address%type;
  output varchar2(20);
begin
  
  select email_address ,
         case email_address
           when 'chris@chris.com' then 'Hi Chris!'
           else 'Who are you?!'
         end
  into   email, output
  from   datau.t where t_id = id;
  
  dbms_output.put_line( output );

  return email;
  
end def_rights;
/

conn appu/appu

select codeu.def_rights(1) from dual ;

CODEU.DEF_RIGHTS(1)  
xxxx@xxxxx.com       

Who are you?!


To overcome this you could change your redaction policies to work on other userenv variaables, such as the action. Then set this as needed in your code:

create or replace function codeu.def_rights ( id int ) 
  return datau.t.email_address%type
  authid definer as
  plain_email    datau.t.email_address%type;
  redacted_email datau.t.email_address%type;
begin
  
  dbms_application_info.set_action('clear');
  select email_address into plain_email
  from datau.t where t_id = id;
  dbms_application_info.set_action('redact');
  
  select email_address into redacted_email
  from datau.t where t_id = id;
  
  if plain_email = 'chris@chris.com' then
    dbms_output.put_line('Hi Chris!');
  else
    dbms_output.put_line('Who are you?!');
  end if;

  return redacted_email;
  
end def_rights;
/

begin
  dbms_redact.alter_policy(
    'datau', 't', 'redact_email_addr',
    action      => dbms_redact.MODIFY_EXPRESSION,
    column_name => 'email_address',
    expression  => q'|SYS_CONTEXT('USERENV','ACTION') <> 'clear'|');
end;
/

conn appu/appu

select codeu.def_rights(1) from dual ;

CODEU.DEF_RIGHTS(1)  
xxxx@xxxxx.com       

Hi Chris!


But this can get really messy. Keeping track of which of the variables are redacted is tricky. And potentially doubles the number of your SQL queries.

It's better to separate your data processing and fetching code completely. Though I understand this brings its own complications...

Rating

  (1 rating)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Clarification on using invoker rights

Mark Hawker, September 21, 2017 - 12:22 pm UTC

Hi Chris,

I would like to clarify that I am using invoker rights in conjunction with code-based access control where object grants (via a dedicated db role) are granted directly to the package in the API schema not the user. This way, users have no privileges on the application tables until the API is invoked.

I doubt that will change your answer on redaction, but I wanted to clear that up. Is it possible to use a custom namespace with SYS_CONTEXT within a redaction policy?

Many thanks for supplying an answer,
Mark.
Chris Saxon
October 11, 2017 - 1:47 pm UTC

I see.

Yes, you can use custom namespaces with data redaction. Provided:

User-defined namespaces, but these must exist in the DBA_CONTEXT catalog view before the policy expression is created.

http://docs.oracle.com/database/122/ASOAG/configuring-oracle-data-redaction-policies.htm#GUID-9E82C480-45CC-46D4-A6B8-D2BE823B795B

More to Explore

Design

New to good database design? Check out Chris Saxon's full fundamentals class.