Fix #41: Dokumente mitsichern #48
@@ -155,9 +155,20 @@ services:
|
||||
PGDATABASE: ${POSTGRES_DB:-betreuungs_cockpit}
|
||||
SICHERUNG_PASSWORT: ${SICHERUNG_PASSWORT:?SICHERUNG_PASSWORT fehlt in der .env}
|
||||
SICHERUNG_AUFBEWAHRUNG_TAGE: ${SICHERUNG_AUFBEWAHRUNG_TAGE:-30}
|
||||
SICHERUNG_DOKUMENTE_QUELLE: /dokumente
|
||||
TZ: Europe/Berlin
|
||||
volumes:
|
||||
- ./backups:/backups
|
||||
# Die Dokumente, ausdruecklich NUR LESEND.
|
||||
#
|
||||
# Der Sicherungsdienst laeuft unbeaufsichtigt per cron. Ein Dienst,
|
||||
# der die Daten zerstoeren kann, die er sichern soll, ist ein Risiko
|
||||
# ohne Gegenwert - ein Fehler im Skript oder ein kompromittierter
|
||||
# Behaelter wuerde beides auf einmal treffen.
|
||||
#
|
||||
# Zurueckspielen geschieht deshalb nicht hierher, sondern in ein
|
||||
# Verzeichnis unter ./backups; siehe scripts/wiederherstellung.sh.
|
||||
- minio-daten:/dokumente:ro
|
||||
- ./scripts/sicherung.sh:/sicherung.sh:ro
|
||||
- ./scripts/wiederherstellung.sh:/wiederherstellung.sh:ro
|
||||
entrypoint: ['/bin/sh', '-c']
|
||||
|
||||
+39
-8
@@ -731,13 +731,19 @@ Erwartete Ausgabe, in dieser Reihenfolge:
|
||||
|
||||
```
|
||||
Sicherung beginnt: /backups/cockpit_2026-08-22_1430.dump.enc
|
||||
Sicherung fertig (1.2M)
|
||||
Selbstpruefung bestanden.
|
||||
Datenbank gesichert (1.2M)
|
||||
Dokumente gesichert (18M)
|
||||
Selbstpruefung bestanden - Datenbank und Dokumente.
|
||||
```
|
||||
|
||||
Die **Selbstprüfung** ist der eigentliche Punkt: Das Skript entschlüsselt
|
||||
die eben geschriebene Datei sofort wieder. Schlägt das fehl, endet der
|
||||
Lauf mit einem Fehler statt mit einem stillen Scheinerfolg.
|
||||
beide Dateien sofort wieder und liest sie einmal durch. Schlägt das fehl,
|
||||
endet der Lauf mit einem Fehler statt mit einem stillen Scheinerfolg.
|
||||
|
||||
Fehlt die Zeile `Dokumente gesichert`, ist das Ablageverzeichnis nicht in
|
||||
den Sicherungsdienst eingehängt. Das Skript bricht dann ab und räumt den
|
||||
angefangenen Datenbankstand wieder weg — eine Sicherung, die nur die halbe
|
||||
Wahrheit enthält, aber vollständig aussieht, wäre schlimmer als gar keine.
|
||||
|
||||
### Und jetzt einmal zurückspielen
|
||||
|
||||
@@ -760,6 +766,25 @@ docker compose exec db psql -U cockpit probe \
|
||||
docker compose exec db dropdb -U cockpit probe
|
||||
```
|
||||
|
||||
Beim Zurückspielen packt das Skript auch das Dokumentenarchiv aus — nach
|
||||
`backups/dokumente-wiederhergestellt_<datum>/`. An ihren Platz schreibt es
|
||||
die Dateien **nicht**: Der Sicherungsdienst hat auf das Ablageverzeichnis
|
||||
bewusst nur Lesezugriff, und in ein laufendes MinIO hineinzuschreiben würde
|
||||
dessen Datenbestand beschädigen. Wie der letzte Handgriff aussieht, gibt
|
||||
das Skript am Ende selbst aus.
|
||||
|
||||
Für die Übung genügt ein Blick in das ausgepackte Verzeichnis:
|
||||
|
||||
```bash
|
||||
find backups/dokumente-wiederhergestellt_*/ -type f | head
|
||||
rm -rf backups/dokumente-wiederhergestellt_*
|
||||
```
|
||||
|
||||
> **Im Ernstfall gehört beides zusammen.** Eine Datenbank ohne die
|
||||
> zugehörigen Dateien ergibt eine Akte, deren Prüfsummen Dokumente
|
||||
> belegen, die es nicht mehr gibt. Spiel immer das Paar mit demselben
|
||||
> Zeitstempel zurück.
|
||||
|
||||
### Kontrolle
|
||||
|
||||
```bash
|
||||
@@ -767,12 +792,18 @@ ls -lh backups/
|
||||
# cockpit_....dump.enc und cockpit_....dump.enc.sha256
|
||||
```
|
||||
|
||||
> **Was das Backup nicht enthält:** den Schlüssel `ENC_KEY_V1` und die
|
||||
> Dateien im Objektspeicher. Für die Dateien:
|
||||
> **Was das Backup enthält:** die Datenbank *und* die Dokumente. Je Lauf
|
||||
> entstehen zwei verschlüsselte Dateien mit demselben Zeitstempel:
|
||||
>
|
||||
> ```bash
|
||||
> docker compose exec minio mc mirror lokal/betreuungs-cockpit /backups/dokumente
|
||||
> ```
|
||||
> cockpit_2026-09-01_0315.dump.enc <- Akte, Fristen, Buchungen
|
||||
> dokumente_2026-09-01_0315.tar.gz.enc <- Bescheide, Schreiben, Belege
|
||||
> ```
|
||||
>
|
||||
> **Was es nicht enthält:** den Schlüssel `ENC_KEY_V1`. Ohne ihn bleiben
|
||||
> auch aus einer vollständig zurückgespielten Sicherung die Diagnosen,
|
||||
> IBANs, Notizen und Dokumente unlesbar. Er gehört an die zwei Orte aus
|
||||
> Schritt 11.
|
||||
>
|
||||
> Und kopier die Sicherungen regelmäßig **weg vom Server** — ein Backup,
|
||||
> das neben dem Original liegt, hilft bei einem Serverausfall nicht.
|
||||
|
||||
+81
-18
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
# =============================================================================
|
||||
# Taegliche Sicherung der Datenbank
|
||||
# Taegliche Sicherung: Datenbank UND Dokumente
|
||||
# =============================================================================
|
||||
#
|
||||
# Laeuft im Behaelter "sicherung" per cron um 03:15 Uhr. Von Hand:
|
||||
@@ -10,17 +10,44 @@
|
||||
# Was passiert:
|
||||
# 1. pg_dump im eigenen Format (-Fc), damit einzelne Tabellen
|
||||
# wiederherstellbar bleiben.
|
||||
# 2. Verschluesselung mit AES-256 und PBKDF2 aus SICHERUNG_PASSWORT.
|
||||
# 2. Die abgelegten Dokumente als Archiv.
|
||||
# 3. Beides verschluesselt mit AES-256 und PBKDF2 aus SICHERUNG_PASSWORT.
|
||||
# Das Backup liegt oft auf fremdem Speicher - unverschluesselt waere
|
||||
# es ein Datenleck mit Ansage.
|
||||
# 3. SHA-256-Pruefsumme daneben.
|
||||
# 4. Staende, die aelter sind als SICHERUNG_AUFBEWAHRUNG_TAGE, entfernen.
|
||||
# 4. SHA-256-Pruefsumme je Datei.
|
||||
# 5. Staende, die aelter sind als SICHERUNG_AUFBEWAHRUNG_TAGE, entfernen.
|
||||
# Der jeweils erste Stand eines Monats bleibt stehen.
|
||||
#
|
||||
# WICHTIG: Dieses Backup enthaelt die verschluesselten Felder, aber NICHT
|
||||
# den Schluessel ENC_KEY_V1. Ohne ihn sind Diagnosen, IBANs und Notizen
|
||||
# auch aus einer wiederhergestellten Datenbank nicht lesbar. Den Schluessel
|
||||
# getrennt sichern - Passwortmanager oder ausgedruckt im Ordner.
|
||||
# ## Warum die Dokumente dazugehoeren
|
||||
#
|
||||
# Bis v1.11.0 sicherte dieses Skript nur die Datenbank. Nach einem
|
||||
# Plattenschaden waere die Akte vollstaendig wiederhergestellt worden -
|
||||
# mit allen Eintraegen, Verweisen und Pruefsummen, aber ohne eine einzige
|
||||
# Datei dahinter. Die Pruefsumme in der Akte haette dann ein Dokument
|
||||
# belegt, das es nicht mehr gibt. Gegenueber dem Betreuungsgericht ist das
|
||||
# der schlechteste denkbare Zustand.
|
||||
#
|
||||
# ## Warum das Ablageverzeichnis roh eingepackt wird
|
||||
#
|
||||
# Der naheliegende Weg waere "mc mirror" aus dem MinIO-Client. Der ist in
|
||||
# diesem Behaelter aber nicht vorhanden und muesste bei jedem Lauf aus dem
|
||||
# Netz geholt werden - eine Internetabhaengigkeit im Sicherungspfad ist
|
||||
# genau das, was man dort nicht will.
|
||||
#
|
||||
# Stattdessen wird das Datenverzeichnis direkt eingepackt. Das ist
|
||||
# vertretbar, weil MinIO ein Objekt erst vollstaendig unter .minio.sys/tmp
|
||||
# schreibt und dann an seinen Platz umbenennt: Ein laufender Upload liegt
|
||||
# also nie halbfertig im Archiv, er fehlt hoechstens. Bei einem Lauf um
|
||||
# 03:15 Uhr auf einem Einzelplatzsystem ist auch das unwahrscheinlich.
|
||||
#
|
||||
# Derselbe Weg deckt den Dateitreiber mit ab - dort liegen die Dateien
|
||||
# ebenfalls einfach im Verzeichnis.
|
||||
#
|
||||
# WICHTIG: Dieses Backup enthaelt die verschluesselten Felder und die
|
||||
# verschluesselten Dateien, aber NICHT den Schluessel ENC_KEY_V1. Ohne ihn
|
||||
# sind Diagnosen, IBANs, Notizen und Dokumente auch aus einer
|
||||
# wiederhergestellten Sicherung nicht lesbar. Den Schluessel getrennt
|
||||
# sichern - Passwortmanager oder ausgedruckt im Ordner.
|
||||
# =============================================================================
|
||||
|
||||
set -eu
|
||||
@@ -30,6 +57,11 @@ AUFBEWAHRUNG="${SICHERUNG_AUFBEWAHRUNG_TAGE:-30}"
|
||||
ZEITSTEMPEL="$(date +%Y-%m-%d_%H%M)"
|
||||
TAG_IM_MONAT="$(date +%d)"
|
||||
DATEI="$ZIEL/cockpit_${ZEITSTEMPEL}.dump.enc"
|
||||
DOKUMENTE_DATEI="$ZIEL/dokumente_${ZEITSTEMPEL}.tar.gz.enc"
|
||||
|
||||
# Wo die Dokumente liegen. Der Behaelter bekommt das Ablageverzeichnis
|
||||
# eingehaengt - siehe docker-compose.yml, Dienst "sicherung".
|
||||
DOKUMENTE_QUELLE="${SICHERUNG_DOKUMENTE_QUELLE:-/dokumente}"
|
||||
|
||||
if [ -z "${SICHERUNG_PASSWORT:-}" ]; then
|
||||
echo "$(date -Iseconds) FEHLER: SICHERUNG_PASSWORT ist nicht gesetzt. Abbruch." >&2
|
||||
@@ -50,17 +82,39 @@ pg_dump --format=custom --compress=6 --no-owner --no-privileges \
|
||||
GROESSE="$(du -h "$DATEI" | cut -f1)"
|
||||
sha256sum "$DATEI" > "$DATEI.sha256"
|
||||
|
||||
# Der jeweils erste Stand eines Monats bekommt eine Markierung und bleibt
|
||||
# beim Aufraeumen verschont.
|
||||
if [ "$TAG_IM_MONAT" = "01" ]; then
|
||||
touch "$DATEI.monatsstand"
|
||||
echo "$(date -Iseconds) Datenbank gesichert ($GROESSE)"
|
||||
|
||||
# --- Dokumente ---------------------------------------------------------------
|
||||
# Schlaegt dieser Teil fehl, bricht der ganze Lauf ab (set -e). Das ist
|
||||
# Absicht: Eine Sicherung, die nur die halbe Wahrheit enthaelt, aber
|
||||
# vollstaendig aussieht, ist schlimmer als gar keine - man verlaesst sich
|
||||
# darauf und merkt es erst im Ernstfall.
|
||||
if [ ! -d "$DOKUMENTE_QUELLE" ]; then
|
||||
echo "$(date -Iseconds) FEHLER: Das Ablageverzeichnis $DOKUMENTE_QUELLE ist nicht eingehaengt." >&2
|
||||
echo " Ohne die Dokumente ist die Sicherung unvollstaendig." >&2
|
||||
echo " Siehe docker-compose.yml, Dienst \"sicherung\"." >&2
|
||||
rm -f "$DATEI" "$DATEI.sha256"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$(date -Iseconds) Sicherung fertig ($GROESSE)"
|
||||
# "tar" ohne Leitung an einen Verbraucher, der frueh schliesst - sonst
|
||||
# SIGPIPE, siehe die Anmerkung zur Selbstpruefung weiter unten.
|
||||
tar -czf - -C "$DOKUMENTE_QUELLE" . | openssl enc -aes-256-cbc -pbkdf2 -iter 200000 -salt -pass env:SICHERUNG_PASSWORT -out "$DOKUMENTE_DATEI"
|
||||
|
||||
DOK_GROESSE="$(du -h "$DOKUMENTE_DATEI" | cut -f1)"
|
||||
sha256sum "$DOKUMENTE_DATEI" > "$DOKUMENTE_DATEI.sha256"
|
||||
|
||||
echo "$(date -Iseconds) Dokumente gesichert ($DOK_GROESSE)"
|
||||
|
||||
# Der jeweils erste Stand eines Monats bekommt eine Markierung und bleibt
|
||||
# beim Aufraeumen verschont. Beide Teile, sonst faellt die Haelfte weg.
|
||||
if [ "$TAG_IM_MONAT" = "01" ]; then
|
||||
touch "$DATEI.monatsstand" "$DOKUMENTE_DATEI.monatsstand"
|
||||
fi
|
||||
|
||||
# --- Aufraeumen --------------------------------------------------------------
|
||||
ENTFERNT=0
|
||||
for alt in "$ZIEL"/cockpit_*.dump.enc; do
|
||||
for alt in "$ZIEL"/cockpit_*.dump.enc "$ZIEL"/dokumente_*.tar.gz.enc; do
|
||||
[ -e "$alt" ] || continue
|
||||
[ -e "$alt.monatsstand" ] && continue
|
||||
|
||||
@@ -87,9 +141,18 @@ KOPF="$(openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
|
||||
-pass env:SICHERUNG_PASSWORT -in "$DATEI" 2>/dev/null \
|
||||
| dd bs=1 count=5 2>/dev/null || true)"
|
||||
|
||||
if [ "$KOPF" = "PGDMP" ]; then
|
||||
echo "$(date -Iseconds) Selbstpruefung bestanden."
|
||||
else
|
||||
echo "$(date -Iseconds) FEHLER: Die Sicherung laesst sich nicht entschluesseln!" >&2
|
||||
if [ "$KOPF" != "PGDMP" ]; then
|
||||
echo "$(date -Iseconds) FEHLER: Der Datenbankstand laesst sich nicht entschluesseln!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Dasselbe fuer die Dokumente: entschluesseln und das Archiv auflisten
|
||||
# lassen. "tar -tzf" liest die ganze Datei und meldet jeden Schaden - das
|
||||
# ist bei den Groessenordnungen hier bezahlbar und sagt mehr aus als ein
|
||||
# Blick auf die ersten Bytes.
|
||||
if ! openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 -pass env:SICHERUNG_PASSWORT -in "$DOKUMENTE_DATEI" 2>/dev/null | tar -tzf - >/dev/null 2>&1; then
|
||||
echo "$(date -Iseconds) FEHLER: Das Dokumentenarchiv laesst sich nicht lesen!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "$(date -Iseconds) Selbstpruefung bestanden - Datenbank und Dokumente."
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
# ausdruecklich nach, weil dabei der aktuelle Datenbestand ueberschrieben
|
||||
# wird.
|
||||
#
|
||||
# ZWEI DINGE, DIE MAN VORHER WISSEN MUSS:
|
||||
# DREI DINGE, DIE MAN VORHER WISSEN MUSS:
|
||||
#
|
||||
# 1. Die Sicherung enthaelt die anwendungsseitig verschluesselten Felder,
|
||||
# aber NICHT den Schluessel. Ohne den passenden ENC_KEY_Vn aus der .env
|
||||
@@ -20,6 +20,14 @@
|
||||
# das hier einmal im Jahr auf einer Testdatenbank aus, bevor du es im
|
||||
# Ernstfall zum ersten Mal brauchst. Siehe README, Abschnitt
|
||||
# "Wiederherstellung ueben".
|
||||
#
|
||||
# 3. Die Dokumente werden NICHT selbsttaetig an ihren Platz geschrieben.
|
||||
# Dieses Skript packt sie nach ./backups aus und sagt, wie es
|
||||
# weitergeht. Grund: Der Sicherungsdienst hat auf das Ablageverzeichnis
|
||||
# bewusst nur Lesezugriff (siehe docker-compose.yml), und in ein
|
||||
# laufendes MinIO hineinzuschreiben wuerde dessen Datenbestand
|
||||
# beschaedigen. Der letzte Handgriff bleibt beim Menschen - er ist ein
|
||||
# Einzeiler und im Klartext beschrieben.
|
||||
# =============================================================================
|
||||
|
||||
set -eu
|
||||
@@ -87,7 +95,44 @@ openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
|
||||
| pg_restore --clean --if-exists --no-owner --no-privileges \
|
||||
--dbname="${PGDATABASE:-postgres}" --exit-on-error
|
||||
|
||||
echo "$(date -Iseconds) Wiederherstellung abgeschlossen."
|
||||
echo "$(date -Iseconds) Datenbank wiederhergestellt."
|
||||
|
||||
# --- Dokumente ---------------------------------------------------------------
|
||||
# Zu "cockpit_2026-09-01_0315.dump.enc" gehoert
|
||||
# "dokumente_2026-09-01_0315.tar.gz.enc" - gleicher Zeitstempel, anderer
|
||||
# Name. Fehlt sie, stammt die Sicherung aus einer Fassung vor v1.11.1.
|
||||
ZEITSTEMPEL="$(basename "$DATEI" | sed -n 's/^cockpit_\(.*\)\.dump\.enc$/\1/p')"
|
||||
DOKUMENTE_DATEI="$(dirname "$DATEI")/dokumente_${ZEITSTEMPEL}.tar.gz.enc"
|
||||
AUSPACKZIEL="$(dirname "$DATEI")/dokumente-wiederhergestellt_${ZEITSTEMPEL}"
|
||||
|
||||
if [ ! -f "$DOKUMENTE_DATEI" ]; then
|
||||
echo ""
|
||||
echo "WARNUNG: Zu dieser Sicherung gibt es kein Dokumentenarchiv."
|
||||
echo " Erwartet wurde: $DOKUMENTE_DATEI"
|
||||
echo " Sicherungen aus Fassungen vor v1.11.1 enthalten nur die"
|
||||
echo " Datenbank. Die Akte ist dann vollstaendig, die Dateien"
|
||||
echo " dahinter fehlen."
|
||||
else
|
||||
if [ -f "$DOKUMENTE_DATEI.sha256" ]; then
|
||||
echo "Pruefsumme des Dokumentenarchivs wird geprueft ..."
|
||||
if sha256sum -c "$DOKUMENTE_DATEI.sha256" >/dev/null 2>&1; then
|
||||
echo " in Ordnung."
|
||||
else
|
||||
echo "FEHLER: Die Pruefsumme des Dokumentenarchivs stimmt nicht." >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$AUSPACKZIEL"
|
||||
openssl enc -d -aes-256-cbc -pbkdf2 -iter 200000 \
|
||||
-pass env:SICHERUNG_PASSWORT -in "$DOKUMENTE_DATEI" \
|
||||
| tar -xzf - -C "$AUSPACKZIEL"
|
||||
|
||||
ANZAHL="$(find "$AUSPACKZIEL" -type f | wc -l)"
|
||||
echo "$(date -Iseconds) Dokumente ausgepackt: $ANZAHL Dateien nach"
|
||||
echo " $AUSPACKZIEL"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Naechste Schritte:"
|
||||
echo " 1. Die SQL-Skripte erneut ausfuehren (Volltextsuche und Audit-Schutz):"
|
||||
@@ -95,6 +140,21 @@ echo " docker compose exec app npm run db:sql"
|
||||
echo " 2. Pruefen, ob ENC_KEY_V1 in der .env zu diesem Bestand passt -"
|
||||
echo " ohne den richtigen Schluessel bleiben die verschluesselten"
|
||||
echo " Felder unlesbar."
|
||||
echo " 3. In der Anwendung eine betreute Person oeffnen und nachsehen, ob"
|
||||
echo " die Diagnosen im Klartext erscheinen. Erst dann ist die"
|
||||
echo " 3. Die Dokumente an ihren Platz bringen. Dazu MinIO anhalten, das"
|
||||
echo " Datenverzeichnis ersetzen und wieder starten - auf dem Host,"
|
||||
echo " nicht in diesem Behaelter:"
|
||||
echo ""
|
||||
echo " docker compose stop minio app"
|
||||
echo " docker run --rm \\"
|
||||
echo " -v betreuungs-cockpit_minio-daten:/ziel \\"
|
||||
echo " -v \"\$PWD/backups:/quelle:ro\" alpine \\"
|
||||
echo " sh -c 'rm -rf /ziel/* /ziel/..?* && cp -a /quelle/dokumente-wiederhergestellt_*/. /ziel/'"
|
||||
echo " docker compose up -d"
|
||||
echo ""
|
||||
echo " Steht mehr als ein ausgepacktes Verzeichnis unter ./backups,"
|
||||
echo " vorher das richtige auswaehlen statt des Sternchens."
|
||||
echo ""
|
||||
echo " 4. In der Anwendung eine betreute Person oeffnen und nachsehen, ob"
|
||||
echo " die Diagnosen im Klartext erscheinen. Dann ein Dokument"
|
||||
echo " herunterladen. Erst wenn beides klappt, ist die"
|
||||
echo " Wiederherstellung wirklich gelungen."
|
||||
|
||||
@@ -132,3 +132,90 @@ describe('Skripte mit pipefail', () => {
|
||||
).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Die Sicherung muss die Dokumente enthalten - nicht nur die Datenbank.
|
||||
*
|
||||
* Bis v1.11.0 sicherte scripts/sicherung.sh ausschliesslich die Datenbank.
|
||||
* Nach einem Plattenschaden waere die Akte vollstaendig wiederhergestellt
|
||||
* worden - mit allen Eintraegen, Verweisen und Pruefsummen, aber ohne eine
|
||||
* einzige Datei dahinter. Die Pruefsumme in der Akte haette dann ein
|
||||
* Dokument belegt, das es nicht mehr gibt.
|
||||
*
|
||||
* Aufgefallen ist das nicht durch einen Test, sondern beim Nachsehen, wo
|
||||
* die Daten eigentlich liegen. Die folgenden Pruefungen lesen deshalb den
|
||||
* Skripttext: Sie koennen die Sicherung nicht ausfuehren - dafuer braeuchte
|
||||
* es Postgres, MinIO und einen Behaelter -, aber sie schlagen an, sobald
|
||||
* einer der Bausteine wieder herausfaellt.
|
||||
*/
|
||||
describe('Sicherung umfasst die Dokumente', () => {
|
||||
const sicherung = readFileSync('scripts/sicherung.sh', 'utf8')
|
||||
const wiederherstellung = readFileSync('scripts/wiederherstellung.sh', 'utf8')
|
||||
|
||||
it('packt das Ablageverzeichnis ein', () => {
|
||||
expect(sicherung).toMatch(/tar -czf - -C "\$DOKUMENTE_QUELLE"/)
|
||||
})
|
||||
|
||||
it('verschlüsselt das Archiv mit demselben Verfahren wie den Datenbankstand', () => {
|
||||
// Zwei Aufrufe: einer fuer den Dump, einer fuer die Dokumente.
|
||||
const verschluesselt = sicherung.match(/openssl enc -aes-256-cbc -pbkdf2 -iter 200000 -salt/g)
|
||||
expect(verschluesselt).toHaveLength(2)
|
||||
})
|
||||
|
||||
it('legt zu beiden Teilen eine Prüfsumme', () => {
|
||||
expect(sicherung).toMatch(/sha256sum "\$DATEI"/)
|
||||
expect(sicherung).toMatch(/sha256sum "\$DOKUMENTE_DATEI"/)
|
||||
})
|
||||
|
||||
it('bricht ab, wenn das Ablageverzeichnis nicht eingehängt ist', () => {
|
||||
// Eine Sicherung, die nur die halbe Wahrheit enthaelt, aber
|
||||
// vollstaendig aussieht, ist schlimmer als gar keine.
|
||||
expect(sicherung).toMatch(/if \[ ! -d "\$DOKUMENTE_QUELLE" \]/)
|
||||
expect(sicherung).toMatch(/rm -f "\$DATEI" "\$DATEI\.sha256"/)
|
||||
})
|
||||
|
||||
it('prüft am Ende beide Teile, nicht nur den Datenbankstand', () => {
|
||||
expect(sicherung).toMatch(/tar -tzf -/)
|
||||
expect(sicherung).toMatch(/Datenbank und Dokumente/)
|
||||
})
|
||||
|
||||
it('räumt alte Dokumentenarchive genauso auf wie alte Datenbankstände', () => {
|
||||
expect(sicherung).toMatch(/dokumente_\*\.tar\.gz\.enc/)
|
||||
})
|
||||
|
||||
it('verschont beim Monatsstand beide Teile', () => {
|
||||
expect(sicherung).toMatch(/touch "\$DATEI\.monatsstand" "\$DOKUMENTE_DATEI\.monatsstand"/)
|
||||
})
|
||||
|
||||
it('holt die Dokumente beim Zurückspielen mit zurück', () => {
|
||||
expect(wiederherstellung).toMatch(/DOKUMENTE_DATEI=/)
|
||||
expect(wiederherstellung).toMatch(/tar -xzf - -C "\$AUSPACKZIEL"/)
|
||||
})
|
||||
|
||||
it('sagt beim Zurückspielen deutlich, wenn kein Dokumentenarchiv dabei ist', () => {
|
||||
// Sonst haelt man eine halbe Wiederherstellung fuer eine ganze.
|
||||
expect(wiederherstellung).toMatch(/kein Dokumentenarchiv/)
|
||||
})
|
||||
|
||||
it('schreibt die Dokumente nicht selbst an ihren Platz', () => {
|
||||
// Der Sicherungsdienst hat auf das Ablageverzeichnis nur Lesezugriff.
|
||||
// In ein laufendes MinIO hineinzuschreiben wuerde dessen Datenbestand
|
||||
// beschaedigen - der letzte Handgriff bleibt beim Menschen.
|
||||
expect(wiederherstellung).toMatch(/docker compose stop minio app/)
|
||||
})
|
||||
})
|
||||
|
||||
/**
|
||||
* Der Sicherungsdienst darf die Daten, die er sichert, nicht zerstoeren
|
||||
* koennen.
|
||||
*
|
||||
* Er laeuft unbeaufsichtigt per cron. Ein Fehler im Skript oder ein
|
||||
* kompromittierter Behaelter wuerde sonst Sicherung und Bestand auf einmal
|
||||
* treffen - genau der Fall, gegen den eine Sicherung da ist.
|
||||
*/
|
||||
describe('Sicherungsdienst greift nur lesend auf die Dokumente zu', () => {
|
||||
it('hängt das Ablageverzeichnis mit :ro ein', () => {
|
||||
const compose = readFileSync('docker-compose.yml', 'utf8')
|
||||
expect(compose).toMatch(/minio-daten:\/dokumente:ro/)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user