19 Oktober 2015

PostgreSQL® 9.5: Row Level Security

Neu in PostgreSQL® 9.5 ist das Feature ROW LEVEL SECURITY.

Was ist ROW LEVEL SECURITY

Mit Hilfe von ROW LEVEL SECURITY lässt sich bestimmen, welche Voraussetzungen erfüllt sein müssen, damit ein Datenbankbenutzer ein Tupel sieht, einfügen, löschen oder bearbeiten darf.

Kurz gesagt, es beseht nun die Möglichkeit Zugriffsberechtigungen auf Tupelebene zu vergeben.

Zur Konfiguration dieser Zugriffsberechtigungen kommen die in PostgreSQL® 9.5 eingeführten, SECURITY POLICIES zum Einsatz. Mit Hilfe dieser können feingranulare Regeln definiert werden, die bei den Zugriff auf Tuple ausgewertet werden.

Beispiel

In unserem Beispiel möchten wir den Zugriff auf eine Tabelle einschränken, die die Verkaufszahlen verschiedener Abteilungen beinhaltet. Wir möchten den Benutzern den Zugriff aber nicht komplett entziehen. Jeder Benutzer soll auf die Zahlen der Abteilung zuzugreifen können der er angehört. Die Zahlen der anderen Abteilungen sollen hingegen nicht sichtbar oder veränderbar sein.

Zunächst benötigen wir eine Zuordnung von Benutzern zu Abteilungen:

avo@[local]:5495 [postgres] > CREATE TABLE user_in_department (
  username text,
  dep text,
  PRIMARY KEY (username, dep));
avo@[local]:5495 [postgres] > INSERT INTO user_in_department
VALUES  ('manuel_mueller'   , 'games')  ,
        ('michael_schneider' , 'games')  ,
        ('michael_schneider' , 'tv')     ,
        ('bobdan_muel'       , 'tv')     ,
        ('tizian_martin'     , 'audio');

Nachdem wir nun eine Zuordnung von Benutzern zu Abteilungen haben, benötigen wir noch unsere „sales“ Tabelle:

avo@[local]:5495 [postgres] > CREATE TABLE sales (
  id SERIAL PRIMARY KEY,
  department text NOT NULL,
  target_range tsrange,
  sum numeric);

Als Beispieldaten dienen uns die Verkaufszahlen vier verschiedener Abteilungen:

avo@[local]:5495 [postgres] > INSERT INTO sales (department, target_range, sum)
VALUES  ('tv'       , '[2015-01-01 00:00 , 2015-02-01 00:00)' , 120000) ,
        ('games'    , '[2015-01-01 00:00 , 2015-02-01 00:00)' , 140000) ,
        ('computer' , '[2015-01-01 00:00 , 2015-02-01 00:00)' , 2000)   ,
        ('audio'    , '[2015-01-01 00:00 , 2015-02-01 00:00)' , 90000);

Die Prüfung der Zugriffsberechtigungen wird über die folgende SECURITY POLICY definiert:

avo@[local]:5495 [postgres] > CREATE POLICY user_in_department
  ON sales
  FOR ALL
  TO public
  USING (
    (
      SELECT COUNT(uid.username) >= 1
      FROM user_in_department uid
      WHERE uid.username = current_user AND
        uid.dep = department
    )
  );

Zu guter Letzt müssen wir ROW LEVEL SECURITY für die Tabelle „sales“ aktivieren, und einen Beispielbenutzer anlegen:

avo@[local]:5495 [postgres] > ALTER TABLE sales ENABLE ROW LEVEL SECURITY;
CREATE USER manuel_mueller;
GRANT SELECT ON user_in_department TO manuel_mueller;
GRANT SELECT, INSERT, UPDATE, DELETE ON sales TO manuel_mueller;

Frag der Benutzer „manuel_mueller“ nun die Verkaufszahlen ab, so erhält er als Ergebnis nur die Verkaufszahlen der Abteilung der er angehört:

manuel_mueller@[local]:5495 [postgres] > SELECT * FROM sales;
 id | department |                 target_range                  |  sum   
----+------------+-----------------------------------------------+--------
  2 | games      | ["2015-01-01 00:00:00","2015-02-01 00:00:00") | 140000
(1 row)

Selbst wenn es ihm ermöglicht ist, selber Eintragungen innerhalb der Tabelle sales vorzunehmen, wird verhindert, dass er Eintragungen für andere Abteilungen vornimmt. Auch ist es Ihm nicht möglich Eintragungen anderer Abteilungen zu bearbeiten:

