Articles tagged "postgres"

Docker-Gitea-Postgres Part 5: Posgres MD5- und SCRAM-Passwörter

Teil 5 einer Reihe über Gitea & Postgres im Docker-Container: Postgres MD5- und SCRAM-Passwörter

Hintergrund dieser Reihe ist das notwendige Update einer Docker-Gitea-Instanz und in diesem Rahmen auch das Major-Version-Upgrade der zugehörigen Postgres-Datenbank.

Postgres MD5- und SCRAM-Passwörter

Mit Version 10 hat Postgres Abschied von den alten und potentiell unsicheren MD5-Passwort-Hashes genommen. Ersatz dafür sind die sog. "SCRAM secrets". Das bedeutet aber auch, dass bei einem Major-Upgrade von 9 auf 10 (oder danach) alle Benutzer keine "passend" verschlüsselten Passwörter mehr haben.

Vorgeschichte

Nachdem Gitea nicht mehr starten bzw. dauerhauft laufen wollte, bin ich auf einigen Umwegen auf folgenden Log-Eintrag gestoßen:

root@docker:~ # docker logs gitea_db
[...]
FATAL:  password authentication failed for user "gitea"
DETAIL:  User "gitea" does not have a valid SCRAM secret.
    Connection matched pg_hba.conf line 100: "host all all all scram-sha-256"
[...]

Wenn man dies direkt im Container ausprobiert, kommt auch nur eine (wenig sagende) Fehlermeldung:

root@docker:~ # docker exec -it gitea_db bash
root@bash-5.1# psql -U gitea -h 172.31.3.2
  psql: error: connection to server at "172.31.3.2", port 5432 failed: FATAL:  password authentication failed for user "gitea"

SCRAM als Ersatz für MD5

Wenn man sich im Container umsieht, findet man auch die entsprechenden Stellen, an denen die Postgres-Instanz auf SCRAM konfiguriert wird;

root@bash-5.1# grep password_encryption /var/lib/postgresql/data/postgresql.conf 
#password_encryption = scram-sha-256    # scram-sha-256 or md5

root@bash-5.1# grep -v "^#\|^$" /var/lib/postgresql/data/pg_hba.conf 
local   all             all                                     trust
host    all             all             127.0.0.1/32            trust
host    all             all             ::1/128                 trust
local   replication     all                                     trust
host    replication     all             127.0.0.1/32            trust
host    replication     all             ::1/128                 trust
host all all all scram-sha-256

Natürlich ist das Ersetzen von MD5 durch einen stärkeren Algorithmus notwendig und zu begrüßen, letztendlich war es aber der Aufhänger für diese ganze Reihe von Artikeln ;)

Um jetzt zu sehen, welche Benutzer bereits ein SCRAM-Passwort haben, verbindet man sich mit der Datenbank und stellt eine Anfrage:

root@bash-5.1# psql -U gitea
psql (14.2)
Type "help" for help.

gitea=# SELECT rolname, rolpassword ~ '^SCRAM-SHA-256\$' AS scram_pw FROM pg_authid WHERE rolcanlogin;
 rolname | scram_pw 
---------+--------------
 gitea   | f
(1 row)

Man sieht, der Benutzer "gitea" hat offensichtlich noch kein SCRAM-Passwort ("f"alse).

Da es in diesem (Gitea-Docker-)Fall nur ein Benutzer ist, kann man das Passwort auch schnell von Hand setzen - wie es im .env, Ansible-Playbook, ... gesetzt wurde:

gitea=# \password gitea
Enter new password for user "gitea": 
Enter it again: 

Im Erfolgsfall gibt es keine weitere Ausgabe, stimmen die beiden Versuche nicht überein, kommentiert Postgres dies mit einem Passwords didn't match.. Die Datenbank-Commandline kann man per exit verlassen. Dann kann man auch nochmals kurz an der Commandline kontrollieren, ob der Benutzer nun ein SCRAM-Passwort hat:

root@bash-5.1# echo "SELECT rolname, rolpassword ~ '^SCRAM-SHA-256\$' AS scram_pw FROM pg_authid WHERE rolcanlogin;" | psql -U gitea
 rolname | scram_pw 
---------+--------------
 gitea   | t
(1 row)

Schluss

Nach dem Setzen des Passworts als SCRAM secret konnte sich der Gitea-Container wieder ohne Probleme an die neue Postgres-Instanz verbinden. Seit dem läuft Gitea und Postgres also wieder in aktueller Version!

Docker-Gitea-Postgres Part 4: Gitea startet nicht

Teil 4 einer Reihe über Gitea & Postgres im Docker-Container: Gitea startet nicht

Hintergrund dieser Reihe ist das notwendige Update einer Docker-Gitea-Instanz und in diesem Rahmen auch das Major-Version-Upgrade der zugehörigen Postgres-Datenbank.

Docker und Debugging - zwei Welten treffen aufeinander

Vorlauf

Bis zu diesem Schritt lief soweit alles problemlos ab:

  • Backup von Gitea - und auch dessen Restore auf einer anderen Maschine!
  • Backup der alten Postgres-9-Instanz
  • Testweiser Restore des Postgres-Backups auf einem Postgres-9 und Postgres-14

Gitea startet nicht mehr - oder doch?

Ein Versuch auf das Web-Frontend zuzugreifen, brachte verschiedene Fehler des Reverse-Proxys zu Tage. Ein Zugriff auf Gitea war aber nicht möglich.

Erst nach einiger Zeit und einigen docker ps -a fiel mir auf, dass der Gitea-Container sich offensichtlich immer wieder beendete - aber eben nicht sofort, sondern nach ca. einer Minuten. Er wurde dann wieder neu gestartet und nach einer weiteren Minute begann das Spiel von vorne. Von einem Blick in die Logs erhofft man sich ja Aufschluss, was da gerade vor sich geht:

root@docker:~ # docker logs gitea
Server listening on :: port 22.
Server listening on 0.0.0.0 port 22.
cmd/web.go:102:runWeb() [I] Starting Gitea on PID: 15
cmd/web.go:150:runWeb() [I] Global init
routers/init.go:107:GlobalInitInstalled() [I] Git Version: 2.30.3, Wire Protocol Version 2 Enabled
routers/init.go:110:GlobalInitInstalled() [I] AppPath: /usr/local/bin/gitea
routers/init.go:111:GlobalInitInstalled() [I] AppWorkPath: /app/gitea
routers/init.go:112:GlobalInitInstalled() [I] Custom path: /data/gitea
routers/init.go:113:GlobalInitInstalled() [I] Log path: /data/gitea/log
routers/init.go:114:GlobalInitInstalled() [I] Configuration file: /data/gitea/conf/app.ini
routers/init.go:115:GlobalInitInstalled() [I] Run Mode: Prod
Received signal 15; terminating.

Und das wiederholte sich immer und immer wieder. Die ersten beiden Zeilen kommen BTW vom OpenSSH-Server, welcher auch im Gitea-Container läuft.

Nach einiger Zeit (und Frust) bin ich dann wieder mal in den Container gegangen und hab dort nach weiteren Informationen gesucht. Und siehe da, im Gitea-Log-File stehen Informationen, die es - warum ich immer - nicht ins docker logs geschafft haben:

root@bash-5.1# cat /data/gitea/logs/gitea.log
[...]
routers/common/db.go:27:InitDBEngine() [I] ORM engine initialization attempt #8/10...
cmd/web.go:153:runWeb() [I] PING DATABASE postgres
routers/common/db.go:33:InitDBEngine() [E] ORM engine initialization attempt #8/10 failed. Error: pq: password authentication failed for user "gitea"
routers/common/db.go:34:InitDBEngine() [I] Backing off for 3 seconds
[...]

Warum zum Geier schafft es eine solch essentielle Fehlermeldung nicht in die docker logs?! Warum auch immer ich die Idee hat, mir im Container die Logs anzusehen - ohne diese Zeilen hätte ich vermutlich bald aufgegeben und wäre wieder auf die alte Postgres-Version zurückgegangen.

