Clusterward
← Zurück zum Blog
BetriebAktualisiert Florian Apel

Eine Datenbank pro Service: Rollen, Rechte und der Lesenutzer, der nichts sieht

Der BI-Nutzer hat Leserechte und sieht trotzdem die neue Tabelle nicht. Das ist kein Bug, das ist PostgreSQL. Und es ist der Grund, warum Rechte in fester Reihenfolge vergeben werden müssen.

Titelbild: Eine Datenbank pro Service: Rollen, Rechte und der Lesenutzer, der nichts sieht

Eine Managed-Instanz für alle Services, eine Datenbank pro Service, eine Rolle pro Datenbank. Das ist das Modell, das Kosten und Trennung zusammenbringt: Die Instanz wird geteilt, die Daten nicht. Es funktioniert gut, solange man drei Eigenschaften von PostgreSQL kennt, die jeder einmal schmerzhaft lernt.

Eigenschaft 1: Rechte gelten für Tabellen, die es schon gibt

GRANT SELECT ON ALL TABLES IN SCHEMA public TO bi_reader klingt vollständig. Es gilt aber nur für die Tabellen, die zum Zeitpunkt des GRANT existieren. Die Tabelle, die die Anwendung bei der nächsten Migration anlegt, gehört dem Besitzer, und bi_reader sieht sie nicht. Der Fehler zeigt sich Wochen später als "Tabelle existiert nicht" im BI-Tool, und niemand bringt ihn mit dem GRANT von damals in Verbindung.

Die Lösung ist ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public GRANT SELECT ON TABLES TO bi_reader. Damit bekommt der Lesenutzer automatisch Rechte an allem, was der Besitzer künftig anlegt. Wichtig ist das FOR ROLE app_owner: Default Privileges gelten pro anlegender Rolle. Ohne diesen Zusatz gelten sie für die Rolle, die das Statement ausführt, und das ist selten die Anwendung.

Eigenschaft 2: Rechte addieren sich, sie ersetzen sich nicht

Wer einen Nutzer von readwrite auf read herabstuft und dafür GRANT SELECT ausführt, hat nichts herabgestuft. Die alten INSERT- und UPDATE-Rechte bleiben. Rechte in PostgreSQL sind eine Menge, und GRANT fügt hinzu. Ein Rechtewechsel muss deshalb immer zuerst REVOKE ALL ausführen und dann die gewünschte Stufe erteilen. Revoke vor Grant, jedes Mal, auch wenn es beim ersten Anlegen überflüssig wirkt.

Eigenschaft 3: Default Privileges leben in der Datenbank

Rollen sind auf einer PostgreSQL-Instanz global, Default Privileges und Tabellenrechte liegen in der jeweiligen Datenbank. Wer die Rechte eines Lesenutzers setzt, muss mit der richtigen Datenbank verbunden sein, nicht mit der Admin-Datenbank. Ein Skript, das alles über eine Verbindung zur Instanz erledigt, setzt die Rechte in der falschen Datenbank und meldet Erfolg.

Welche Rechte-Stufen reichen aus?

In der Praxis braucht ein Service selten mehr als drei Nutzertypen neben dem Besitzer:

Stufe

Rechte

Typischer Nutzer

read

SELECT auf alle Tabellen, auch künftige

BI-Tool, Backup, Support

readwrite

SELECT, INSERT, UPDATE, DELETE

Worker, zweite Anwendung

all

Alle Rechte innerhalb der Datenbank

Migrations-Werkzeug

Was bewusst fehlt: eine Stufe für Instanz-Admin. Die Scaleway-Konsole bietet beim Anlegen eines Nutzers den Schalter "Admin-Rechte" an. Wer ihn für einen Anwendungsnutzer setzt, hat einen Nutzer, dessen Passwort in Umgebungsvariablen, in einem Secret und im Cockpit lesbar ist und der auf der Instanz neue Datenbanken und Nutzer anlegen darf. Die Rechte, die für die einzelnen Datenbanken vergeben sind, hebt der Schalter laut Scaleway zwar nicht auf – ein Anwendungsnutzer braucht diese Befugnisse aber nicht. Ein geleaktes Anwendungspasswort darf nie weiter reichen als bis zur eigenen Datenbank.

Namen und Passwörter sind keine Parameter

Eine unangenehme Eigenschaft von SQL: CREATE ROLE ... PASSWORD nimmt ein Literal, keinen Bind-Parameter. Wer Rollennamen und Passwörter aus Eingaben zusammensetzt, muss beides selbst validieren und escapen. Bei Namen hilft eine strenge Regel wie kleine Buchstaben, Ziffern, Unterstrich. Bei Passwörtern eine Regel, die den Backslash ausschließt, weil PostgreSQL ihn je nach standard_conforming_strings als Escape liest oder nicht. Scaleway begrenzt Datenbank- und Nutzernamen außerdem auf 32 Zeichen; ein Name aus einer langen Domain muss vorher gekürzt werden.

