Column based Virtual Private Database

In Zeiten des Datendiebstahls und der Bespitzelung sollten Anwendungsentwickler besonderen Wert auf die Sicherheit der gespeicherten Daten legen. Gerade personenbezogene Daten und Passwörter bedürfen einem besonderen Schutz auf aller tiefster Ebene: der Datenbank. Oracle stellt einen Mechanismus genannt Virtual Private Database (VPD) zur Verfügung den ich hier etwas näher erläutern möchte.

Als Beispiel nutzen wir eine einfach Benutzertabelle mit ID, Username und Passwort:

CREATE TABLE users
(
   id        NUMBER       PRIMARY KEY NOT NULL,
   username  VARCHAR2(30)             NOT NULL,
   password  VARCHAR2(64)             NOT NULL
);

INSERT INTO users VALUES ( 1, 'USERA', 'strenggeheim' );
INSERT INTO users VALUES ( 2, 'USERB', 'meinPasswort' );
INSERT INTO users VALUES ( 3, 'USERC', 'usercspwd' );

Selbstverständlich sollten Passwörter niemals unverschlüsselt in der Datenbank stehen, für dieses Beispiel ist dies aber eher Nebensache.

VPD nutzt zur Sicherung der Daten sogenannte Policies die in Form von PL/SQL Prozeduren definiert werden. Die Prozeduren zeichnen sich dadurch aus, dass sie immer einen String im Format einer gültigen SQL WHERE Klausel zurückliefern. Die folgende Prozedur unterscheidet zwischen den angemeldeten Benutzern. Heißt der aktuelle Benutzer “ADMIN“, so liefert sie die WHERE Klausel “1=1″ zurück, ansonsten “USER = USERNAME”.

CREATE OR REPLACE FUNCTION pwdpolicy(
   object_schema IN VARCHAR2,
   object_name   IN VARCHAR2)
             RETURN VARCHAR2
IS
BEGIN
   IF USER = 'ADMIN' THEN
      RETURN '1 = 1';
   ELSE
      RETURN 'USER = USERNAME';
   END IF;
END;

Welche Auswirkungen die WHERE Klauseln haben sehen wir später. Zunächst muss die Policy noch an die Tabelle gebunden werden:

begin
   DBMS_RLS.ADD_POLICY (
      object_name              => 'USERS',
      policy_name              => 'PASSWORD_POLICY',
      policy_function          => 'PWDPOLICY');
end;

Die Auswirkung der Policy sehen nun am folgenden Beispiel, wenn wir uns mit 2 unterschiedlichen Benutzern an der Datenbank anmelden. Einmal dem Benutzer ADMIN und dem Benutzer USERB.

ADMIN> select * from admin.users;

 ID USERNAME   PASSWORD
--- ---------- ---------------
  1 USERA      strenggeheim
  2 USERB      meinPasswort
  3 USERC      usercspwd

USER> select * from admin.users;

 ID USERNAME   PASSWORD
--- ---------- ---------------
  2 USERB      meinPasswort

Bei beiden Benutzern ist die Policy wirksam, jedoch durch die stets wahre Bedingung “1=1” werden im ersten Fall alle Zeilen zurückgeliefert. Im zweiten Fall mit Benutzer USERB nur die Zeilen, die auch zum Benutzer USERB gehören. Die zurückgelieferte Bedingung in der Policy kann dabei beliebig komplex werden und darf z.B. auch Subselects usw. enthalten. Ungeschickt ist, dass USERB nun nicht mehr in der Lage ist die Datensätze der anderen Benutzer zu sehen, obwohl wir ja eigentlich nur die Passwörter der anderen Benutzer geheim halten wollen.

Glücklicherweise lässt sich VPD jedoch auch auf Spaltenebene eingrenzen. Im folgenden habe ich die Policy leicht geändert und als relevante Spalte die PASSWORD Spalte angegeben.

begin
   DBMS_RLS.DROP_POLICY( NULL, 'USERS', 'PASSWORD_POLICY');

   DBMS_RLS.ADD_POLICY (
      object_name              => 'USERS',
      policy_name              => 'PASSWORD_POLICY',
      policy_function          => 'PWDPOLICY',
      sec_relevant_cols        => 'PASSWORD');
end;
/

USER> select id, username, password from admin.users;

 ID USERNAME   PASSWORD
--- ---------- --------------
  2 USERB      meinPasswort

USER> select id, username from admin.users;

 ID USERNAME
--- ---------
  1 USERA
  2 USERB
  3 USERC

Das Ergebnis ist erfreulich. Will USERB ein Passwort wissen und nimmt die Spalte in das SELECT Statement auf, so greift die Policy und liefert ihm nur seinen Datensatz zurück. Lässt er jedoch die Spalte mit den sensiblen Daten weg, so darf er alle Datensätze in der Tabelle betrachten. Eine weiter Option zur Spaltensicherheit zeigt das folgende Beispiel:

begin
   DBMS_RLS.DROP_POLICY( NULL, 'USERS', 'PASSWORD_POLICY');

   DBMS_RLS.ADD_POLICY (
      object_name              => 'USERS',
      policy_name              => 'PASSWORD_POLICY',
      policy_function          => 'PWDPOLICY',
      sec_relevant_cols        => 'PASSWORD',
      sec_relevant_cols_opt    => DBMS_RLS.ALL_ROWS);
end;
/

USERB>select * from admin.users

 ID USERNAME   PASSWORD
--- ---------- --------------
  1 USERA
  2 USERB      meinPasswort
  3 USERC      

Durch die Option “sec_relevant_cols_opt => DBMS_RLS.ALL_ROWS” werden dem Benutzer stets alle Datensätze zurückgeliefert. Lediglich für die Spalte PASSWORD greift dann die Policy und liefert je nach Auswertung entweder ein NULL oder den Wert zurück.

Schlagworte: , , , , ,

Kommentieren