Logo Oracle Deutschland   Application Express Community
Daten für den Endanwender "maskieren": Data Redaction und APEX
Erscheinungsmonat APEX-Version Datenbankversion
November 2014 ab 4.0 ab 11.2.0.4

Data Redaction, also das Maskieren sensibler Informationen in der Ausgabe, hat wahrscheinlich jeder schon mal gesehen. Sobald man etwas mit einer Kreditkarte bezahlt, sind die letzten oder die ersten Stellen auf dem Beleg durch "X" oder ein "*" ersetzt - einfach, damit kein Unbefugter die Kreditkartennummer sehen kann.

Doch was hat das mit APEX und der Datenbank zu tun ...? Sehr viel, denn solche maskierten Ausgaben sind auch für Unternehmensanwendungen immer wieder gefragt. Sei es das Geburtsdatum, die private Anschrift oder die Personalnummer - viele dieser Informationen dürfen für den Sachbearbeiter zwar sichtbar sein, für andere Personenkreise (bspw. in einem unternehmensweiten Mitarbeiterverzeichnis) müssen sie jedoch geschützt werden.

Dieser Community Tipp stellt die Oracle-Datenbankfunktion Data Redaction im Zusammenspiel mit APEX-Anwendungen vor. Data Redaction wurde mit dem Release 12c eingeführt und sogar auf die Version 11.2.0.4 zurückportiert. Sie können es also auch mit einer aktuellen 11g-Datenbank nutzen. Es ist allerdings eine Lizenz der Advanced Security Option nötig.

Nun könnte man denken, dass es für etwas scheinbar Banales wie dem Ersetzen bestimmter Zeichen durch ein "X" kein besonderes Datenbankfeature braucht; eine einfache View tut es doch sicherlich auch - aber schauen wir uns das erst mal anhand eines Beispiels an. Zunächst eine Abfrage auf die Tabelle EMP; Data Redaction ist für die Spalte ENAME bereits aktiviert (wie das genau geht, folgt in den nächsten Abschnitten dieses Tipps).

SQL> select * from emp where ename = 'ADAMS'

EMPNO ENAME           JOB         MGR HIREDATE              SAL  COMM DEPTNO
----- --------------- --------- ----- ------------------- ----- ----- ------
 7876 ***MS           CLERK      7788 12.01.1983 00:00:00  1100           20

1 Zeile wurde ausgewählt.

SQL>

Beachten Sie die drei Sternchen, mit denen jeder Eintrag in der Spalte ENAME anfängt. Die Abfrage gibt also maskierte Namen zurück, in der WHERE-Klausel kann man aber ganz offensichtlich den richtigen Namen verwenden, denn die Einschränkung anhand des "ADAMS" hat funktioniert. Im Gegensatz dazu eine einfache View - was für die View gilt, gilt genauso für einen Bericht mit Suchfeld in APEX.

SQL> create or replace view v_emp_redacted as 
  2  select 
  3     empno, regexp_replace(ename, '(...)(.*)','***\2') ename, job, 
  4     mgr, hiredate, sal, comm, deptno 
  5  from emp;

SQL> select * from v_emp_redacted;

View wurde erstellt.

EMPNO ENAME      JOB         MGR HIREDATE              SAL  COMM DEPTNO
----- ---------- --------- ----- ------------------- ----- ----- ------
 7369 ***TH      CLERK      7902 17.12.1980 00:00:00   800           20
 7499 ***EN      SALESMAN   7698 20.02.1981 00:00:00  1600   300     30
 7521 ***D       SALESMAN   7698 22.02.1981 00:00:00  1250   500     30

SQL> select * from v_emp_redacted where ename = 'ADAMS';

Es wurden keine Zeilen ausgewählt

Hier funktioniert das Maskieren bei einem einfachen SELECT * FROM V_EMP_REDACTED zunächst ganz hervorragend. Sobald man aber mit der WHERE-Klausel filtern möchte, merkt man, dass dieser Ansatz doch nicht so gut ist. Hier würden auch keine Indizes mehr funktionieren. Data Redaction ist also offensichtlich etwas anderes als eine einfache View, denn die Maskierung wird nur für die Ausgabe angewendet, nicht jedoch zur Ausführung der Query in der WHERE-Klausel. Demzufolge hat Data Redaction auch keine Auswirkungen auf den Optimizer und die etwaige Nutzbarkeit von Indizes. Nun wird es aber Zeit, sich Data Redaction genauer anzusehen - wie wurde die oben in Aktion erlebte Data Redaction eigentlich eingerichtet?

Data Redaction auf SQL Ebene einrichten