Adoptieren statt neu anlegen

Bestehende Datenbanken gehören nicht neu angelegt, sondern übernommen. Der Test, ob Name, Rolle und Passwort stimmen, ist nicht ein Blick in pg_database, sondern eine Verbindung als diese Rolle in diese Datenbank. Nur sie beweist alle drei auf einmal. Und eine übernommene Datenbank darf nie automatisch gelöscht werden, weil sie Daten enthält, die älter sind als die Automatik. Sie wird nur wieder freigegeben.

Die Statements, in der richtigen Reihenfolge

Für einen Lesenutzer auf einer Datenbank, die der Rolle app_owner gehört, sieht der vollständige Ablauf so aus, ausgeführt mit einer Verbindung zur Datenbank selbst:

  1. CREATE ROLE bi_reader LOGIN PASSWORD '...', einmalig, auf Instanzebene.
  2. REVOKE ALL ON ALL TABLES IN SCHEMA public FROM bi_reader, damit ein vorheriger Stand nicht weiterlebt.
  3. GRANT CONNECT ON DATABASE app TO bi_reader und GRANT USAGE ON SCHEMA public TO bi_reader, ohne die der Nutzer nicht einmal die Tabellenliste sieht.
  4. GRANT SELECT ON ALL TABLES IN SCHEMA public TO bi_reader für den heutigen Stand.
  5. ALTER DEFAULT PRIVILEGES FOR ROLE app_owner IN SCHEMA public GRANT SELECT ON TABLES TO bi_reader für alles Künftige.

Schritt drei wird am häufigsten vergessen. Der Nutzer kann sich verbinden, sieht aber keine Tabellen, und die Fehlermeldung nennt das Schema, nicht die fehlende Berechtigung. Sequenzen brauchen für Schreibnutzer einen eigenen Grant, sonst scheitert das erste INSERT mit einer Seriennummer.

Was ist bei MySQL anders?

Bei MySQL gelten Rechte auf datenbank.* für alle Tabellen, auch künftige. Es gibt keine Default Privileges, weil es sie nicht braucht. Dafür sind Nutzer an einen Host gebunden, und ein Nutzer, der von überall kommen darf, heißt 'name'@'%'. Wer beide Engines mit demselben Werkzeug verwaltet, braucht zwei Sätze von Statements hinter derselben Oberfläche. Die drei Stufen bleiben gleich, ihre Umsetzung nicht.

In welcher Reihenfolge wird gelöscht?

Eine Datenbank mit Zusatznutzern lässt sich nicht einfach droppen, solange die Nutzer Rechte darin halten. Erst die Zusatznutzer entfernen, dann die Datenbank, dann die Besitzer-Rolle. Und vorher der Dump. Wer das in ein Werkzeug gießt, sollte die Reihenfolge festschreiben, weil sie beim dritten Mal per Hand jemand vergisst.

Wie Clusterward das kapselt

Auf der Service-Seite erzeugt ein Klick Datenbank, Rolle und Passwort in einem Schritt. Zusatznutzer bekommen eine der drei Stufen; die Statements dahinter widerrufen vor dem Erteilen und setzen Default Privileges für den Besitzer, in der richtigen Datenbank. Instanz-Admin gibt es nicht. Adoptierte Datenbanken werden per Login verifiziert und nie gedroppt. Die Details stehen unter Managed Datenbanken.

Fazit

Eine Datenbank pro Service ist das richtige Modell. Es braucht drei Dinge, die man nicht sieht: Default Privileges für künftige Tabellen, Revoke vor Grant, und den Verzicht auf Instanz-Admin für Anwendungsnutzer. Wer die drei einmal in ein Werkzeug gießt, muss sie nie wieder erklären.

Datenbanken für Ihre Services planen? Schreiben Sie uns, wie viele Services auf wie vielen Datenbanken laufen. Wir zeigen, wie Rollen und Rechte pro Service aussehen. Frage zu Datenbanken stellen →

Quellen und weiterführende Links

Häufige Fragen

  • Weil GRANT SELECT ON ALL TABLES nur für Tabellen gilt, die zum Zeitpunkt des GRANT existieren. Tabellen aus späteren Migrationen gehören dem Besitzer und bleiben unsichtbar. Abhilfe schafft ALTER DEFAULT PRIVILEGES FOR ROLE mit der Besitzer-Rolle: Dann erhält der Lesenutzer automatisch Rechte an allem, was künftig angelegt wird.