Saturday, December 28, 2013

Oracle Data Redaction 12c

Oracle Data Redaction provides functionality to mask (redact) data that is returned from user SELECT queries. The masking takes place in real time. The Data Redaction policy applies to the querying user, depending on this user's SYS_CONTEXT values. Oracle Database redacts only the data for the rows specified by the user's query, not the data for the entire column. The redaction takes place immediately before the data is returned to the querying user or application.

To set up a demonstration, uses the setup.sql script, then uses the code shown in below listing to set up a redaction policy on the EMP table.

Setup.sql
create table emp
(  empno number,
   ename varchar2(20),
   salary number,
   hire_date date,
   email_id varchar2(30) ) ;
 
insert into emp(empno,ename,salary,hire_date,email_id)
values(1,'John',1013,sysdate,'John@example.com');
insert into emp(empno,ename,salary,hire_date,email_id)
values(2,'Miller',1578,sysdate-2,'miller@example.com');
insert into emp(empno,ename,salary,hire_date,email_id)
values(3,'Kim',1398,sysdate-3,'Kim@example.com');
commit;

rajesh@PDB1> select * from emp;

     EMPNO ENAME                    SALARY HIRE_DATE               EMAIL_ID
---------- -------------------- ---------- ----------------------- ----------------------
         1 John                       1013 28-DEC-2013 04:10:34 PM John@example.com
         2 Miller                     1578 26-DEC-2013 04:10:34 PM miller@example.com
         3 Kim                        1398 25-DEC-2013 04:10:34 PM Kim@example.com

3 rows selected.

rajesh@PDB1>

When the owner of the table "EMP" selects data, the results displays the values intact - but when any other user selects data from the table, the data must be masked.

Column
Description of redaction
Salary
replace with some random numbers and don't show actual values
Hire_date
show only the day and month year should be replaced with 1990
Email_id
Keep the domain name and rest others should be masked


rajesh@PDB1> grant select on emp to scott;

Grant succeeded.

rajesh@PDB1> begin
  2     dbms_redact.add_policy
  3     ( object_schema=>user,
  4       object_name=>'EMP',
  5       policy_name=>'EMP_REDACT',
  6       column_name=>'SALARY',
  7       function_type=>dbms_redact.random,
  8       expression=> q'|sys_context('userenv','current_user')!= 'RAJESH'|'
  9      );
 10  end;
 11  /

PL/SQL procedure successfully completed.

rajesh@PDB1>
rajesh@PDB1> begin
  2     dbms_redact.alter_policy
  3     ( object_schema=> user,
  4       object_name=>'EMP',
  5       policy_name=>'EMP_REDACT',
  6       action=>dbms_redact.add_column,
  7       column_name=>'HIRE_DATE',
  8       function_type=>dbms_redact.partial,
  9       function_parameters=>'MDy1990' );
 10  end;
 11  /

PL/SQL procedure successfully completed.

rajesh@PDB1> begin
  2     dbms_redact.alter_policy
  3     ( object_schema=> user,
  4       object_name=>'EMP',
  5       policy_name=>'EMP_REDACT',
  6       action=>dbms_redact.add_column,
  7       column_name=>'EMAIL_ID',
  8       function_type=>dbms_redact.regexp,
  9       regexp_pattern=>dbms_redact.RE_PATTERN_EMAIL_ADDRESS,
 10       regexp_replace_string=>dbms_redact.RE_REDACT_EMAIL_NAME,
 11       regexp_match_parameter=>dbms_redact.RE_CASE_INSENSITIVE );
 12  end;
 13  /

PL/SQL procedure successfully completed.

rajesh@PDB1>
rajesh@PDB1> select * from emp ;

     EMPNO ENAME          SALARY HIRE_DATE               EMAIL_ID
---------- ---------- ---------- ----------------------- --------------------
         1 John             1013 28-DEC-2013 04:10:34 PM John@example.com
         2 Miller           1578 26-DEC-2013 04:10:34 PM miller@example.com
         3 Kim              1398 25-DEC-2013 04:10:34 PM Kim@example.com

3 rows selected.

rajesh@PDB1>


When connected from Scott schema the redacted data look like this.

scott@PDB1> select * from rajesh.emp ;

     EMPNO ENAME                    SALARY HIRE_DATE               EMAIL_ID
---------- -------------------- ---------- ----------------------- -------------------
         1 John                        796 28-DEC-1990 04:10:34 PM xxxx@example.com
         2 Miller                      694 26-DEC-1990 04:10:34 PM xxxx@example.com
         3 Kim                          79 25-DEC-1990 04:10:34 PM xxxx@example.com