manuel_mueller@[local]:5495 [postgres] > UPDATE sales SET sum=sum+100;
UPDATE 1

Die aktuellen SECURITY POLICIES können mit SELECT * FROM pg_policies; oder mit Hilfe des Backslashcommands \dp abgefragt werden. In unserem Beispiel sieht das Ergebnis wie folgt aus:

avo@[local]:5495 [postgres] > SELECT * FROM pg_policies ;
-[ RECORD 1 ]-------------------------------------------------------------------------------------
schemaname | public
tablename  | sales
policyname | user_in_department
roles      | {public}
cmd        | ALL
qual       | ( SELECT (count(uid.username) >= 1)                                                  +
           |    FROM user_in_department uid                                                       +
           |   WHERE ((uid.username = ("current_user"())::text) AND (uid.dep = sales.department)))
with_check | __NULL__

Zusätzlich zu USING kann die WITH CHECK Option angegeben werden. Diese greift, sobald ein Benutzer Tupel einfügen, ändern oder entfernen möchte.

Das folgende Beispiel unterbindet die nachträgliche Bearbeitung von Eintragungen:

ALTER POLICY user_in_department ON sales WITH CHECK ( target_range @> NOW()::timestamp );

Technische Details

Neben der Einschränkung aller Operationen können auch spezifische Operationen eingeschränkt werden. Dazu wird bei der Erstellung der SECURITY POLICY nicht das Schlüsselwort ALL, sondern die entsprechende Operation eingetragen (SELECT, INSERT, UPDATE oder DELETE).

In unserem Beispiel wirkt sich die Einschränkung auf alle Benutzer aus (TO
public
). Aber auch dies lässt sich spezifizieren. Soll die Überprüfung nur für einen gewissen Kreis an Benutzern geprüft werden, so kann anstelle von „public“ auch eine spezielle Gruppe angegeben werden. Achtung: allen anderen Benutzern wird der Zugriff ohne Prüfung verwehrt.

Mit ROW LEVEL SECURITY wurde der Parameter row_security eingeführt. Dieser nimmt einen von drei möglichen Werten an: off, onund force.

  • off deaktiviert die Überprüfung der SECURITY POLICIES. Dies führt dazu, dass alle Tabellen für die ROW LEVEL SECURITY aktiviert worden ist, für normale Benutzer gesperrt werden. Ein versuchter Zugriff auf solch eine Tabelle wird mir der Fehlermeldung „ERROR: insufficient privilege to bypass row security.“ geblockt.
  • on aktiviert die Überprüfung. Es ist zugleich die Standardeinstellung.
  • force aktiviert die Überprüfung. Im Gegensatz zu on unterliegen in dieser Einstellung auch TABLE OWNER der Überprüfung, welche normalerweise nicht von der Überprüfung betroffen sind.

SUPERUSER unterliegen der ROW LEVEL SECURITY nicht.

Fazit

Das oben genannte Beispiel ist nur eins von vielen denkbaren Einsatzszenarien. Zwar lassen sich viele Operationen auch mit entsprechenden VIEWS oder TRIGGERN realisieren, jedoch bietet PostgreSQL® mir ROW LEVEL SECURITY nun eine einfacher zu administrierende Methode zur Umsetzung. Zugleich entfällt der zusätzliche Overhead für die Ausführung der entsprechenden Trigger.

Details können der PostgreSQL® Dokumentation entnommen werden.

ROW LEVEL SECURITY ist nur eines von vielen neuen und interessanten Features, die mit Version 9.5 von PostgreSQL® eingeführt werden. Eine Übersicht über die neuen Features bietet das Kategorien: PostgreSQL® Tags: PostgreSQL®


AV

über den Autor

Adrian Vondendriesch

Technischer Leiter

zur Person

Adrian ist seit 2013 Mitarbeiter der credativ GmbH. Als technischer Leiter des Cloud Infrastructure Teams beschäftigt er sich hauptsächlich mit der Planung, Realisierung und Betreuung verteilter Infrastrukturen wie zum Beispiel Kubernetes und Ceph sowie mit der Erarbeitung von Deployment-Strategien. Zuvor war er Teil des Datenbank-Teams bei credativ und war dort unter anderem mit dem Aufbau und der Verwaltung von hochverfügbaren Datenbank-Systemen betreut. Seit 2015 beteiligt er sich aktiv am Debian-Projekt.

Beiträge ansehen


Beitrag teilen: