Data Engineering, Datensicherheit und Zugriffsrechte

Posted on Mi 12 Juni 2024 in Blog

Data Engineering, Datensicherheit und Zugriffsrechte

Inhalt:

Einleitung

In einem Vortrag, den ich vor einiger Zeit gehört habe, waren die einleitenden Worte sinngemäß folgende:

Am liebsten würde man als Data Engineer ständig an einem tollen neuen Produkt arbeiten, aber manchmal muss man sich auch mit Sicherheit und Rollen herumschlagen.

Ich persönlich halte das für den falschen Ansatz. Denn ein tolles Produkt hat man nur dann, wenn die Sicherheit auch gegeben ist. Zu schnell können dann meine Daten weg sein, wie auch data breaches Woche für Woche beweisen.

Zusätzlich kommen auch noch rechtliche Aspekte, beispielsweise die DSGVO, hinzu. Daher sollte meiner Meinung in einem ersten Schritt auch immer der Fokus sein, wer auf welche Daten zugreifen kann. Idealerweise funktionieren Anwendungen mit so wenig Rechten, wie nur möglich.

Der Begriff Datensicherheit umfasst wesentlich mehr, als im Folgenden dargestellt wird. Hier ist der Fokus rein darauf, wie man Zugriffsrechte in Datenbanken am Beispiel einer Postgres-Datenbank so setzen kann, dass die einzelnen Rollen möglichst wenig Rechte haben, aber alles machen können, was sie tun müssen.

Rollen in Postgres

Postgres bietet standardmäßig umfangreiche Möglichkeiten, um die Zugriffsrechte auf Daten zu gestalten.

Das fängt schon mit der Möglichkeit an in der Datei pg_hba.conf zu definieren, welche Rollen Zugriff auf welche Datenbanken haben. HBA steht für host-based authentication und in dieser Datei wird neben dem Zugriff von Rollen auf Datenbanken, auch definiert von welcher IP-Adresse man zugreifen darf, ob eine Verbindung nur per SSL/TLS erlaubt ist und welche Methode für die Authentifzierung verwendet werden muss. Die Dokumentation von Postgres ist, wie üblich, sehr umfangreich dazu: The pg_hba.conf File.

Die wahrscheinlich größere Bedeutung für einen Data Engineer hat das Rollenkonzept von Postgres selbst. Die Dokumentation beschreibt Rollen in Chapter 22. Database Roles folgendermaßen:

PostgreSQL manages database access permissions using the concept of roles. A role can be thought of as either a database user, or a group of database users, depending on how the role is set up. Roles can own database objects (for example, tables and functions) and can assign privileges on those objects to other roles to control who has access to which objects. Furthermore, it is possible to grant membership in a role to another role, thus allowing the member role to use privileges assigned to another role.

Um auf eine Tabelle zumindest lesend zugreifen zu können, muss eine Rolle die Möglichkeit haben sich mit der Datenbank verbinden zu können, auf das Schema, wo die Tabelle liegt, zugreifen zu dürfen und für die Tabelle selbst Select-Rechte haben. Grundsätzlich kann man mit diesem Rollenkonzept sehr genau definieren, welche Rolle auf welche Objekte wie zugreifen darf (beispielsweise lesend oder schreibend).

Mit der Version 15 von Postgres hat sich hinsichtlich des Schemas public etwas geändert. Davor konnte dort jede Rolle, die berichtigt war auf die Datenbank zuzugreifen, auch Tabellen bzw. anderen Datenbankobjekte erstellen. Seit der Version 15 ist das nicht mehr möglich und meines Erachtens ein richtiger Schritt, denn es sollten in einer Datenbank nur Rollen, denen es explizit erlaubt ist, Datenbankobjekte (z.B. Tabellen) erstellen können. Bei EDB erklärt Peter Eisentraut die Veränderungen: New Public Schema Permissions in PostgreSQL 15. Ich würde, wenn möglich, sowieso davon absehen in das Schema public Tabellen zu schreiben. Macht das Leben oftmals einfacher.