3 rows selected.

scott@PDB1> 

Information's on redaction policies and policy columns are maintained in REDACTION_POLICIES and REDACTION_COLUMNS data dictionary.

rajesh@PDB1> @printtbl ' select * from redaction_policies '
OBJECT_OWNER                  : "RAJESH"
OBJECT_NAME                   : "EMP"
POLICY_NAME                   : "EMP_REDACT"
EXPRESSION                    : "sys_context('userenv','current_user')!= 'RAJESH'"
ENABLE                        : "YES    "
POLICY_DESCRIPTION            : ""
-----------------

PL/SQL procedure successfully completed.

rajesh@PDB1> 
rajesh@PDB1>
rajesh@PDB1>
rajesh@PDB1> @printtbl ' select * from redaction_columns '
OBJECT_OWNER                  : "RAJESH"
OBJECT_NAME                   : "EMP"
COLUMN_NAME                   : "SALARY"
FUNCTION_TYPE                 : "RANDOM REDACTION"
FUNCTION_PARAMETERS           : ""
REGEXP_PATTERN                : ""
REGEXP_REPLACE_STRING         : ""
REGEXP_POSITION               : "0"
REGEXP_OCCURRENCE             : "0"
REGEXP_MATCH_PARAMETER        : ""
COLUMN_DESCRIPTION            : ""
-----------------
OBJECT_OWNER                  : "RAJESH"
OBJECT_NAME                   : "EMP"
COLUMN_NAME                   : "EMAIL_ID"
FUNCTION_TYPE                 : "REGEXP REDACTION"
FUNCTION_PARAMETERS           : ""
REGEXP_PATTERN                : "([A-Za-z0-9._%+-]+)@([A-Za-z0-9.-]+\.[A-Za-z]{2,4})"
REGEXP_REPLACE_STRING         : "xxxx@\2"
REGEXP_POSITION               : "1"
REGEXP_OCCURRENCE             : "0"
REGEXP_MATCH_PARAMETER        : "i"
COLUMN_DESCRIPTION            : ""
-----------------
OBJECT_OWNER                  : "RAJESH"
OBJECT_NAME                   : "EMP"
COLUMN_NAME                   : "HIRE_DATE"
FUNCTION_TYPE                 : "PARTIAL REDACTION"
FUNCTION_PARAMETERS           : "MDy1990"
REGEXP_PATTERN                : ""
REGEXP_REPLACE_STRING         : ""
REGEXP_POSITION               : "0"
REGEXP_OCCURRENCE             : "0"
REGEXP_MATCH_PARAMETER        : ""
COLUMN_DESCRIPTION            : ""
-----------------

PL/SQL procedure successfully completed.

rajesh@PDB1> 

However If the querying user has the EXEMPT REDACTION POLICY system privilege, redaction will not be performed.


rajesh@PDB1> connect sys/oracle@pdb1 as sysdba
Connected.
sys@PDB1> grant exempt redaction policy to scott;

Grant succeeded.

sys@PDB1> set timing off
sys@PDB1> 
sys@PDB1> 
sys@PDB1> grant exempt redaction policy to scott;

Grant succeeded.

sys@PDB1> connect scott/tiger@pdb1
Connected.
scott@PDB1> 
scott@PDB1> select * from session_privs ;

PRIVILEGE
----------------------------------------
CREATE SESSION
EXEMPT REDACTION POLICY

2 rows selected.

scott@PDB1> 
scott@PDB1> select * from rajesh.emp ;

     EMPNO ENAME                    SALARY HIRE_DATE               EMAIL_ID
---------- -------------------- ---------- ----------------------- -------------------
         1 John                       1013 28-DEC-2013 04:10:34 PM John@example.com
         2 Miller                     1578 26-DEC-2013 04:10:34 PM miller@example.com
         3 Kim                        1398 25-DEC-2013 04:10:34 PM Kim@example.com

3 rows selected.

scott@PDB1> 

13 comments:

  1. Hi Rajesh,

    Can we create a script to create the policy for first table-first colmn and then alter policy for first table-second column?
    Like

    if exists ( select object_name from redaction_policies where object_owner='RAJESH' and object_name=upper('&1'))
    select distinct 'BEGIN DBMS_REDACT.ALTER_POLICY(......)
    else select distinct 'BEGIN DBMS_REDACT.ADD_POLICY (....)

    Thanks!

    ReplyDelete
  2. @Anonymous:

    Are you looking for something like this.

    rajesh@ORA11G> create table emp(empno number,salary number,email_id varchar2(10),hire_date date);

    Table created.

    rajesh@ORA11G>
    rajesh@ORA11G> begin
    2 dbms_redact.add_policy
    3 (object_schema=>user,
    4 object_name=>'EMP',
    5 policy_name=>'EMP_PLCY',
    6 column_name=>'SALARY',
    7 function_type=>dbms_redact.RANDOM,
    8 expression=> q'| sys_context('userenv','current_user') != 'RAJESH'|' );
    9 end;
    10 /

    PL/SQL procedure successfully completed.

    rajesh@ORA11G> select object_owner,object_name,
    2 column_name, function_type
    3 from redaction_columns ;

    OBJECT_OWN OBJECT_NAM COLUMN_NAM FUNCTION_TYPE
    ---------- ---------- ---------- ---------------------------
    RAJESH EMP SALARY RANDOM REDACTION

    1 row selected.

    rajesh@ORA11G> declare
    2 l_policy_exists exception;
    3 l_policy_exists_on_column exception;
    4 pragma exception_init(l_policy_exists,-28069);
    5 pragma exception_init(l_policy_exists_on_column,-28060);
    6 begin
    7 for x in ( select * from user_tab_columns
    8 where table_name ='EMP'
    9 and column_name in ('SALARY','EMAIL_ID','HIRE_DATE')
    10 order by column_ID )
    11 loop
    12 begin
    13 dbms_redact.add_policy
    14 (object_schema=>user,
    15 object_name=>'EMP',
    16 policy_name=>'EMP_PLCY',
    17 column_name=> x.column_name,
    18 function_type=>dbms_redact.RANDOM,
    19 expression=> q'| sys_context('userenv','current_user') != 'RAJESH'|' );
    20
    21 exception
    22 when l_policy_exists_on_column then
    23 null ;
    24 when l_policy_exists then
    25 begin
    26 dbms_redact.alter_policy
    27 ( object_schema=>user,
    28 object_name=>'EMP',
    29 policy_name=>'EMP_PLCY',
    30 action=>dbms_redact.add_column,
    31 column_name=> x.column_name,
    32 function_type=>dbms_redact.RANDOM,
    33 expression=> q'| sys_context('userenv','current_user') != 'RAJESH'|' );
    34 exception
    35 when l_policy_exists_on_column then null ;
    36 end;
    37
    38 end;
    39 end loop;
    40 end;
    41 /

    PL/SQL procedure successfully completed.

    rajesh@ORA11G> select object_owner,object_name,
    2 column_name, function_type
    3 from redaction_columns;

    OBJECT_OWN OBJECT_NAM COLUMN_NAM FUNCTION_TYPE
    ---------- ---------- ---------- ---------------------------
    RAJESH EMP HIRE_DATE RANDOM REDACTION
    RAJESH EMP SALARY RANDOM REDACTION
    RAJESH EMP EMAIL_ID RANDOM REDACTION

    3 rows selected.

    rajesh@ORA11G>

    ReplyDelete
  3. Hi Rajesh,

    Can we create a data redaction policy on a column if a specific value match specific condition? for example:
    table stock has (stock name, stock number , department)
    I need to create a data redaction on column (stock number) if the (department) is IT only.
    Is there anyway to do it?

    Regards,
    Bhushan

    ReplyDelete
    Replies
    1. @Anonymous:

      Policy to be applied for the entire column, not to the specific sets.

      rajesh@ORA11G> select * from t;

      STOCK_NUM STOCK_NAME DEPT_NAME
      ---------- ------------------------------ ----------
      1 ICOL$ IT
      2 I_USER1 I_USER1
      3 CON$ CON$
      4 UNDO$ UNDO$
      5 C_COBJ# C_COBJ#

      rajesh@ORA11G> begin
      2 dbms_redact.add_policy
      3 (object_schema=>user,
      4 object_name=>'T',
      5 policy_name=>'T_PLCY1',
      6 policy_description=>'Redact_stock_num',
      7 column_name=>'STOCK_NUM',
      8 expression=>q'#dept_name<>'IT'#' );
      9 end;
      10 /
      begin
      *
      ERROR at line 1:
      ORA-28075: The policy expression has unsupported functions.
      ORA-06512: at "SYS.DBMS_REDACT_INT", line 3
      ORA-06512: at "SYS.DBMS_REDACT", line 40
      ORA-06512: at line 2

      Delete
  4. Hi Rajesh,

    Can we create a redaction policy to redact all digits except last two digits on a variable length field.

    I am trying to create a policy on bank account number, each bank have different length restriction.

    ReplyDelete
    Replies
    1. @Anonymous - Please check if this helps.

      rajesh@ORA11G> set feedback off
      rajesh@ORA11G> drop table t purge;
      rajesh@ORA11G> create table t(bank_acc varchar2(25));
      rajesh@ORA11G> insert into t
      2 select dbms_random.string('x',20)
      3 from dual
      4 connect by level <= 5;
      rajesh@ORA11G> commit;
      rajesh@ORA11G> set feedback on
      rajesh@ORA11G> select * from t;

      BANK_ACC
      -------------------------
      FM9PQ4IW58DA8PDD747K
      411SN5CT9MWN2R2YJUX6
      7WS2W6OZAK08JL73RMJZ
      QEJ70F8SJAJF0ZM08QRX
      PD1T73QTDLYA8HD7B1UF

      5 rows selected.

      rajesh@ORA11G> begin
      2 dbms_redact.add_policy
      3 (object_schema=>user,
      4 object_name=>'T',
      5 column_name=>'BANK_ACC',
      6 policy_name=>'BANK_ACC_CHK',
      7 function_type=>DBMS_REDACT.REGEXP,
      8 expression=> q'| sys_context('userenv','current_user') != 'RAJESH'|' ,
      9 regexp_pattern=>'^(.*)(..)$',
      10 regexp_replace_string=>'XXXX-XXXX-XXX-\2') ;
      11 end;
      12 /

      PL/SQL procedure successfully completed.

      rajesh@ORA11G> select * from t;

      BANK_ACC
      --------------------------------
      FM9PQ4IW58DA8PDD747K
      411SN5CT9MWN2R2YJUX6
      7WS2W6OZAK08JL73RMJZ
      QEJ70F8SJAJF0ZM08QRX
      PD1T73QTDLYA8HD7B1UF

      5 rows selected.

      rajesh@ORA11G> grant select on t to scott;

      Grant succeeded.

      rajesh@ORA11G> conn scott/tiger@ora11g
      Connected.
      scott@ORA11G> set timing off
      scott@ORA11G> select * from rajesh.t;

      BANK_ACC
      --------------------------------------
      XXXX-XXXX-XXX-7K
      XXXX-XXXX-XXX-X6
      XXXX-XXXX-XXX-JZ
      XXXX-XXXX-XXX-RX
      XXXX-XXXX-XXX-UF

      5 rows selected.

      scott@ORA11G>

      Delete
  5. Hi Rajesh great article! I need some help can you give an example using OS USER. I can't seem to get it to work.

    Cheers
    David

    ReplyDelete
    Replies
    1. @ David - you mean "Externally identified" users?

      are you looking something like this ?

      rajesh@ORA11G> show parameter os_aut

      NAME TYPE VALUE
      --------------------------------------------- ----------- --------------
      os_authent_prefix string OPS$
      remote_os_authent boolean FALSE
      rajesh@ORA11G>
      rajesh@ORA11G> create user "OPS$CTS\179818" identified externally;

      User created.

      rajesh@ORA11G> grant create session to "OPS$CTS\179818" ;

      Grant succeeded.

      rajesh@ORA11G> CONN /@ora11g
      Connected.
      ops$cts\179818@ORA11G>
      ops$cts\179818@ORA11G>

      Delete
  6. Hi Rajesh,
    I have below requirement can it be done via VPD or someother mechanism.
    ACCT_TYPE ACCT_ID
    -------------------- ------------------------
    EE 100001702143
    EE 100000091326
    SBA 005010100117295
    SBA 31641350321
    CC 4893771231610001
    CC 4511230001711401

    we want to mask ACCT_ID when ACCT_TYPE is CC

    Below is the Desired masking ouput

    ACCT_TYPE ACCT_ID
    -------------------- ------------------------
    EE 100001703143
    EE 100000091326
    SBA 005010100117295
    SBA 31641350321
    CC 4893XXXXXXX
    CC 4511XXXXXXX

    Regards,
    Manoj

    ReplyDelete
    Replies
    1. @Manoj - VPD / Data redaction applies at the entire set, not on the specific set like this. can this be possible ?

      1) create a view (v1) as where acct_type ='CC' and define VPD on it. (to mask account_id)
      2) create a view (v2) as where acct_type <> 'CC'
      3) create a view (v3) as V1 union all V2 - and expose this view to end users.

      Delete
    2. Thanks Rajesh,

      Well application team does not want views , as the code base is large and they do know the impact.

      Delete
    3. @Manoj,

      a) create a view that select out acct_id as substr(acct_id,1,4)||'XXXXX' when acc_type='CC' else acct_id as new_acc_ids.
      b) Defind VPD on this view "new_acc_ids" columns.
      c) the users should select this view as nvl(acct_id,new_acc_ids);

      rajesh@ORA10G> set feedback off
      rajesh@ORA10G> drop table t purge;
      rajesh@ORA10G> create table t(acc_type varchar2(3), acct_id varchar2(20));
      rajesh@ORA10G> insert into t values('EE',100001702143);
      rajesh@ORA10G> insert into t values('EE',100000091326);
      rajesh@ORA10G> insert into t values('SBA',005010100117295);
      rajesh@ORA10G> insert into t values('SBA',31641350321);
      rajesh@ORA10G> insert into t values('CC',4893771231610001);
      rajesh@ORA10G> insert into t values('CC',4511230001711401);
      rajesh@ORA10G> commit;
      rajesh@ORA10G> select * from t;

      ACC ACCT_ID
      --- --------------------
      EE 100001702143
      EE 100000091326
      SBA 5010100117295
      SBA 31641350321
      CC 4893771231610001
      CC 4511230001711401
      rajesh@ORA10G> create or replace view v
      2 as
      3 select acc_type,acct_id,
      4 decode(acc_type,'CC',
      5 substr(acct_id,1,4)||'XXXXXX',
      6 acct_id) fn_acct_id
      7 from t;
      rajesh@ORA10G> select * from v;

      ACC ACCT_ID FN_ACCT_ID
      --- -------------------- --------------------
      EE 100001702143 100001702143
      EE 100000091326 100000091326
      SBA 5010100117295 5010100117295
      SBA 31641350321 31641350321
      CC 4893771231610001 4893XXXXXX
      CC 4511230001711401 4511XXXXXX
      rajesh@ORA10G> create or replace function foo(p_owner varchar2,p_name varchar2)
      2 return varchar2 as
      3 begin
      4 if user ='RAJESH' then
      5 return '1=1' ;
      6 else
      7 return '1=0';
      8 end if;
      9 end;
      10 /
      rajesh@ORA10G>
      rajesh@ORA10G> begin
      2 dbms_rls.add_policy(object_schema=>user,
      3 object_name=>'V',
      4 policy_name=>'V_PLCY',
      5 function_schema=>user,
      6 policy_function=>'FOO',
      7 sec_relevant_cols=>'ACCT_ID',
      8 sec_relevant_cols_opt=>dbms_rls.all_rows);
      9 end;
      10 /
      rajesh@ORA10G>
      rajesh@ORA10G> select * from v;

      ACC ACCT_ID FN_ACCT_ID
      --- -------------------- --------------------
      EE 100001702143 100001702143
      EE 100000091326 100000091326
      SBA 5010100117295 5010100117295
      SBA 31641350321 31641350321
      CC 4893771231610001 4893XXXXXX
      CC 4511230001711401 4511XXXXXX
      rajesh@ORA10G> grant select on v to scott;
      rajesh@ORA10G> conn scott/tiger@ora10g
      Connected.
      scott@ORA10G> select * from rajesh.v;

      ACC A FN_ACCT_ID
      --- - --------------------
      EE 100001702143
      EE 100000091326
      SBA 5010100117295
      SBA 31641350321
      CC 4893XXXXXX
      CC 4511XXXXXX

      6 rows selected.

      scott@ORA10G> select acc_type, nvl(acct_id,fn_acct_id) as new_acc_ids
      2 from rajesh.v;

      ACC NEW_ACC_IDS
      --- --------------------
      EE 100001702143
      EE 100000091326
      SBA 5010100117295
      SBA 31641350321
      CC 4893XXXXXX
      CC 4511XXXXXX

      6 rows selected.

      scott@ORA10G>

      Delete
  7. Thanks for sharing this great information I am impressed by the information that you have on this blog. Same as your blog i found another one Oracle Fusion Financials , Oracle Project Portfolio Management (PPM) Cloud Tutorial and Oracle Fusion Procurement . Actually I was looking for the same information on internet for Oracle Fusion Manufacturing , Oracle APEX , Oracle Financials Cloud and came across your blog. I am impressed by the information that you have on this blog. It shows how well you understand this subject, you can check more aboutOracle Fusion Supply Chain Management Cloud , Oracle HCM Cloud , Oracle Project Portfolio Management (PPM) Cloud , Oracle Cloud Applications to better understand

    ReplyDelete