Zum Einrichten von Data Reaction für eine Tabelle wird das PL/SQL Paket DBMS_REDACT verwendet. Normalerweise hat ein Datenbankuser keine Privilegien daran, Sie brauchen also ggfs. von Ihrem DBA das EXECUTE Privileg auf DBMS_REDACT. Eine neue Redaction Policy wird danach mit der Prozedur ADD_POLICY wie folgt erzeugt.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'ENAME',
   policy_name            => 'redact_ename', 
   function_type          => DBMS_REDACT.REGEXP,
   expression             => '1=1',
   regexp_pattern         => '(...)(.*)',
   regexp_replace_string  => '***\2',
   regexp_position        => DBMS_REDACT.RE_BEGINNING,
   regexp_occurrence      => 0,
   regexp_match_parameter => 'i');
END;
/

Die ersten vier Parameter OBJECT_SCHEMA, OBJECT_NAME, COLUMN_NAME und POLICY_NAME sind selbsterklärend: Auf welche Tabelle, in welchem Schema und für welche Tabellenspalte soll Data Redaction aktiviert werden und welchen Namen soll diese "Policy" tragen? Danach wird es interessant. Der Function Type legt fest, auf welche Art und Weise die Daten maskiert werden sollen. Neben dem hier verwendeten DBMS_REDACT.REGEXP stehen noch NONE, FULL, PARTIAL und RANDOM bereit. FULL bedeutet, dass der gesamte Inhalt der Spalte - ohne weitere Parametrisierung - maskiert wird.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'ENAME',
   policy_name            => 'redact_ename', 
   function_type          => DBMS_REDACT.FULL,
   expression             => '1=1');
END;
/

Das Ergebnis einer Abfrage sieht dann so aus.

SQL> select * from emp;

EMPNO ENAME           JOB         MGR HIREDATE              SAL  COMM DEPTNO
----- --------------- --------- ----- ------------------- ----- ----- ------
 7369                 CLERK      7902 17.12.1980 00:00:00   800           20
 7499                 SALESMAN   7698 20.02.1981 00:00:00  1600   300     30
 7521                 SALESMAN   7698 22.02.1981 00:00:00  1250   500     30
 : 

RANDOM ersetzt den tatsächlichen Inhalt der Spalte durch etwas zufällig Generiertes.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'ENAME',
   policy_name            => 'redact_ename', 
   function_type          => DBMS_REDACT.RANDOM,
   expression             => '1=1');
END;
/

SQL>  select * from emp;

EMPNO ENAME           JOB         MGR HIREDATE              SAL  COMM DEPTNO
----- --------------- --------- ----- ------------------- ----- ----- ------
 7369 "`$72           CLERK      7902 17.12.1980 00:00:00   800           20
 7499 O3dw~           SALESMAN   7698 20.02.1981 00:00:00  1600   300     30
 7521 _k@k            SALESMAN   7698 22.02.1981 00:00:00  1250   500     30
 7566 }K08Z           MANAGER    7839 02.04.1981 00:00:00  2975           20
 : 

PARTIAL legt fixe Abschnitte fest, die maskiert werden sollen (bspw. die Zeichen von 3 bis 10). Das eingangs verwendete REGEXP enspricht am ehesten einem PARTIAL, allerdings erlauben reguläre Ausdrücke mehr Freiheit bei der Festlegung, welche Teile maskiert werden sollen. Es hätte also auch mit PARTIAL umgesetzt werden können - dann sähe der Code so aus.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'ENAME',
   policy_name            => 'redact_ename', 
   function_type          => DBMS_REDACT.PARTIAL,
   function_parameters    => 'VVVVVVVVVV,VVVVVVVVVV,*,1,3',
   expression             => '1=1');
END;
/

Auch numerische oder Datumswerte lassen sich maskieren; hier liegen natürlich auch nach der Maskierung noch NUMBER- bzw. DATE-Datentypen vor. Oder anders gesagt: eine maskierte DATE-Spalte ist danach immer noch eine DATE-Spalte, wie das folgende Beispiel anhand von RANDOM zeigt.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'HIREDATE',
   policy_name            => 'redact_hiredate', 
   function_type          => DBMS_REDACT.RANDOM,
   expression             => '1=1');
END;
/

SQL>  select * from emp;

EMPNO ENAME           JOB         MGR HIREDATE              SAL  COMM DEPTNO
----- --------------- --------- ----- ------------------- ----- ----- ------
 7369 SMITH           CLERK      7902 06.10.0130 00:00:00   800           20
 7499 ALLEN           SALESMAN   7698 03.09.0071 00:00:00  1600   300     30
 7521 WARD            SALESMAN   7698 13.12.0222 00:00:00  1250   500     30
 : 

Für weitere und noch detailliertere Informationen zur Anwendung der verschiedenen Redaction-Funktionen sei auf die Dokumentation von DBMS_REDACT.ADD_POLICY verwiesen.

Data Redaction im APEX Kontext richtig nutzen

In den bisherigen Beispiele wurde Data Redaction ohne Bedingungen - für alle Fälle - aktiviert. Das erkennen Sie am Parameter expression mit dem Inhalt 1=1. In der Praxis ist das natürlich nicht brauchbar - keine Anwendung kann es sich leisten, die Maskierung immer vorzunehmen. Es liegen immer Bedingungen für, unter denen die Daten im Originalzustand angezeigt werden sollen. So lässt sich eine APEX-spezifische Redaction Policy wie folgt einrichten.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'ENAME',
   policy_name            => 'redact_ename', 
   function_type          => DBMS_REDACT.RANDOM,
   expression             => 'v(''APP_USER'') != ''ADMIN''');
END;
/

Achten Sie vor allem auf den Parameter expression. Hier wird explizit der Inhalt der APEX-Umgebungsvariable APP_USER geprüft. Ist dieser nicht gleich ADMIN, so findet Data Redaction statt, ansonsten nicht. Erstellt man dann eine kleine APEX-Anwendung, so funktioniert Data Redaction auch völlig "blasenfrei" (Abbildungen 1 und 2).

Data Redaction in Aktion: Angemeldet als "SCOTT"

Abbildung 1: Data Redaction in Aktion: Angemeldet als "SCOTT"

Kein Data Redaction: Angemeldet als "ADMIN"

Abbildung 2: Kein Data Redaction: Angemeldet als "ADMIN"

Der Nachteil der gewählten Strategie wird deutlich, wenn man die Tabelle außerhalb von APEX abfragt - in SQL*Plus findet Data Redaction auch nicht mehr statt. Logisch, denn die SQL*Plus-Sitzung ist keine APEX-Sitzung; es gibt also auch keinen APEX-Usernamen. Man müsste die expression ein wenig verfeinern: Liegt kein APEX-Username vor, soll "nobody" angenommen werden.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'ENAME',
   policy_name            => 'redact_ename', 
   function_type          => DBMS_REDACT.RANDOM,
   expression             => 'nvl(v(''APP_USER''),''nobody'') != ''ADMIN''');
END;
/

Nun arbeitet Data Redaction auch außerhalb von APEX richtig. Wenn es aber auch außerhalb von APEX eine Bedingung geben soll, unter der Data Redaction nicht mehr stattfinden soll, wird die Expression noch komplexer. Außerdem sollten APEX-Spezifika aus Data Redaction Policies nach Möglichkeit herausgehalten werden. Sinnvoller ist es also, mit einem Kontext-Objekt zu arbeiten. Diese Vorgehensweise ist auch mit der Virtual Private Database üblich. Um ein Kontext-Objekt anzulegen und damit den folgenden Code laufen zu lassen, benötigen Sie das CREATE ANY CONTEXT Privileg.

create context my_sec_context using my_sec_package
/

create or replace package my_sec_package authid current_user is
  procedure set_context;
end my_sec_package;
/
sho err

create or replace package body my_sec_package is
  procedure set_context is
  begin
    if v('APP_USER') is null then -- Keine APEX Session
      if SYS_CONTEXT('userenv', 'CURRENT_USER') in ('SYSTEM','TESTIT','SUPERVISOR') then
        dbms_session.set_context('my_sec_context', 'redact', 'NO');
      else 
        dbms_session.set_context('my_sec_context', 'redact', 'YES');
      end if;
    else 
      if v('APP_USER') in ('ADMIN','SUPERVISOR') then 
        dbms_session.set_context('my_sec_context', 'redact', 'NO');
      else 
        dbms_session.set_context('my_sec_context', 'redact', 'YES');
      end if;
    end if;
  end set_context;
end my_sec_package;
/
sho err

Die Data Redaction Policy muss nun wie folgt umgestellt werden.

BEGIN
 DBMS_REDACT.ADD_POLICY(
   object_schema          => 'TESTIT', 
   object_name            => 'EMP', 
   column_name            => 'ENAME',
   policy_name            => 'redact_ename', 
   function_type          => DBMS_REDACT.RANDOM,
   expression             => 'nvl(SYS_CONTEXT(''my_sec_context'',''redact''), ''YES'') = ''YES''');
END;
/