Aber neben der Möglichkeit den Zugriff auf einzelne Tabellen zu beschränken, gibt es auch auf Ebene der Tabellen weitere Möglichkeiten, den Zugriff einzuschränken. Hierbei gibt es die Möglichkeit den Zugriff auf Spalten und/oder Zeilen einzuschränken. Auf Ebene der Spalten (Column-Level Security) gibt es einerseits die Möglichkeit mit Views zu arbeiten oder aber auch mit column-level permissions, d.h. den Zugriff auf einzelne Spalten nur bestimmten Rollen zu erlauben:

grant select (spalteA, spalteB, spalteC, ...) on tabelle to rolle;

Der Zugriff auf einzelne Zeilen (Row-Level Security) erfolgt über sogenannte row security policies. In 5.8. Row Security Policies beschreibt die Dokumentation das Konzept folgerndermaßen:

In addition to the SQL-standard privilege system available through GRANT, tables can have row security policies that restrict, on a per-user basis, which rows can be returned by normal queries or inserted, updated, or deleted by data modification commands. This feature is also known as Row-Level Security. By default, tables do not have any policies, so that if a user has access privileges to a table according to the SQL privilege system, all rows within it are equally available for querying or updating.

Zusätzlich ist es natürlich möglich Column-Level Security und Row-Level Security zu kombinieren und die Daten für unterschiedliche Rollen weiter einzuschränken.

Eine weitere Möglichkeit, die Postgres bietet, um die Zugriffe auf Datenbankobjekte für die jeweiligen Rollen standardmäßig einzuschränken, ist die Möglichkeit die Default Privileges einzustellen. Hierbei ist allerdings zu beachten, Änderungen habe nur Auswirkungen auf zukünftige Objekte. Siehe ALTER DEFAULT PRIVILEGES:

ALTER DEFAULT PRIVILEGES allows you to set the privileges that will be applied to objects created in the future. (It does not affect privileges assigned to already-existing objects.)

Allerdings sollte man auch bedenken, dass durch die Kobination der einzelnen Möglichkeiten, die Postgres bietet, es zu sehr komplexen Berechtigungskonzepten kommen kann und man dadurch eventuell auch den Überblick verliert, welche Rolle jetzt was darf. Im schlimmsten Fall können dadurch Lücken im Berechtigungskonzept von einem Angreifer ausgenutzt werden. Daher sollte man sich überlegen, ob nicht beispielsweise Views eine einfachere Lösung sind, um Column-Level Security zu erreichen. Postgres bietet viele Möglichkeiten, aber jeder Einsatz sollte sorgfältig überlegt werden.

Welche Rollen braucht man?

Welche Rollen man wirklich braucht, hängt dann natürlich auch von den jeweiligen Gegebenheiten ab. Grundsätzlich bietet sich an, nirgends Berechtigungen nur für einen einzelnen User hinzufügen, sondern die User zu Gruppen hinzufügen. Auch wenn beides in Postgres Rollen heißt.

Im Laufe der Zeit hat sich bei mir ungefähr folgender Ablauf als ganz brauchbar herausgestellt. Anmerkung: es kann sich bei jedem Schritt um eine oder mehrere Rollen mit ähnlichen Privilegien handeln:

  • Um Daten aus unterschiedlichsten Quellen einzuspielen, sollte es eine Rolle geben, die auf gewisse Tabellen schreibend zugreifen kann. Idealerweise sind die Tabellen auch in einem Schema für "Rohdaten". Auf Daten außerhalb der Schemata für "Rohdaten" sollte die Rolle keinerlei Zugriff haben.
  • Für die Transformation, beispielsweise mit dbt, wird dann eine Rolle benötigt, die auf alle benötigten Tabllen lesenden Zugriff hat und in die Schemata, wo die Resultate der Transformationen geschrieben werden, schreibenden Zugriff hat.
  • Die Endnutzer, diese können Analysten, Anbindungen an Dashboards oder APIs sein, sollten dann (idealerweise) nur Lesezugriff auf die Resultate der Transformationen haben.

