Read-Only-Nutzer in PostgreSQL

Nur damit ich nicht immer wieder nachschlagen muss (oder, was wahrscheinlicher ist, einen Teil vergesse)

So wird in PostgreSQL ein universeller Read-Only-Nutzer richtig angelegt:

CREATE ROLE nutzer LOGIN PASSWORD 'passwort' NOINHERIT;
GRANT CONNECT ON DATABASE datenbank TO nutzer;
\c datenbank;

-- Zugriffe auf existierende Objekte
GRANT USAGE ON SCHEMA public TO nutzer;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO nutzer;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO nutzer;
GRANT EXECUTE ON ALL FUNCTIONS IN SCHEMA public TO nutzer;

-- Zugriffe auf zukünftige Objekte
ALTER DEFAULT PRIVILEGES FOR ROLE dbowner IN SCHEMA public GRANT SELECT ON TABLES TO nutzer;
ALTER DEFAULT PRIVILEGES FOR ROLE dbowner IN SCHEMA public GRANT SELECT ON SEQUENCES TO nutzer;
ALTER DEFAULT PRIVILEGES FOR ROLE dbowner IN SCHEMA public GRANT EXECUTE ON FUNCTIONS TO nutzer;

Plastische Verformung

Die rechte, leere Flasche zeigt, wie die ursprüngliche Form aussah – die linke, was eine unsachgemäße Lagerung von Ginger Ale auf dem Balkon bei Minusgraden bewirken kann:

image

Man kann noch hinzufügen, dass die linke Flasche ungeöffnet ist – der leere Raum oben ist allein durch die Volumenvergrößerung entstanden. Dem Ginger Ale selbst hat die Prozedur übrigens nicht geschadet…

Deutscher Vorentscheid 2014 (Unser Song für Dänemark)

Erste Runde:

01 – Das Gezeichnete Ich – Weil du da bist (1-2-1-2)
Wir sind immer noch gezeichnet von der beeindruckenden Atonalität des Beitrags. Haben Sänger und Chor eigentlich irgendwann mal in der gleichen Tonlage gesungen?

02 – Oceana – Thank You (2-2-2-2)
Verzweifelter (und letztlich vergeblicher) Versuch irgendwie an Tina Turner ranzukommen.

03 – Santiano – Fiddler on the deck (4-4-4-4)
Sicherlich keine anspruchsvolle, aber definitiv gute Laune verbreitende Musik – für den Grand Prix nicht die schlechteste Wahl.

04 – MariaMarie – Cotton Candy Hurricane (3-3-3-2)
Loreena McKennitt meets 80s. Skurille aber nicht uninteressante Mischung.

05 – The Baseballs – Mo Hotta Mo Betta (4-3-4-5)
Toller Beitrag für den ESC 1950, aber da hätten sie wirklich Gewinnchancen. Extra-Optik-Bonuspunkt von Kuni für das brennende Klavier.

06 – Elaiza – Is it right (3-3-2-2)
Schrammelmusik, für die wir mitten in der Woche definitiv zu wenig Alkohol intus hatten.

07 – Unheilig – Als wär’s das erste Mal (4-4-4-3)
Beim ganz genauen Zuhören klingt der Song … wie jeder andere Unheilig-Song. Der Mann hat ja wirklich eine schöne Stimme – schade, dass man sie immer nur in Kombination mit diesem pseudopoetischen Tralala hören kann.

08 – Madeline Juno – Like Lovers Do (2-2-2-2)
Ok, Lena ist wiederauferstanden – inklusive gesung-sprochenen Passagen und gehaucht-gebrüllten, leider in der Tonhöhe nicht ganz getroffenen Highlights.

Zweite Runde:

01 – Santiano – Niemals untergehn (3-4-3-4)
Melodisch flacher als der erste Song. Vom Text her zwar auch, aber den verstehen die nichtdeutschen Zuschauer beim ESC ja nicht…

02 – MarieMarie – Candy Jar (3-3-3-3)
Das 80s-Revival mit Harfe geht weiter – Der Song ist noch etwas eingängiger als der erste. Beim ESC werden aber beide vermutlich maximal Mittelfeld erreichen können.

03 – Elaiza – Fight Against Myself (2-1-2-1)
Und es wird weiter geschrammelt – jetzt mit Tuba, die das Ganze noch ein Runde provinzieller macht.

04 – Unheilig – Wir sind alle wie eins (3-3-3-3)
Ein weiterer typischer Unheilig-Song: schmalzig dahingedüdelte Wortgruppen die pseudobedeutungsschwanger vorgetragen werden.

Erklärung des Quadrupels (a-b-c-d):
a, b: Wertung von Nessi
c, d: Wertung von Kuni
a, c: akustische Wertung
b, d: optische Wertung
1 (schlecht) <= a, b, c, d <= 5 (gut)

Prozessor fehlt – ansonsten ist alles ok

Nur ein kurzer, sehr spezieller Blogeintrag für mich selbst, obwohl ich hoffe, es nie wieder machen zu müssen…

Wenn man mittels check_ipmi_sensor seine Server abfragt, kann es unter Umständen passieren, dass man folgende, beunruhigend wirkende Meldung bekommt:

IPMI Status: Critical [Presence = Critical ('Entity Absent')]

Hmm, offensichtlich fehlt irgendwas, aber was? Man erinnert sich kurz, dass das Skript auch den netten Schalter “-v” kennt, und wird durch folgende Zeilen nicht wirklich weiter erhellt:

[...]
FAN MOD 5A RPM = 2760.000000 (Status: Nominal)
Presence = 'Entity Present' (Status: Nominal)
Presence = 'Entity Absent' (Status: Critical)
Presence = 'Entity Present' (Status: Nominal)
Presence = 'Entity Present' (Status: Nominal)
Presence = 'Entity Present' (Status: Nominal)
Status = 'Processor Presence detected' (Status: Nominal)
[...]

Ok, was auch immer die fünf Präsenzen sind: eine fehlt. Also auf die Kiste selbst rauf und nachgucken. Das Werkzeug der Wahl vor Ort lautet ipmitool und mittels des Aufrufes

ipmitool sdr elist

erhält nun im Prinzip die gleiche Antwort wie zuletzt, nur anders formatiert und als definitiv weniger kritisch eingestuft (ok):

[...]
FAN MOD 5B RPM   | 39h | ok  |  7.1 | 2760 RPM
Presence         | 50h | ok  |  3.1 | Present
Presence         | 51h | ok  |  3.2 | Absent
Presence         | 54h | ok  | 10.1 | Present
Presence         | 55h | ok  | 10.2 | Present
Presence         | 56h | ok  | 26.1 | Present
Status           | 60h | ok  |  3.1 | Presence detected
[...]

Nun kennt aber auch ipmitool noch einen Verbose-Schalter und nun werden wir endlich schlauer.

[...]
Sensor ID              : FAN MOD 5B RPM (0x39)
 [...]

Sensor ID              : Presence (0x50)
 Entity ID             : 3.1 (Processor)
 Sensor Type (Discrete): Entity Presence
 States Asserted       : Entity Presence
                         [Present]
 Assertion Events      : Entity Presence
                         [Present]

Sensor ID              : Presence (0x51)
 Entity ID             : 3.2 (Processor)
 Sensor Type (Discrete): Entity Presence
 States Asserted       : Entity Presence
                         [Absent]
 Assertion Events      : Entity Presence
                         [Absent]

Sensor ID              : Presence (0x54)
 Entity ID             : 10.1 (Power Supply)
 Sensor Type (Discrete): Entity Presence
 States Asserted       : Entity Presence
                         [Present]
 Assertion Events      : Entity Presence
                         [Present]

[...]

Ja, die Kiste hat zwei Prozessorsockel, aber nur einen Prozessor, weshalb der fehlende zweite Prozessor zum CRITICAL führt. Die restlichen “Präsenzen” sind übrigens zwei Netzteile und ein “Disk Drive Bay” – hätte man auch selbst drauf kommen können.

Soweit, so gut – und wie schalten wir nun den Check aus? Kurzes Googlen sagt uns, dass wir die Finger davon lassen sollen, da ein Konfigurieren der Sensoreinstellung gefährlich ist und im schlimmsten Fall den nächsten Atomkrieg auslösen kann…

Es funktioniert folgendermaßen: Mittels

ipmi-sensors-config --checkout --filename config.txt

besorgt man sich einen aktuellen Auszug der Sensorkonfiguration (dauert ne Weile).

In diesen geht man nun mit einem Editor seiner Wahl rein und sucht den entsprechenden Sensor (leider werden in dieser Datei keine Sensor-IDs angegeben, so dass man sich an der bisherigen Reihenfolge der Sensoren orientieren muss).

Der entsprechende Auszug sieht in unserem Fall so aus:

[...]
Section 45_FAN_MOD_5B_RPM
	[...]
Section 46_Presence
	## Possible values: Yes/No
	Enable_All_Event_Messages       Yes
	## Possible values: Yes/No
	Enable_Scanning_On_This_Sensor  Yes
EndSection
Section 47_Presence
	## Possible values: Yes/No
	Enable_All_Event_Messages       Yes
	## Possible values: Yes/No
	Enable_Scanning_On_This_Sensor  Yes
EndSection
Section 48_Presence
[...]

Nun ändert man die beiden “Yes” in Section 47 in jeweils ein “No” und schreibt die Konfiguration zurück mittels

ipmi-sensors-config --commit --filename config.txt

Bei der nächsten IPMI-Anfrage guckt man dann wie gewollt durch die rosarote Brille und alles wird gut…

Spaß im Alltag bei der Datenbankwiederherstellung

tl;dr
Wenn man bei PostgreSQL einzelne gelöschte Datenbanken, deren Nutzer sowie alle damit zusammenhängenden Berechtigungen wiederherstellen möchte, reichen pg_dump und pg_dumpall --globals-only nicht aus. Zusätzlich muss man kreativ im vollständigen Dump von pg_dumpall rumgreppen.

Wie ich schon in mehreren Blogartikeln schrub, ist die Betätigung im Umfeld von PostgreSQL lehrreich und bisweilen überraschend. Das Folgende wird ein etwas längeres Posting, da der Kontext des beschriebenen Phänomens/Problems etwas umfangreicher ist.

Daneben sollte ich für einige kritische Leser evtl. noch vorher erwähnen, dass ich den ganzen Kram hier auch schreibe, damit ich mich später erinnere, warum ich es so gemacht habe…

Hintergrund

Wie sicherlich die meisten meiner Leser wissen, betreut meine Wenigkeit hauptberuflich (und das natürlich nur neben unzähligen anderen Dingen 😉 ) die zentralen PostgreSQL-Datenbankserver der Humboldt-Universität, auf denen Wissenschaftler, Projektmitarbeiter, usw. ihre Datenbanken ablegen können.

Auf PostgreSQL-Ebene wird das Ganze so umgesetzt, dass jeder Nutzer nach Beantragung ein oder mehrere Datenbanken (in den folgenden Beispielen “datenbank” genannt) mit je einem eigenen Datenbankaccount (im Folgenden “nutzer”) erhält. Zusätzlich kann es je nach Bedarf weitere Datenbankaccounts (im Folgenden “unternutzer”) mit optional eingeschränkten Zugriffsrechten geben.

Standardrechte in PostgreSQL

Bereits beim Anlegen der Datenbanken muss man tierisch aufpassen, da das Standardverhalten von PostgreSQL offensichtlich für andere Einsatzszenarien vorgesehen ist: auf eine neu angelegte Datenbank hat automatisch jeder, dem Server bekannte Nutzer Zugriff!

Um dies zu verhindern, lautet die erste Anweisung, die bei uns automatisch bei jeder neuen Datenbank ausgeführt wird:

REVOKE ALL ON DATABASE datenbank FROM PUBLIC;

Damit werden der magischen Rolle PUBLIC alle Rechte entzogen. Dass der Nutzer dennoch an seine Datenbank kommt, liegt daran, dass sein Account als Owner der Datenbank weiterhin alle Rechte besitzt.

Da dies jedoch nicht für die weiteren Accounts des Nutzers gilt, müssen für diese die Zugriffsberechtigungen (bei uns ebenfalls automatisch) nachgetragen werden:

GRANT CONNECT ON DATABASE datenbank TO unternutzer;

Die zweite Falle lauert eine Ebene tiefer: Auf das Standardschema namens public (nicht zu verwechseln mit der bereits genannten, gleichnamigen Rolle) darf jeder mit der Datenbank verbundene Nutzer (egal welchen Einschränkungen er sonst unterliegt) per default eigene Objekte (also Tabellen, Views, usw.) anlegen.

Also folgen bei uns den bereits genannten Befehlen beim Anlegen einer Datenbank noch:

\c datenbank;
REVOKE ALL ON SCHEMA public FROM PUBLIC;
GRANT ALL ON SCHEMA public TO nutzer;

…sowie für jeden einzelnen Unteraccount:

\c datenbank;
GRANT USAGE ON SCHEMA public TO unternutzer;

Soweit so gut – kommen wir zum Backup:

Logisches Backup/Restore im Allgemeinen

Bei PostgreSQL hat man, wenn man nur logische Backups betrachtet, die Wahl zwischen 2,5 Werkzeugen: pg_dump, pg_dumpall und pg_dumpall mit dem Schalter -g.

Ohne zu tief in die Details einzusteigen (die ich teilweise schon an anderer Stelle beschrieben habe), leisten die Werkzeuge folgendes:

  • pg_dump zieht eine einzelne Datenbank ab
  • pg_dumpall zieht den gesamten Cluster ab (Datenbanken und sonstige Objekte, z.B. Rollen)
  • pg_dumpall -g zieht nur die sonstigen Objekte (globals) ab

Wir lassen täglich alle genannten Werkzeuge laufen: je einmal pg_dumpall und pg_dumpall -g auf jedem unserer Cluster und dann noch einmal pg_dump für jede einzelne Datenbank.

Interessant ist nun die Frage, welches Backup wir zur Wiederherstellung verwenden und die wenig überraschende Antwort lautet: Kommt drauf an…

Wenn man den gesamten Cluster wiederherstellen muss, ist die Antwort leicht: man legt einen neuen Cluster an und spielt den pg_dumpall-Dump ein (kam bisher erfreulicherweise nur geplant bei großen Datenbankupdates vor).

Hat ein Nutzer nur eine Tabelle gelöscht, kann man mit dem pg_dump-Dump sehr selektiv nur diese wiederherstellen (kommt sehr selten vor).

Der mit Abstand häufigste Fall ist jedoch das komplette Wiederherstellen einer einzelnen Datenbank, gerne nachdem wir(!) sie gelöscht haben – nein, nur weil ein Nutzer sagt, dass er eine Datenbank nicht mehr braucht, heißt nicht, dass er sie nicht mehr braucht – und bisweilen merkt er das erst deutlich später. In diesem Fall existiert nicht nur die Datenbank nicht mehr; auch die Datenbankaccounts haben durch uns bereits das Zeitliche gesegnet.

Aus diesem Grund hilft dann auch der reine pg_dump-Dump nicht mehr weiter, da dieser die Existenz aller Datenbankaccounts voraussetzt.

Logisches Backup/Restore einzelner Datenbanken inkl. Nutzer

Naiver Ansatz: man greppt aus dem pg_dumpall -g (geht deutlich schneller als den pg_dumpall-Dump zu nehmen) alle CREATE- und ALTER-ROLE-Statements raus und führt diese vor dem Einspielen des Dumps aus. Dieser Ansatz hat aber leider zwei Nachteile: erstens muss man alle betroffenen Datenbankaccounts kennen und, viel schlimmer, einige Berechtigungen werden nicht wiederhergestellt. pg_dump speichert nämlich nur die Informationen inklusive Berechtigungen die sich innerhalb einer Datenbank befinden. Das CREATE-DATABASE-Statement legt pg_dump nur auf besonderen Wunsch und dann aber auch nur mit dem ganz oben beschriebenen Standardverhalten (alle haben Zugriff) an.

Der pg_dumpall-Dump enthält alle notwendigen Informationen; leider kann man aus diesem aber keine einzelnen Datenbanken wiederherstellen (es sei denn, man editiert von Hand in dem riesigen Dumpfile, bei uns im GB-Bereich, rum).

Als Lösung verwenden wir folgenden Ansatz: wir greppen im pg_dumpall-Dump nach allen GRANTS auf die wiederherzustellende Datenbank und greppen die CREATE-ROLE-Anweisungen der dabei gefundenen Datenbankaccounts dann aus dem pg_dumpall -g-Dump. Dann müssen wir nur das CREATE-DATABASE- sowie die GRANT- und REVOKE-Statements aus dem pg_dumpall-Dump holen.

Sobald wir PostgreSQL mit den gesammelten SQL-Anweisungen gefüttert haben, erhalten wir eine leere Datenbank samt aller Nutzer und deren Berechtigungen, die nur noch darauf wartet, mittels des pg_dump-Dumps befüllt zu werden.

Das beschriebene Zusammenklauben aller SQL-Anweisungen leistet das folgende Shellskript:

#!/bin/bash

# Nach allen Nutzern suchen, die Rechte
# auf der Datenbank haben...
for i in `\
	grep "^GRANT .* ON DATABASE $DATABASE TO" $PGDUMPALLFILE \
	| sed "s/^GRANT .* ON DATABASE $DATABASE TO \(.*\);/\1/" \
	`;
do
	# ... und zu diesen Nutzern die CREATE- und
	# ALTER-ROLE-Anweisungen holen
	fgrep -w "ROLE $i" $PGDUMPALLGLOBALFILE ;
done

# Die CREATE- und ALTER-DATABASE- sowie die zugehörigen
# REVOKE- und GRANT-Anweisungen holen
fgrep -w "DATABASE $DATABASE" $PGDUMPALLFILE;

Nur der Vollständigkeit halber – der anschließende Aufruf von pg_restore sieht folgendermaßen aus:

pg_restore -1 -d $DATABASE $PGDUMPFILE

Yep, PostgreSQL macht Spaß…

Das andere Beste aus zwei Welten

Wie ich schon irgendwie erwartet hatte, gefiel Uwe meine Regex-SQL-Mischung nicht.

Mit seiner Erlaubnis veröffentliche ich im Folgenden seinen (von mir reparierten 🙂 ) Gegenvorschlag:

select pers_name, sp_id, max(strikesequence) as strikesinarow from
( select *,
        length(
            unnest(
                string_to_array(
                    regexp_replace(
                        regexp_replace(
                            concat(
                                spiel.sp_w01_1, ',',
                                spiel.sp_w02_1 , ',',
                                spiel.sp_w03_1 , ',',
                                spiel.sp_w04_1 , ',',
                                spiel.sp_w05_1 , ',',
                                spiel.sp_w06_1 , ',',
                                spiel.sp_w07_1 , ',',
                                spiel.sp_w08_1 , ',',
                                spiel.sp_w09_1 , ',',
                                spiel.sp_w10_1 , ',',
                                spiel.sp_w10_2 , ',',
                                spiel.sp_w10_3 , ','
                            ), '10,', 'X', 'g'
                        ), '[^X]+', '-', 'g'
                    ), '-'
                )
            )
        ) as strikesequence
    from spiel natural join person
) as sub
group by pers_name, sp_id
order by pers_name, strikesinarow desc; 

Sein Ansatz ist definitiv kürzer und beim tippen deutlich weniger fehleranfällig, dafür aber IMHO nicht ganz so leicht nachvollziehbar.

Unbestritten sollte jedoch sein, dass weder seine noch meine Lösung in Produktivsystemen eingesetzt werden sollten…