Was sagt denn die Datenbank dazu?

Mit diesem Hinweis schaute ich dann natürlich auch noch in die docker logs der Datenbank. Und siehe da:

root@bash-5.1# docker logs gitea_db
[...]
FATAL:  password authentication failed for user "gitea"
DETAIL:  User "gitea" does not have a valid SCRAM secret.
    Connection matched pg_hba.conf line 100: "host all all all scram-sha-256"
[...]

Der Fehler ist also weniger bei Gitea- als in der Postgres-Datenbank zu suchen - wobei "Fehler" hier natürlich auch nicht korrekt ist. Der alte, aus dem Backup stammende, gitea-Benutzer der Postgres-Datenbank hat einfach kein passend verschlüsseltes Passwort!

Das Setzen eines neue Passworts wird im Teil 5: Postgres MD5- und SCRAM-Passwörter beschrieben.

Fazit

Solche Dinge begegnen mir im Docker-Umfeld immer und immer wieder. Gar keine Logs, Logs nicht an der vermuteten/offiziellen Stelle, zu schnell rotiert Logs - Gründe dafür gibt's viele. Aber genau das sorgt dafür, dass man ohne tiefes Wissen gar keine Chance mehr hat, selbständig aus dieser Situation wieder herauszukommen. Docker wurde eingeführt, damit sich Admins mit gewissen Themen nicht mehr beschäftigen müss(t)en - aber dann eben einfach an einer Wand zerschellen, weil sie keine Ahnung (in diesem Fall rund um die Logs) haben...

Docker-Gitea-Postgres Part 3: Postgres Upgrade

Teil 3 einer Reihe über Gitea & Postgres im Docker-Container: Postgres Upgrade

Hintergrund dieser Reihe ist das notwendige Update einer Docker-Gitea-Instanz und in diesem Rahmen auch das Major-Version-Upgrade der zugehörigen Postgres-Datenbank.

Postgres

Major-Versionen, interne Struktur

Bis einschließlich zur Version 9 waren die zweistelligen Versionsnummern (die letzte Version war 9.6) die Haupt- oder "Major"-Versionen von Postgres. Dass die zweite Stelle im Allgemeinen bzw. bei vielen Projekten als "Minor"-Version bezeichnet wird, lief dem entgegen. Trotzdem war beim Sprung 9.5 auf 9.6 ein entsprechendes Vorgehen notwendig, da sich die interne Datenstruktur geändert hat (bzw. ändern hätte können) und die neuen Version nicht mit den alten Dateien auf der Platte umgehen konnte.

Ab der Version 10 sind jetzt auch bei Postgres Major-Versionssprünge an der ersten Stelle erkennbar, z.B. von 10 auf 11.

Die letzten Sprünge, an denen ein Upgrade nicht einfach durchgeführt werden konnte, waren folglich: 9.4 → 9.5 → 9.6 → 10 → 11 → 12 → 13 → 14

Welche Postgres-Versionen noch supported werden, kann man auf der "Versioning Policy"-Seite der Postgres-Dokumentation nachsehen.

Upgrade mit "pg_upgrade"

Seit einiger Zeit bringt Postgres ein Tool namens pg_upgrade mit, welches den alten und etwas aufwändigen Weg nicht mehr notwendig macht. Dieses Command ist allerdings auf einen Betrieb des Postgres-Servers auf einer "normalen" Linux-Installation (Host, VM, OS-Container, ...) ausgelegt und kann hier nicht helfen.

Als Alternative stehen allerdings mehrere Scripte, Tools und Docker-Container zur Verfügung, welche dieses Upgrade automagisch durchführen sollen (können). Da ich aber sowieso ein Backup der Datenbank machen möchte, bediene ich mich aus dem Skript des zweiten Teils dieser Reihe

Klassisches Upgrade

Das klassische Upgrade entspricht einem Backup, einem Upgrade des Postgres-Server und dem Restore. Die entsprechenden Schritte sind im zweiten Teil dieser Reihe (Postgres Backup) beschrieben.

Upgrade mit Docker

Ich habe mich inzwischen aus Sicherheitsgründen entschieden, das "alte" Daten-Docker-Volume erstmal aufzuheben und für die neue Postgres-Version ein neues Docker-Volume anzulegen. Erinnerung: Die neue Postgres-Version kann mit dem on-disk-format der alten Version leider nichts anfangen bzw. sie öffnen.

Folgende Schritte sind dann notwendig:

  • Den Gitea-Container stoppen, damit keine Schreibzugriffe (und später auch Lesezugriffe) auf die Datenbank kommen
  • Ein neues Volume (hier: gitea_db_14data, die 14 kommt von der Postgres-Major-Version) anlegen (lassen) - z.B. durch Anpassung im docker-compose.yml oder Ansible-Playbook oder wie auch immer die Container(-Volumes) angelegt werden/wurden.
  • Den Postgres-Container bzw. den Tag/Version im docker-compose.yml, Ansible-Playbook, ... entsprechend ändern und anlegen (lassen). In einem Fall habe ich ein Upgrade von 9-alpine auf 14-alpine durchgeführt und es hat problemlos funktioniert.
  • Den Datenbank-Container (und nur diesen) starten. Im Zweifelsfall beide Container starten und gitea wieder stoppen.
  • Anhand des zweiten Teiles dieser Reihe, Abschnitt "Restore im Docker" die Backup-Datei in den Container kopieren und wieder in die Postgres einspielen.
  • Den Gitea-Container wieder starten
  • Wen man sicher ist, dass es nicht mehr braucht wird, das Volume gitea_db_data (das alte Volume! Ohne "14"!) löschen.

Docker-Gitea-Postgres Part 2: Postgres Backup

Teil 2 einer Reihe über Gitea & Postgres im Docker-Container: Postgres Backup

Hintergrund dieser Reihe ist das notwendige Update einer Docker-Gitea-Instanz und in diesem Rahmen auch das Major-Version-Upgrade der zugehörigen Postgres-Datenbank.

Postgres

Backup allgemein

Um aus einer laufenden Postgres-Instanz (nahezu) alle Daten zu sichern, gibt es den Befehl pg_dumpall. Da das entstehende Backup reiner Text ist, wird es gleich in einem Rutsch mit xz komprimiert, damit es kleiner wird.

postgres@db:~ # pg_dumpall | xz >pg-backup.xz
[...]

Umgekehrt kann man auch einen Dump in eine (leere) Datenbank wieder einspielen :

postgres@db:~ # xzcat pg-backup.xz | psql -U <USERNAME> -d <DATABASE>

Backup im Docker

Da in den meisten Docker-Umgebungen nicht nur eine Postgres-Datenbank laufen wird, habe ich mir im Laufe der Zeit ein Script geschrieben, welches bei Übergabe des Container-Namens sich ein Backup auf den Host (konkret: ins aktuelle Verzeichnis) zieht:

root@docker:~ # .../postgres-backup.sh gitea_db
-rw-r--r-- 1 root root   115320 Apr 17 09:59 gitea_db_2022-04-17_09:59:23_postgres:9-alpine_9.6.24.xz

Im Backup-Dateinamen sind nacheinander Container-Name, Zeitstempel, Image-Name und Posgres-Version hinterlegt. So kann man auch z.B. im Rahmen eines Upgrades ;) nachvollziehen, mit bzw. auf Basis welcher Version das Backup angelegt wurde.

Das Script macht folgende Schritte:

  • (Zeile 3-13) Basierend auf dem übergebenen Namen wird die Container-ID ermittelt
  • (Zeile 14-19) Beginnt der zu Grunde liegende Image-Name nicht mit "postgres" wird das Script abgebrochen - es ist vermutlich kein Postgres-Container
  • (Zeile 21-29) Der Postgres-Username und -Version werden aus den Container-Umgebungsvariablen extrahiert
  • (Zeile 31-36) Der Backup-Dateinamen wird generiert und sichergestellt, dass es nicht bereits existiert
  • (Zeile 37-39) pg_dumpall wird aufgerufen, das Ergebnis durch xz komprimiert und ein ls -l auf die Datei angezeigt