Ob man einen Data Lake, ein Data Warehouse, ein Data Mart, ein Data Lakehouse oder was auch immer hat, die Idee bleibt gleich. Unabhängig ob Daten zwischen Datenbanken transferiert werden müssen oder sich alle Daten sowieso in einer Datenbank befinden. Wichtig ist mir nur, zu zeigen, jede Rolle für jeden Schritt sollte maximal die Berechtigungen haben, die unbedingt zur Ausführung des Schritts bzw. zur Erfüllung der Aufgaben benötigt werden. Stichwort hierfür ist Principle of least privilege.

Außerdem macht es auch Sinn mittels pgaudit mitzuprotokollieren, welche Rollen wie auf welche Tabellen zugreifen. Gerade wenn die Anzahl der Rollen zunimmt, kann es durchaus sinnvoll sein, wenn man dadurch einen Überblick behält, wer auf was zugreift. Und im Falle es Sicherheitsvorfalls kann man dann diesen vielleicht besser aufarbeiten.

Wie kann man den Überblick über die Rechte behalten?

Ein Rechtemanagement kann noch so ausgeklügelt sein, aber wichtig dabei ist einerseits eine nachvollziehbare Konfiguration und auch einen Überblick zu behalten, ob die Berechtigungen auch mit der Erwartung übereinstimmen. Dafür verwende ich zwei Tools. Theoretisch mag es hier auch noch weitere Tools geben, aber das Postgres-Ökosystem ist so bunt und breit, da kann es schon einmal passeren, dass man nicht alles mitbekommt und das eine oder andere Werkzeug unter dem Radar durchfliegt.

Als erstes Werkzeug möchte ich ldap2pg von Dalibo kurz erwähnen. Dieses Tool ermöglicht die Synchronisation von LDAP-Benutzern nach Postgres und eine Zuweisung der Rollen anhand von LDAP-Benutzergruppen. Die Konfiguration von ldap2pg erfolgt über eine YAML-Datei. Es wird bei uns sehr stark eingesetzt, um die Berechtigungen der einzelnen Rollen zu managen, indem in FreeIPA die Benutzer den entsprechenden Gruppen zugewiesen werden. Zusätzlich werden Änderungen an der Konfigurationsdatei per Ansible ausgerollt und per git versioniert. Dadurch ist auch eine gewisse Nachvollziehbarkeit der Änderungen gegeben.

Beim zweiten Tool handelt es sich um pg_permissions von Cybertec. Diese Erweiterung ermöglicht es sich ziemlich schnell einen Überblick über die Permissions in einer Datenbank zu verschaffen. Zusätzlich ermöglicht es einen "Diff"-View zu geben, d.h. was erwartet man sich und wie ist die Realität. Dieser Teil wird bei uns derzeit noch (?) nicht so stark genutzt, da wir noch keinen "perfekten" Ablauf haben, die Änderungen von ldap2pg auch pg_permissions sichtbar zu machen. Aber das steht noch auf der To-do-Liste.

Zusammenfassung

Ich denke, mit die wichtigste Aufgabe für einen Data Engineer ist es, die Berechtigungen nach dem Principle of least privilege zu setzen. Jede Rolle sollte genau die Berechtigungen haben, die gebraucht werden, um die Aufgabe zu erfüllen. Diese Aufgabe sollte auch immer am Anfang stehen und nicht im Nachhinein geschehen.

Postgres bietet vielfältige Möglichkeiten, um die Rechte für einzelne Rollen zu definieren. Aber es kann auch dazu verleiten, die Dinge komplexer zu gestalten, als es sinnvoll ist. Es ist wichtig, die Dinge so zu gestalten, dass es noch möglich ist, einen Überblick zu behalten.

Neben den Möglichkeiten, die Postgres out-of-the-box bietet, gibt es auch noch Erweiterungen, die es ermöglichen die Berechtigungen zu setzen, zu überprüfen und mitzuprotokollieren. Gerade die regelmäßige Überprüfung der Rechte ist wichtig, um unerlaubtem Zugriff vorzubeugen.

Da es ein relativ aktueller Artikel ist, gibt es noch einen Lesetipp: PG Phriday: Taking Postgres for GRANTed.