Ein Context-Objekt ist eine Art Speicherbereich für eigene Session-Parameter. Zu Beginn wird also der Context my_sec_context angelegt - darin lassen sich beliebige Attribute als Key-Value-Paar speichern. Der Context hat aber noch eine Besonderheit: Das USING MY_SEC_PACKAGE legt fest, dass der Inhalt nur vom Package MY_SEC_PACKAGE verändert werden darf. Lesen darf es jeder (auch mit einfachem SQL), verändern darf es nur das Package. Probieren Sie mal, den im PL/SQL-Code enthaltenen Aufruf von DBMS_SESSION.SET_CONTEXT in einem anonymen Block direkt auf SQL-Ebene auszuführen.

SQL> exec dbms_session.set_context('my_sec_context', 'redact', 'NO');
BEGIN dbms_session.set_context('my_sec_context', 'redact', 'NO'); END;

*
FEHLER in Zeile 1:
ORA-01031: Nicht ausreichende Berechtigungen
ORA-06512: in "SYS.DBMS_SESSION", Zeile 114
ORA-06512: in Zeile 1

Die Redaction-Policy schaut nun in diesem Context-Objekt (mit Hilfe der SQL Funktion SYS_CONTEXT) nach, ob das Attribut REDACT auf NULL oder YES steht. Wenn ja, wird Data Redaction durchgeführt, ansonsten nicht. Zu Beginn der Session ist es immer SQL NULL; die Daten werden also zunächst mit Data Redaction geschützt.

Um die Daten im Klartext sehen zu können, muss das Attribut REDACT im Kontext MY_SEC_CONTEXT auf NO gestellt werden. Das kann, wie gesagt, nur mit dem Package MY_SEC_PACKAGE geschehen. Das Package enthält genau eine Prozedur SET_CONTEXT, die keine Paramater entgegennimmt und, anhand einer Logik, das Attribut REDACT auf YES oder NO stellt. In dieser Logik, die beliebig komplex (wie die Praxis) werden kann, kann nun geprüft werden, ob eine APEX-Session vorliegt oder nicht, und welcher User in welcher Umgebung gerade angemeldet ist. Sie können nun Execute-Rechte an diesem Package an alle Datenbankuser vergeben, die mit der geschützten Tabelle arbeiten sollen.

Wichtig ist nun, dass sichergestellt ist, dass die Prozedur MY_SEC_PACKAGE.SET_CONTEXT zu Beginn einer jeden Sitzung aufgerufen wird. Außerhalb von APEX (bei "normalen" Datenbanksessions) kommt ein Datenbanktrigger (AFTER LOGON ON DATABASE) in Betracht - in APEX navigieren Sie zu den Gemeinsamen Komponenten, dort zur Definition der Anwendungsattribute und dann in den Bereich Sicherheit. Tragen Sie dann im Abschnitt Datenbanksitzung bei Initialisierungscode den Aufruf von #OWNER#.MY_SEC_PACKAGE.SET_CONTEXT ein (Abbildung 3).

PL/SQL Prozedur MY_SEC_PACKAGE.SET_CONTEXT zu Beginn jeder APEX-Session aufrufen

Abbildung 3: PL/SQL Prozedur MY_SEC_PACKAGE.SET_CONTEXT zu Beginn jeder APEX-Session aufrufen

Von nun an erreichen Sie in APEX wieder die gleichen Ergebnisse wie in den Abbildungen 1 und 2, Sie haben aber das APEX-Spezifikum v('APP_USER') aus dem Aufruf von DBMS_REDACT eliminiert. Außerdem kann das System nun auch außerhalb von APEX, anhand des physikalischen Datenbankusers, entscheiden, ob Data Redaction stattfinden soll oder nicht. Und natürlich kann das für beliebige Umgebungen erweitert werden.

Fazit

Data Redaction ist eine interessante neue Datenbankfunktion, mit der Sie Tabelleninhalte unmittelbar vor der Ausgabe maskieren können. Dabei funktionieren alle Indizes und Zugriffsoptimierungen weiterhin, da Data Redaction auf die konkrete Ausführung der Abfrage keinerlei Auswirkungen hat. Der große Vorteil von Data Redaction ist die einheitliche Anwendung der Maskierung über alle Anwendungen, die mit den Tabellendaten arbeiten, hinweg. "Peinlichkeiten", dass Anwendung "A" die ersten 3 Stellen maskiert, Anwendung "B" dagegen die letzten drei, werden ausgeschlossen. Weiterhin kann das Maskieren aus der Anwendung komplett herausgehalten werden; die konkrete APEX-Anwendung verwendete nur einen Standard-Bericht und hatte keinerlei "Ahnung" von der Maskierung.

Mehr Informationen zu Data Redaction finden Sie im Abschnitt II des Handbuchs Oracle Advanced Security Guide.

Zurück zur Community-Seite