#!/bin/ash

PGCONTAINER=$1
PGCONTID=$(docker ps -qf "name=^${PGCONTAINER}$")

if [ -z "$PGCONTID" ]; then
    echo "Container '${PGCONTAINER}' not found"
    exit 1
elif [ $(echo ${PGCONTID}| wc -m) -ne 13 ]; then
    echo "Unknown container id: ${PGCONTID}"
    exit 1
fi

DIMAGE=$(docker inspect ${PGCONTID} | jq '.[].Config.Image' | tr -d '"')
PGIMAGE=$(basename $DIMAGE | grep '\(^\|/\)postgres:')
if [ -z "${PGIMAGE}" ]; then
    echo "Not a Posgres image '${DIMAGE}'"
    exit 1
fi

export $(docker inspect ${PGCONTID} | jq '.[].Config.Env' | grep '"POSTGRES_USER\|"PG_VERSION' | tr -d '"' | sed -e 's/,$//')

if [ -z "${POSTGRES_USER}" ]; then
    echo "No Posgres user found"
    exit 1
elif [ -z "${PG_VERSION}" ]; then
    echo "No Postgres version number found"
    exit 1
fi

BACKUPFILE=${PGCONTAINER}_$(date +%F_%T)_${PGIMAGE}_${PG_VERSION}
if [ -f "${BACKUPFILE}.xz" ]; then
    echo "Backupfile exists"
    exit 1
fi

docker exec -it -w /tmp ${PGCONTID} sh -c "pg_dumpall -U ${POSTGRES_USER}" | xz >${BACKUPFILE}.xz

ls -l ${BACKUPFILE}.xz

Restore im Docker

Ich habe es nicht geschafft, die Backup-Datei vom Host aus direkt an einen psql in einen Container per Pipe zu übergeben. Falls hier jemand einen Tipp hat, gerne per Mail an mich ;)

Um also die Daten in den neu angelegten (oder umgezogenen oder...) Container zu bringen, kopiert man die Backup-Datei in den Container:

root@docker:~ # docker cp $BACKUPFILE gitea_db:/tmp/

Der zweite Schritt besteht dann darin, im Container das Backup wieder an Postgres zu verfüttern:

root@docker:~ # docker exec -it gitea_db bash
root@bash-5.1# psql -U gitea -d gitea < /tmp/gitea_db_2022-04-17_09:59:23_postgres:9-alpine_9.6.24.xz
root@bash-5.1# rm /tmp/gitea_db_2022-04-17_09:59:23_postgres:9-alpine_9.6.24.xz

Es macht natürlich keinen Sinn, das Backup im Container liegen zu lassen, es ist schließlich nur eine Kopie, daher wird es auch gleich wieder gelöscht.

Anmerkungen:

  • Meine Docker-Installationen laufen im Allgemeinen auf einem btrfs-Dateisystem. Daher ist Kopieren und Löschen ein Vorgang, der keinerlei Speicher benötigt. Läuft Docker allerdings mit irgendeiner Art von Images, dann ist dieser HD-/SSD-Speicherplatz bis zum nächsten Container-Anlegen und anschließendem "purge" verloren! Hier geht's nur um ca. 100kB, also wirklich nicht viel. Bei einer Multi-GiByte-Datenbank sieht das dann vielleicht anders aus!
  • Natürlich könnte man die Backup-Datei in ein temporäres Docker-Volume kopieren, dieses in den neuen Datenbank-Container einhängen und von dort das Restore machen. Der Aufwand war/ist mir aber zu groß.
  • Wer sofort seinen Speicher zurück möchte, der kann natürlich nach dem Restore den Container zerstören und neu anlegen. Dann ist der Speicher nach der nächsten Maintainance auch wieder frei.