Backup in die Cloud
Verteilt? aber sicher!
Eines ist sicher, meine Daten sind sicher. So sicher, dass ich genau dann, wenn ich sie brauche, eventuell keinen Zugriff darauf habe:
- Im Urlaub im Ausland, wenn meine Papiere gestohlen wurden.
- Ein Stromausfall, der meinen Server außer Gefecht setzt, er nicht mehr fehlerfrei startet und ich keinen Zugriff auf den Fileserver mehr bekomme.
- Oder noch schlimmer: ein Unglück Zuhause wie Feuer oder Hochwasser, das meinen Server zerstört.
- Denkbar ist auch, dass Daten aus Versehen gelöscht wurden, was vielleicht noch nicht einmal bemerkt wurde.
Für letzteren Fall läuft auf meinem Fileserver drei Mal täglich ein Skript, welches ein Snapshot Backup erzeugt, ohne wirklich viel Platz zu benötigen: Das allseits bekannte „rsync“ – das Schweizer Taschenmesser unter Sync- bzw. Kopierprogrammen – ist in der Lage, per Hardlink Kopien im Filesystem anzulegen, welche keinen zusätzlichen Speicherplatz belegen. Es gibt natürlich viele Programme die Backups erzeugen können, aber ein stabiles selbstgeschriebenes Skript hat für mich immer noch einen gewissen Vorteil: Es funktioniert, überall. Ohne dass ein Programm-Update daran eventuell rumpfuscht, es nicht mehr unterstützt wird, eine neue Version ein Upgrade oder sogar eine Migration erfordern würde.
Verschlüsseltes Backup mit GoCryptFS
Dieser Artikel ist zugegebenermaßen entstanden, weil ich mein seit Jahr(zehnt)en installiertes System einer längst überfälligen Generalüberholung unterzog. Zur Verschlüsselung meiner persönlichen Daten kam bisher EncFS zum Einsatz, welches 2014 in einem Audit einige Sicherheitslücken offenbarte, von denen einige immer noch nicht behoben sind (https://vgough.github.io/encfs/). Meine Recherche nach einer Alternative führte mich zu gocryptfs (https://github.com/rfjakob/gocryptfs-website).
Gocryptfs auf der anderen Seite hielt (bisher) einem 2-Tages Audit stand. EncFS und gocryptfs verwenden das gleiche Konzept – ein "virtuell" ver- / entschlüsseltes Dateisystem wird analog einem Mount-Befehl in das Dateisystem eingehängt. Beide bieten eine „-reverse“ Option, welche (unverschlüsselte) Dateien verschlüsselt in ein Dateisystem einbinden. Diese Option ist prädestiniert für das verschlüsselte Backup von Dateien, welche individuell und nur Bedarfsorientiert gesichert werden sollen.
LUKS und GoCryptFS
Ein früherer Defekt einer nagelneuen 4TB Disk innerhalb der Garantie zog aufgrund des Hardware-Fehlers einen zwei Wochen dauernden Löschvorgang der Disk nach sich - wegen der Garantie musste ich die Disk ja zurücksenden und konnte sie nicht einfach vernichten. In Kombination mit den Sicherheitsbedenken des Einsatzes von EncFS für Backup-Szenarien in Cloud-Speicher ist demzufolge die transparente Vollverschlüsselung aller Daten auf den Festplatten mittels LUKS und der Einsatz von gocryptfs naheliegend.
Mein altes Backup-Skript kopierte die bisher mittels EncFS im Dateisystem verschlüsselten Dateien 1:1 in die Cloud. Mit der LUKS Vollverschlüsselung der Festplatte oder dessen (RAID-) Partitionen stehen aber keine verschlüsselten Dateien zum Kopieren mehr zur Verfügung - im Dateisystem selbst liegen sie ja nur entschlüsselt vor. Gocryptfs erzeugt mit der Option -reverse ein virtuelles Filesystem, welches die Daten online verschlüsselt ins Filesystem einbindet, ohne diese erneut auf der Festplatte zu speichern.
Verschlüsseln ohne Passwortabfrage
Das Passwort zum Ver- und Entschlüsseln der Disks/Partitionen sollte nicht auf der zu verschlüsselnden Festplatte selbst gespeichert werden. Damit der Server bei einem Reboot trotzdem ohne Passwortabfrage automatisch hochfährt, wird das Passwort z.B. von einem USB-Stick gelesen. Dieses kann in einer Datei oder auch versteckt in den ersten ungenutzten Blocks eines Devices außerhalb des Dateisystems gespeichert sein. Da in meinem Boot-Vorgang das Netzwerk schon zur Verfügung steht, wenn die Dateisysteme mit den User-Daten eingehängt werden, lese ich das GoCryptFS Passwort per SSH von einem anderen Server oder Gerät ein, sodass der USB-Stick selbst nicht auf meinem Backup-Server erreichbar ist.
Ich habe ein Bootscript erstellt, welches den LUKS-Schlüssel zum Booten meines XEN-Servers auf mehreren Devices (USB-Sticks oder Festplatten) sucht. Etwas Paranoid? Vielleicht, auch USB-Sticks können mal kaputtgehen oder ein Disaster Recovery des XEN-Server anstehen.
Datensicherheit
Folgende Aspekte sind jetzt also berücksichtigt:
- Mehrfache tägliche Datensicherung schützt vor versehentlichem Löschen
- Vollverschlüsselung schützt persönliche Daten auf den Festplatten
- Passwörter zum Entschlüsseln der Festplatten sind auf unabhängiges System bzw. Device wie USB-Sticks ausgelagert
- Backup der Daten liegt verschlüsselt in der Cloud vor
- Disaster Recovery ist von überall mit einem beliebigen PC möglich, der meinen ebenfalls verschlüsselten Live-USB Stick mit MX-Linux bootet
- RAID Konfiguration zum Schutz vor Festplattenfehlern
Trotz RAID gilt: "Kein Backup - kein Mitleid" ;-)
Cloud Backup
Wenden wir uns dem Thema Cloud-Backup zu. Wir haben vermutlich alle (erzwungenermaßen) bereits Accounts bei mehreren Cloud-Providern wie z.B Google, Microsoft, T-Online um nur einige zu nennen. Viele bieten auch mehr oder weniger kostenlosen oder zusätzlichen Cloud-Speicher an, der selten Verwendung findet. Warum sollten wir also diesen allzeit und weltweit zur Verfügung stehenden Datenspeicher nicht dafür nutzen, unsere wichtigen Daten dort verschlüsselt zu speichern? Dem steht nur die durch dezentrale Zersplitterung entstehende Aufwand zur Beherrschung des Datenchaos entgegen. Doch dem kann abgeholfen werden.
Die Idee ist, dass wir die vorhandenen unterschiedlichen Cloud-Speicher für ein Backup der wichtigen persönlichen Dateien nutzen. Dieser lässt ich normalerweise über Protokolle wie webdav oder SSH/SFTP auf dem eigenen Rechner einbinden. Zur Synchronisierung der Dateien in die Cloud kommt rsync zum Einsatz. Entweder in Kombination mit SSH, oder per webdav werden die Daten per gocryptfs verschlüsselt und in die Cloud kopiert.
Die benötigten Tools hierfür stehen alle als kostenlose Standard-Tools zur Verfügung: webdav, ssh, shell-skript, du, mount, find, rsync und gocryptfs.
Um die in der Cloud verschlüsselten Daten bei Bedarf wieder zu lesen, werden diese per webdav oder sshfs auf einem Rechner gemountet und anschließend mittels gocryptfs wieder entschlüsselt in das lokale Dateisystem eingebunden. Das kann sogar auf einem Live-Linux und damit auch auf einem USB-Stick erfolgen, den ich am Schlüsselbund immer parat habe.
GoCryptFS
Gocryptfs ist einfach zu benutzen. Man verschlüsselt ein vorhandenes Verzeichnis SourceDir wie folgt:
$ mkdir Encrypted $ gocryptfs -reverse -config ConfigFile -init SourceDir $ gocryptfs -reverse -config ConfigFile SourceDir Encrypted
Im Encrypted Verzeichnis sind jetzt alle Daten aus SourceDir verschlüsselt abgebildet. Eine Änderung der verschlüsselten Dateien selbst ist nicht möglich, Encrypted ist als Abbild von SourceDir nur ReadOnly.
Die Dateien aus Encrypted können entschlüsselt werden mit:
$ mkdir Decrypted $ gocryptfs -config ConfigFile Encrypted Decrypted
Die Daten stehen jetzt wieder entschlüsselt im Verzeichnis Decrypted zur Verfügung.
Lösung
Etwas Struktur, der natürliche Feind von Chaos
Trivial ist anders: Verteile individuelle Verzeichnisse zu verschiedenen Cloud-Anbietern.
KISS – „Keep it small and simple“ hilft auch hier. Bringe Ordnung ins vermeintliche Chaos, und die angestrebte Usability und Stabilität der Lösung.
Wie sieht die Lösung aus?
- Der Cloud-Speicher wird per SSH oder WebDav eingebunden
- Die individuellen Daten-Verzeichnisse werden (temporär) in einem Backup-Verzeichnis gebündelt (ReadOnly)
- Das Backup-Verzeichnis wird GoCryptFS -reverse verschlüsselt
- Das verschlüsselte Backup-Verzeichnis wird mit rsync in den Cloud-Speicher synchronisiert
Der Mount-Befehl stellt mittels der Optionen –bind und -ro sämtliche Daten-Verzeichnisse in einem Backup-Verzeichnis zusammen. Gocryptfs verschlüsselt dieses Verzeichnis mit der Option –reverse. Rsync kopiert schließlich sämtliche Dateien direkt in den Cloud-Speicher (-e ssh), oder in das per WebDav eingebundene Cloud-Verzeichnis.
Ist-Situation
Normalerweise sind die persönlichen Daten aller Nutzer eines Servers über mehrere Verzeichnisse verstreut. Außerdem müssen nur einige Verzeichnisse in die Cloud gesichert werden, nicht alle.
Eine einfache Konfiguration für ein zentrales CryptFS-Backup-Cloud Skript bildet die Grundlage für Usability und Stabilität (robust und wartungsarm) der Lösung. Da nur Standard-Protokolle und Programme verwendet werden, ist die Lösung extrem portabel und kann z.B. auf einem Rasperry, einem Linux-SAT-Receiver und anderen vorhandenen Devices implementiert werden.
Die Cloud-Backup Konfiguration muss folgende Informationen verknüpfen:
- Cloud-Speicher mit zugehörigem Account
- lokale Datenverzeichnisse der Benutzer
Für jeden Anwender bzw. dessen Accounts bei Cloud-Anbietern wird definiert, welche Verzeichnisse in dessen Cloud-Speicher synchronisiert werden sollen.
That’s it – sounds easy, is easy – and rock stable!
Konfiguration
Die individuellen Konfigurationen sind in sehr einfachen (Shell-) Skripten z.B. unter /etc/cryptfs-backup-cloud hinterlegt.
Konfiguration der Backup-Verzeichnisse, hier am Beispiel MagentaCloud von T-Online für Benutzer neobiker:
$ mkdir /etc/cryptfs-backup-cloud $ cat > /etc/cryptfs-backup-cloud/t-online_neobiker <<EOF #!/bin/sh # my cryptfs-backup-cloud script # rsync an encrypted filesystem to cloud storage [ -z "$RSYNC_CRYPT" ] && RSYNC_CRYPT=/usr/local/sbin/rsync-gocryptfs-cloud # ----------------------------------------------- provider=t-online # provider name WEBDAV=https://webdav.magentacloud.de/ # URL of provider cloud storage SSH_HOST= login=neobiker #----------------------------------------------- user=neobiker #$RSYNC_CRYPT -p $provider -s $SSH_HOST -l $login -u $user \ $RSYNC_CRYPT -p $provider -w $WEBDAV -l $login -u $user \ -r /srv/daten \ -x Downloads \ home/neobiker-daten \ home/neobiker \ EOF
Eine Namenskonvention wie <provider>_<login> bringt etwas Übersicht.
Bei mehreren Konfigurationsfiles für verschiedene Provider und Benutzer sieht das so aus:
$ ls /etc/cryptfs-backup-cloud t-online_neobiker t-online_otherUser 1blu_homepage_neobiker 1blu_double_neobiker
cryptfs-backup-cloud
Ein Hilfsprogramm cryptfs-backup-cloud liest in Zusammenspiel mit einem Cronjob alle Konfigurationen und startet die Backups nacheinander:
$ cryptfs-backup-cloud [-c] [Konfiguration1] [Konfiguration2] […]
Die Option -c (check) ermittelt per du -csh den belegten Speicherplatz aller Daten-Verzeichnisse, wobei -x einzelne Verzeichnisse vom Backup ausschliesst. Die Angabe der Grösse des Backup hilft beim Verteilen der Datenmengen auf die verschiedenen Cloud-Speicher und deren freier Kapazität.
$ cryptfs-backup-cloud -c t-online_neobiker disc usage in configuration t-online_neobiker: 19G home/neobiker-daten 108K home/neobiker 19G insgesamt thereof excluded: 8,5G home/neobiker-daten/Downloads 9,5M home/neobiker-daten/Documents/Downloads 8,5G insgesamt
Alle anderen optionalen Parameter aus dem Konfigurationsfile werden transparent an das Backup Skript rsync-gocryptfs-cloud übermittelt.
rsync-gocryptfs-cloud
Die Parameter des Backupscripts von rsync-gocryptfs-cloud:
$ rsync-gocryptfs-cloud [-t] [-v] [-d] [-delete] -p <provider> (-w <webdav> | -s <ssh_host> | -m <mount_point>) -l <login> -u <user> [-b <mybackup>] [-x <exclude1> [-x <exclude2>]…] -r <root dir> <dir1> <dir2> <…>
Die Option -t (test) führt einen Testlauf („dry-run“) aus, es werden keine Dateien synchronisiert. Die Option -v (verbose) listet alle von rsync identifizierten Dateien auf, -d (debug) gibt zusätzliche Informationen des Skripts aus, wie z.B. den genauen Aufruf von rsync bzw. find samt den identifizierten Dateien für den anstehenden Backuplauf. Mit -x (exclude) können einzelne Verzeichnisse vom Backup ausgeschlossen werden, -b (backup) bezeichnet das Verzeichnis, in welches die Daten synchronisiert werden, voreingestellt ist „mybackup“. Die Option -m (mount) wurde am Schluss hinzugefügt, um das verschlüsselte Backup auch einfach auf einen beliebigen lokalen Mount-Point speichern zu können, z.B. eine externe Festplatte. Ist diese Option definiert, wird kein Remote-Backup gestartet, sondern stattdessen nur ein „lokales“ Backup. So braucht man die normale Remote-Konfiguration nicht anpassen, und kann trotzdem auf einem lokalen Laufwerk das verschlüsselte Backup zusätzlich sichern.
Die Option –delete teilt rsync mit, fehlende (gelöschte) Dateien im Zielsystem auch zu löschen. Sofern ein Zeitstempel des letzten erfolgreichen Backups vorliegt wird kein Fullbackup initiiert. In diesem Fall ermittelt der Befehl find im lokalen Filesystem alle Dateien, welche einen neueren Zeitstempel haben und sendet diese Liste an rsync. Die Option -f (full) initiiert ein Fullbackup trotzdem. Dies stellte sich als sinnvoll heraus, da webdav als Filesystem denkbar schlecht für rsync und dessen Standard Methoden zum Identifizieren von Dateiänderungen geeignet ist. Beim Kopieren von Dateien bleiben Zeitstempel nicht erhalten weshalb die Prüfsumme von Dateien verwendet wird. Die delta-x-fer Option arbeitet bei verschlüsselten Dateien und mit webdav nicht performant. Deshalb bevorzugt das Skript, sofern möglich, die Nutzung von rsync via ssh und verwendet die Option –whole-file.
Apropos find im lokalen Filesystem: Hiermit werden natürlich keine Dateien gefunden, welche nachträglich mit einem älterem Zeitstempel in ein Backup-Verzeichnis verschoben wurden (alter Zeitstempel bleibt erhalten). In diesem Fall ist die Option -f für ein Fullbackup einmalig anzuwenden.
Installation
Installation und Beispiele im Detail:
/etc/cryptfs-backup-cloud/* # individuelle Backup Konfigurationen /usr/local/sbin/cryptfs-backup-cloud # Hilfsskript, liest Konfigurationsfiles /usr/local/sbin/rsync-gocryptfs-cloud # Backup Skript
Das Passwort zum Ver-/entschlüsseln der Daten liest das Skript standardmäßig aus der Datei /etc/GoCryptFS.PWD. Auf Debian Systemen installiert man gocryptfs am besten aus dem Testing repository, andernfalls ist die Version veraltet. Sshfs benutze ich als Alternative für Cloud-Speicher, welche mir nur ssh aber kein webdav anbieten. Benötigt wird das vom Backup-Skript nicht, sondern nur, wenn ich solchen Cloud-Speicher entschlüsselt wieder einbinden möchte.
$ apt-get install davfs2 gocryptfs rsync sshfs $ cp cryptfs-backup-cloud rsync-gocryptfs-cloud /usr/local/sbin $ cd /usr/local/sbin $ chmod +x cryptfs-backup-cloud rsync-gocryptfs-cloud
Das wars auch schon mit der Installation, jetzt muss das Backup nur noch durch die Backup-Konfigurationsfiles für jeden Benutzer konfiguriert werden.
Anpassungen
In den beiden Skripts cryptfs-backup-cloud (Hilfsskript) und rsync-gocryptfs-cloud (Backup Skript) können am Anfang individuelle Konfigurationen angepasst werden.
Erwähnenswert ist hier die Variable CHECK_LIMIT. Überschreitet die Anzahl geänderten Dateien seit dem letzten Backuplauf diesen Wert, bricht das Skript mit einer Warnung ab – ein einfaches Mittel z.B. gegen einen Locker-Schädling, damit dieser nicht gleich das Backup mitverschlüsselt. Der einmalige Aufruf mit der Option -f für ein Fullbackup hilft hier, nach einer vorherigen Prüfung (z.B. Optionen -t -v -d) warum das Limit überschritten wurde, das trotzdem alles gesichert wird.
Standardmäßig verwendet das Skript folgende Dateien und Verzeichnisse:
/etc/GoCryptFS.PWD # GoCryptFS Passwort /etc/cryptfs-backup-cloud/ # Backup Konfigurationen /mnt/mybackup-cloud/<provider>/<login> # assemble backup-dirs /mnt/mybackup-cloud/gocryptfs/<provider>/<login> # encrypted dir /mnt/webdav/<provider>/<login> # webdav Cloud-Storage /mnt/webdav/decrypt/<provider>/<login> # decrypted <mybackup> dir
Die Konfiguration der Cloudspeicher mit Webdav erfolgt in den beiden Dateien davfs2.conf und secrets im Verzeichnis /etc/davfs2. Der Webdav Cloudspeicher ist in der Datei secrets und /etc/fstab zu konfigurieren:
$ cat /etc/secrets # T-Online Magentacloud /mnt/webdav/t-online/neobiker <Login@t-online.de> "<passwort>"
Zugehöriger /etc/fstab Eintrag zum mounten der Cloud-Speicher:
$ grep davfs /etc/fstab https://webdav.magentacloud.de/ /mnt/webdav/t-online/neobiker davfs rw,noauto,uid=neobiker,gid=User-neobiker,file_mode=777,dir_mode=777 0 0
Damit kann die Konfiguration und der Zugriff auf den Cloudspeicher getestet werden, nachdem noch ein Passwort zur Verschlüsselung definiert wird:
Setze ein Passwort für gocryptfs:
$ echo > /etc/GoCryptFS.PWD „mein GoCryptFS Passwort“ $ chmod 660 /etc/GoCryptFS.PWD
Anwendungsbeispiele
Die Backup-Konfiguration wurde schon weiter oben definiert. Wieviel Speicherplatz die Konfiguration beinhaltet prüft man mittels:
$ cryptfs-backup-cloud -c t-online_neobiker disc usage in configuration t-online_neobiker: 19G home/neobiker-daten 108K home/neobiker 19G insgesamt thereof excluded: 8,5G home/neobiker-daten/Downloads 9,5M home/neobiker-daten/Documents/Downloads 8,5G insgesamt
Teste das Backup-Skript und den Cloudspeicher Zugriff:
$ cryptfs-backup-cloud -t t-online_neobiker bind t-online/jens: home/neobiker-daten bind t-online/jens: home/neobiker 2020-05-01 13:01 : (webdav) start neobiker rsync-crypt to t-online: neobiker started full initial sync /sbin/umount.davfs: warte bis mount.davfs (PID 12852) die Dateien im Cache gesichert hat .. OK 2020-05-01 13:01: cryptfs-backup-cloud finished
Konfiguration eines Cronjob, der täglich um 01:31 Uhr das Backup startet:
$ crontab -e ### Cloud-Server Remote Backup 31 01 * * * /usr/local/sbin/cryptfs-backup-cloud
Nützliche Aliase für die Standardkonfiguration des Skriptes in ~/.bash_aliases:
alias mount_t-online_neobiker='mount /mnt/webdav/t-online/neobiker' alias mount_t-online_neobiker_CFS='gocryptfs -passfile /etc/GoCryptFS.PWD -config /mnt/mybackup-cloud/gocryptfs/.config/t-online_neobiker.reverse.conf /mnt/webdav/t-online/neobiker/mybackup /mnt/webdav/decrypt/t-online/neobiker' $ mount_t-online_neobiker # mountet den MagentaCloud Speicher nach /mnt/webdav/t-online_neobiker $ mount_t-online_neobiker_CFS # entschlüsselt das Crypted-Backup unter /mnt/webdav/decrypt/t-online_neobiker
Anwendungsfall 1
Homeverzeichnisse per WebDav in MagentaCloud von T-Online sichern, wurde im Artikel ausgeführt.
Verzeichnis von T-Online mounten (/etc/fstab Eintrag ist vorhanden, oder alias verwenden):
$ mount /mnt/webdav/t-online/neobiker/
Backup Verzeichnis mounten und Datei wiederherstellen (verwendet obigen alias):
$ mount_t-online_neobiker_CFS $ cp /mnt/webdav/decrypt/t-online/neobiker/home/neobiker-daten/Documents/Cryptfs_Artikel.docx .
Anwendungsfall 2
Es wird rsync direkt mittels SSH zur Synchronisation der Dateien benutzt. SSHFS wird als Alternative zu webdav verwendet, um den Cloud-Speicher zu mounten:
alias sshfs_1blu='sshfs <login>@<webhosting_host>.1blu.de: /mnt/webdav/1blu/<login>/'
Konfiguration eines SSH Cloud-Speicher bei 1Blu:
$ cat /etc/cryptfs-backup-cloud/1blu_homepage_neobiker #!/bin/sh # my cryptfs-backup-cloud script # rsync an encrypted filesystem to cloud storage [ -z "$RSYNC_CRYPT" ] && RSYNC_CRYPT=/usr/local/sbin/rsync-gocryptfs-cloud #----------------------------------------------- provider=1blu # provider name WEBDAV= # URL of provider cloud storage SSH_HOST=<webhosting_host>.1blu.de login=<1blu login> #----------------------------------------------- user=neobiker #$RSYNC_CRYPT -p $provider -w $WEBDAV -l $login -u $user \ $RSYNC_CRYPT -p $provider -s $SSH_HOST -l $login -u $user \ -r /srv/daten \ data/homepage \
Test und Debug
Beispiel des Speicherns dieses Artikels mit den Optionen -v(erbose) und -d(ebug): Man sieht die Aufrufparameter von mount, find und rsync und den letztlichen Aufruf mit allen Parametern:
$ cryptfs-backup-cloud -d -v t-online_neobiker 2020-05-22 17:10: rsync-gocryptfs-cloud assemble for t-online: neobiker bind t-online/neobiker: home/neobiker-daten bind t-online/neobiker: home/neobiker 2020-05-22 17:10: mount -t davfs -o uid=neobiker,gid=User-neobiker,file_mode=770,dir_mode=770 https://webdav.magentacloud.de/ /mnt/webdav/t-online/neobiker 2020-05-22 17:10 : (webdav) start neobiker 2020-05-22 17:10: find /mnt/mybackup-cloud/t-online/neobiker/home/ -path */temp -prune -o -path */Temp -prune -o -path */Downloads -prune -o -path */Downloads -prune -o -newer /mnt/mybackup-cloud/t-online/neobiker/.backup -type f -print Files to sync: 1 (rm /mnt/mybackup-cloud/t-online/neobiker/.backup for full backup) find -newer: home/neobiker-daten/Documents/Cryptfs_Artikel.docx rsync to t-online: neobiker started 2020-05-22 17:10: nice rsync --files-from=- --whole-file --bwlimit 1.5m --ignore-errors --exclude-from /var/run/rsync_exclude_6764 --exclude temp --exclude Temp --exclude Downloads --exclude Downloads --verbose --stats --checksum /mnt/mybackup-cloud/gocryptfs/t-online/neobiker/ /mnt/webdav/t-online/neobiker/mybackup/ find -newer: XdLfx5-75ieq8bMIYGeP7Q==/-eFqsSvk2rnYpMpT8PCxvA==/ZrfIzMtF9hLwBx7WozrLDg==/1UsMcd37sCt4lHaTK2Nw_G1Ei7fo5mRjfi00xJ2GbQc= XdLfx5-75ieq8bMIYGeP7Q==/-eFqsSvk2rnYpMpT8PCxvA==/ZrfIzMtF9hLwBx7WozrLDg==/gocryptfs.diriv building file list ... done XdLfx5-75ieq8bMIYGeP7Q==/-eFqsSvk2rnYpMpT8PCxvA==/ZrfIzMtF9hLwBx7WozrLDg==/1UsMcd37sCt4lHaTK2Nw_G1Ei7fo5mRjfi00xJ2GbQc= Number of files: 5 (reg: 2, dir: 3) Number of created files: 0 Number of deleted files: 0 Number of regular files transferred: 1 Total file size: 82,656 bytes Total transferred file size: 82,640 bytes Literal data: 82,640 bytes Matched data: 0 bytes File list size: 0 File list generation time: 0.592 seconds File list transfer time: 0.000 seconds Total bytes sent: 82,976 Total bytes received: 31 sent 82,976 bytes received 31 bytes 33,202.80 bytes/sec total size is 82,656 speedup is 1.00 2020-05-22 17:10: rsync to t-online: jens (ok) /sbin/umount.davfs: warte bis mount.davfs (PID 7018) die Dateien im Cache gesichert hat .. OK 2020-05-22 17:10: cryptfs-backup-cloud finished
Skripts
cryptfs-backup-cloud
#!/bin/sh # read configuration files from a config dir # rsync my directories encrypted to cloud storage # neobiker (2020) # --- individual configuration --- CONF_DIR=/etc/cryptfs-backup-cloud # read and execute all scripts RSYNC_CRYPT=/usr/local/sbin/rsync-gocryptfs-cloud # my rsync-gocryptfs-cloud script CHECK_MOUNTS=/srv # if set, exit script if it is not a mounted filesyst # --- end of individual configuration --- # don't start when another instance is (still?) running prg=`basename $0` run=$( ps ax|awk '{print $5}'|grep -c $0 ) pid_file=/run/${prg}.pid if [ "0$run" -gt 1 ]; then if [ -e $pid_file -a $( cat $pid_file ) != $$ ]; then echo "Warning: $prg already running ($run times). Exiting" exit 1 fi fi echo $$ > $pid_file # only option -c is relevant for myself # all other options are just forwarded to $RSYNC_CRYPTFS # otherwise i will execute the configuration scripts while [ "$#" -gt "0" ]; do case "$1" in -c) check_config=true ;; # read additional params ... just forward most of them -t|-d|-v|-f|--delete) RSYNC_CRYPT="$RSYNC_CRYPT $1" ;; -m) shift RSYNC_CRYPT="$RSYNC_CRYPT -m $1" ;; -x) shift RSYNC_CRYPT="$RSYNC_CRYPT -x $1" skipped_data="$skipped_data $1" ;; *) break ;; esac shift done # process all dirs in $CONF_DIR, or dirs='*' # only specified dirs [ -n "$*" ] && dirs=$* # if defined, check my SOURCE filesystems # skip if i'm not a fileserver [ -n "$MOUNT" ] && for mnt in $MOUNT; do if ! mount | grep -q $mnt; then if ! mount $mnt; then echo "Error: mount $mnt failed." exit 1 fi fi done # # i just check the configuration file # using du -s for all dirs # check_backup_config () { data_skipped="" # read configuration -r <root_dir> -x <exclude dir> ... <src1> ... while [ "$#" -gt "0" ]; do case "$1" in -h|-d|-t|-f|-v) # skip options -t -f -v -d -h ;; -p|-w|-s|-l|-b|-u) shift # skip parameter -p <provider> -w <webdav> -l <login> -u <uid> ;; -x) shift data_skipped="$data_skip -name $1" ;; -r) shift data_root=$1 ;; *) break ;; esac shift done echo disc usage in configuration $( basename $backup_script ): (cd $data_root; du -c -s -h $*) [ -n "$data_skipped" ]&& echo " thereof excluded:" [ -n "$data_skipped" ]&& (cd $data_root; find $* $data_skipped | xargs du -csh) echo } # --------------------------------------------------------- # run backup scripts, or check disk usage in configurations # --------------------------------------------------------- for backup_script in $CONF_DIR/$dirs; do if [ "$check_config" = "true" ]; then RSYNC_CRYPT=echo check_backup_config $( . $backup_script ) else # run the backup script . $backup_script fi done [ -e /run/${prg}.pid ]&& rm /run/${prg}.pid echo `date +"%F %R"`: $prg finished
rsync-gocryptfs-cloud
#!/bin/bash # rsync files encrypted by gocryptfs # via davfs2 or rsync-ssh # # Description: # backup encrypted (gocryptfs) data to # webdav mount-point of provider cloud storage # or by rsync via ssh # # data directories will be assembled (--bind) to a temporary # rsync directory, which will be virtually encrypted by gocryptfs # and synced via rsync to backup directory # # $data_dir (src) -> $rsync_dir (bind) -> $crypt_dir (crypted) -> $backup_dir (e.g. cloud) # # Author: neobiker (2020) # --- individual configuration --- BACKUP=mybackup # remote backup directory on cloud storage CHANGE_LIMIT=300 # don't sync if more than X files changed # list directories to skip during in backup # think about, can be set by -x <dir> also RSYNC_EXCLUDE="$RSYNC_EXCLUDE --exclude temp" RSYNC_EXCLUDE="$RSYNC_EXCLUDE --exclude Temp" RSYNC_EXCLUDE="$RSYNC_EXCLUDE --exclude Downloads" WEBDAV_MNT=/mnt/webdav # webdav mount point for provider cloud storage BACKUP_SRC=/mnt/mybackup-cloud # assemble the backup dirs by --bind mounts here CRYPTFS_MNT=$BACKUP_SRC/gocryptfs # create encrypted filesystem DECRYPTED=$WEBDAV_MNT/decrypt # mount decrypted webdav filesystem here (not used in this script) # rsync options RSYNC="nice rsync" #OPT_RSYNC="$OPT_RSYNC --no-whole-file" # makes no sense whith encryption OPT_RSYNC="$OPT_RSYNC --whole-file" # makes sense whith encryption OPT_RSYNC="$OPT_RSYNC --bwlimit 1.5m" # works not with davfs mount :-( #OPT_RSYNC="$OPT_RSYNC --safe-links" # GoCryptFS is used for encryption CRYPTFS=gocryptfs OPT_CFS="-reverse" # don't change reverse mode OPT_CFS="$OPT_CFS -passfile /etc/GoCryptFS.PWD" # eg. link to /run/YourPassWord read from encrypted device on boot ;-) OPT_CFS="$OPT_CFS -quiet" # your choice # --- end of individual configuration --- cat > /var/run/rsync_exclude_$$ <<-EOF lost+found System Volume Information \$RECYCLE.BIN gocryptfs.conf EOF OPT_RSYNC="$OPT_RSYNC --ignore-errors --exclude-from /var/run/rsync_exclude_$$" OPT_RSYNC="$OPT_RSYNC $RSYNC_EXCLUDE" for exclude_dir in $(echo "$RSYNC_EXCLUDE" | tr = \ ); do [ $exclude_dir != "--exclude" ] && OPT_FIND="$OPT_FIND -path */$exclude_dir -prune -o" done print_usage () { echo "Usage: `basename $0` -p <provider> (-w <webdav> | -s <ssh_host> | -m <mount_point>) -l <login> -u <uid>" echo " [-b <remote backup_dir (mybackup)>] [-f(ull)] [-v(erbose)] [-t(est)] [-d(ebug)] [--delete]" echo " [[-x <exclude dir1>] [-x <exclude dir2>] ...]" echo " -r <data_root> <src1 relative to data_root> [<src2 relative to data_root> ...]" } myname=$(basename $0) delete=false # use --delete option verbose=false # use -v option test_run=false # use -t option full_sync=false # use -f option DEBUG=false # use -d option if [ "$#" -le 10 ]; then print_usage exit 1 fi # read params <provider> <webdav> <login> <uid> <src1> ... while [ "$#" -gt "0" ]; do case "$1" in -h) help=true ;; -p) shift provider=$1 ;; -w) shift WEBDAV=$1 ;; -s) shift SSH_HOST=$1 ;; -b) shift BACKUP=$1 ;; -l) shift login=$1 ;; -m) shift mount_point=$1 ;; -u) shift user_id=$1 ;; -r) shift data_root=$1 ;; -x) shift OPT_RSYNC="$OPT_RSYNC --exclude $1" OPT_FIND="$OPT_FIND -path */$1 -prune -o" ;; -f) full_sync=true ;; --delete) delete=true ;; -d) DEBUG=true ;; -v) verbose=true ;; -t) test_run=true ;; *) break ;; esac shift done if [ -n "$help" ]; then print_usage exit 0 fi if [ -z "$user_id" -o -z "$login" -o -z "$provider" -o -z "$SSH_HOST$WEBDAV" ]; then if [ -z "$mount_point" ]; then echo "Error: missing arguments for backup directory" echo "Error: either use -m <mount_point> or -s <SSH_HOST> or -w <WEBDAV>" print_usage exit 1 fi fi if [ -z "$data_root" -o ! -e "$data_root" ]; then echo "Error: missing or wrong -r <data_root>" print_usage exit 1 fi # skip, if no <src> ... dirs defined if [ -z "$*" ]; then echo "Error: missing <source dirs>" print_usage exit 1 fi # simple debug debug_echo () { $DEBUG && echo `date +"%F %R"`: $* } $test_run && OPT_RSYNC="$OPT_RSYNC --dry-run" $verbose && OPT_RSYNC="$OPT_RSYNC --verbose" $delete && OPT_RSYNC="$OPT_RSYNC --delete" $DEBUG && OPT_RSYNC="$OPT_RSYNC --stats" # ---------------------------------------------------------- # rsync-cryptfs: <$src> -> $webdav_dir/$BACKUP # ---------------------------------------------------------- # assemble $data_dir -> $rsync_dir (bind -ro src1 ...) # crypt $rsync_dir -> $crypt_dir (cryptfs -ro) # rsync $crypt_dir -> $backup_dir (webdav or rsync ssh) # ---------------------------------------------------------- rsync_dir=$BACKUP_SRC/$provider/$login crypt_dir=$CRYPTFS_MNT/$provider/$login webdav_dir=$WEBDAV_MNT/$provider/$login backup_dir=$webdav_dir/$BACKUP [ -e $rsync_dir ] || mkdir -p $rsync_dir [ -e $crypt_dir ] || mkdir -p $crypt_dir [ -e $webdav_dir ] || mkdir -p $webdav_dir # this would de-/encrypt the file-/dirnames, but it doesn't work in my host :-( # OPT_CFS="$OPT_CFS -ctlsock $rsync_dir/.ctlsock" # -------------------------------- # we use rsync via ssh if possible # -------------------------------- if [ -n "$SSH_HOST" ]; then method=ssh backup_dir=$SSH_HOST:$BACKUP export RSYNC_RSH="ssh -l $login" OPT_RSYNC="$OPT_RSYNC --times" # rsync-ssh preserves times else method=webdav OPT_RSYNC="$OPT_RSYNC --checksum" # webdav doesn't preserve time fi # a local mount can always be used instead of the remote backup if [ -n "$mount_point" ]; then method=mount backup_dir=$mount_point/$BACKUP debug_echo Info: backup_dir=$mount_point/$BACKUP fi # ----------------------------------------------- # webdav parameters: mount with defined $uid $gid # ----------------------------------------------- uid=$(id -un $user_id) gid=$(id -gn $uid) OPT_DAV="-o uid=$uid,gid=$gid,file_mode=770,dir_mode=770" # ------------------------------------------- # assemble all source dirs: # mount --bind $<src1> $rsync_dir/$<src1> # mount --bind $<src2> $rsync_dir/$<src2> # ... # ------------------------------------------- assemble_src () { debug_echo " $myname assemble for $provider: $login" logger -t $myname "assemble-source-dirs for $provider: $login" # clear assemble area at start ($rsync_dir) for m in $( mount|grep $rsync_dir|cut -d\ -f3 ); do debug_echo " Info: umount $m" umount $m done # assemble all src dir into $rsync_dir ret=1 for src in $*; do # ------------------------------- # mount (--bind) $src $rsync_dir # ------------------------------- if [ -d $data_root/$src ]; then # check if it is already mounted (double listed?) mount | cut -d\ -f3 | grep -q $rsync_dir/$src && continue # we need a dir to mount [ -d $rsync_dir/$src ] || mkdir -p $rsync_dir/$src if mount --bind -o ro $data_root/$src $rsync_dir/$src; then ret=0 echo " bind $provider/$login: $src" logger -t $myname "bind $provider/$login: $src" else logger -t $myname "Error: mount --bind $src failed" fi else echo "Warning: $data_root/$src not found, skipped" fi done return $ret } # --------------------------------------------------- # gocryptfs --reverse $rsync_dir -> $crypt_dir # --------------------------------------------------- mount_cryptfs () { # cryptfs config file should never be synced to cloud! cfs_config=$CRYPTFS_MNT/.config/${provider}_${login}.reverse.conf # don't mount me twice if ! mount | grep gocryptfs-reverse | cut -d\ -f3 | grep -q "$crypt_dir "; then # check if cryptfs is already initialized ret=1 if [ ! -e $cfs_config ]; then # init cryptfs reverse view logger -t $myname "$CRYPTFS -init -reverse $rsync_dir" $CRYPTFS -init $OPT_CFS -config $cfs_config $rsync_dir 2>/dev/null fi # mount cryptfs view logger -t $myname "$CRYPTFS -reverse $rsync_dir $crypt_dir" $CRYPTFS $OPT_CFS -config $cfs_config $rsync_dir $crypt_dir ret=$? else echo "Warning: gocryptfs already mounted $crypt_dir" ret=0 fi return $ret } # --------------------------------------------------- # webdav mount only if 'rsync -e ssh' isn't available # --------------------------------------------------- mount_backup_dir () { # ---------------------------- # local mountpoint configured? # ---------------------------- if [ $method = "mount" ]; then # avoid wrong (former) mounts if mount | cut -d\ -f3 | grep -q "$mount_point "; then debug_echo " Info: umount $mount_point" umount "$mount_point" fi # ---------------------- # mount local mountpoint # ---------------------- if ! mount | cut -d\ -f3 | grep -q "$mount_point "; then debug_echo " mount $mount_point" if ! mount $mount_point; then ret=$? echo "Error: mount $mount_point failed (ret=$ret)." return $ret else [ -e $backup_dir ] || mkdir -p $backup_dir fi else echo "Warning: $mount_point was already mounted, skipped." fi elif [ $method = "webdav" ]; then # avoid wrong (former) webdav mounts (e.g. wrong uid gid) if mount | cut -d\ -f3 | grep -q "$webdav_dir "; then debug_echo " Info: umount $webdav_dir" umount "$webdav_dir" fi # ---------------------------- # mount $WEBDAV to $webdav_dir # ---------------------------- DAV_PID=$(echo $webdav_dir | cut -d/ -f2- | sed -e 's#/#-#g').pid if ! mount | cut -d\ -f3 | grep -q "$webdav_dir "; then [ -f "/var/run/mount.davfs/$DAV_PID" ] && rm /var/run/mount.davfs/$DAV_PID debug_echo " mount -t davfs $OPT_DAV $WEBDAV $webdav_dir" if ! mount -t davfs $OPT_DAV $WEBDAV $webdav_dir; then ret=$? echo "Error: mount $WEBDAV $webdav_dir failed (ret=$ret)." return $ret fi else echo "Warning: $webdav_dir was already mounted, skipped." fi fi } # ---------------------------------------- # my backup rsync function for webdav dirs # ---------------------------------------- # use 'find -newer' in order to create the list of files # which changed since last successful backup # -------------------------------------- # rsync $crypt_dir/$src $backup_dir/$src # -------------------------------------- backup_encrypted_data () { echo `date +"%F %R"` ": $maname ($method) start $provider_$login" # some stat markers backup_start=.backup_start backup_last=.backup if [ ! -d $crypt_dir ]; then echo "Error: missing <crypt_dir> for backup_encrypted_data()" return fi if [ -z "$SSH_HOST" -a ! -d $backup_dir ]; then echo "Error: missing <backup_dir> for backup_encrypted_data()" return fi # initial full backup ? # otherwise, only "newer" files will be synced (to reduce remote rsync file-checks) if [ "$full_sync" != "true" -a -e $rsync_dir/$backup_last ]; then # check how many files changed since last backup debug_echo find $rsync_dir/*/ $OPT_FIND -newer $rsync_dir/$backup_last -type f -print file_nr=$( find $rsync_dir/*/ $OPT_FIND -newer $rsync_dir/$backup_last -type f -print | wc -l ) echo "Files to sync: $file_nr (rm $rsync_dir/$backup_last for full backup)" $DEBUG && ( echo "find -newer: "; cd $rsync_dir; find */ $OPT_FIND -newer $rsync_dir/$backup_last -type f -print ) # skip backup if $CHANGE_LIMIT is exceeded if [ $CHANGE_LIMIT -ne 0 -a $file_nr -gt $CHANGE_LIMIT ]; then echo "Warning: MANY Files changed." echo "Info : Check if 'full sync' is needed (rm $rsync_dir/$backup_last to trigger full sync)" echo "Error : Skipping Sync (to prevent Crypto-Locker Virus)" ret=1000 # ------------------------------------------------------------ # backup only "newer" files since last backup -> partial rsync # ------------------------------------------------------------ else if [ $file_nr -ne 0 ] ; then echo " rsync to $provider: $login started" logger -t $myname "rsync to $provider: $login started " debug_echo $RSYNC --files-from=- $OPT_RSYNC $crypt_dir/ $backup_dir/ $DEBUG && ( echo "find -newer: "; cd $crypt_dir; find */ $OPT_FIND -newer $rsync_dir/$backup_last -type f -print ) $test_run || touch $rsync_dir/$backup_start $RSYNC $crypt_dir/gocryptfs.diriv $backup_dir/ ( cd $crypt_dir; find */ $OPT_FIND -newer $rsync_dir/$backup_last -type f -print ) | \ $RSYNC $OPT_RSYNC --files-from=- $crypt_dir/ $backup_dir/ ret=$? [ $ret -ne 0 ] && logger -t $myname "rsync to $provider: $login (RET = $ret)" else ret=0 fi fi else # -------------------------------------------- # no backup_last timestamp found -> full rsync # -------------------------------------------- echo " rsync-crypt to $provider: $login started full initial sync" logger -t $myname "rsync-crypt to $provider: $login started full initial sync" $test_run || touch $rsync_dir/$backup_start crypted_dirs=$( cd $crypt_dir; find * -maxdepth 0 -type d -printf '%f ' ) debug_echo "$RSYNC $OPT_RSYNC gocryptfs.diriv $crypted_dirs $backup_dir/" ( cd $crypt_dir; $RSYNC $OPT_RSYNC -r gocryptfs.diriv $crypted_dirs $backup_dir/ ) ret=$? [ $ret -ne 0 ] && logger -t $myname "rsync to $provider: $login (RET = $ret)" fi if [ $ret -eq 0 ]; then [ -e $rsync_dir/$backup_start -a $test_run = "false" ] && mv $rsync_dir/$backup_start $rsync_dir/$backup_last logger -t $myname "rsync to $provider: $login (ok)" debug_echo " rsync to $provider: $login (ok)" else logger -t $myname "rsync to $provider: $login (RET = $ret)" fi [ -e $rsync_dir/$backup_start ] && rm $rsync_dir/$backup_start return $ret } # ---------------------------------------------------- # assemble directories to be backed up into $rsync_dir # ---------------------------------------------------- if assemble_src $*; then # --------------------------------------- # CRYPTFS $rsync_dir <-> $crypt_dir # --------------------------------------- if mount_cryptfs; then # ---------------------------------------------- # mount backup directory (webdav or local mount) # ---------------------------------------------- if mount_backup_dir; then # ------------------------------ # backup encrypted directory # ------------------------------ backup_encrypted_data fi fi fi rm /var/run/rsync_exclude_$$ # --------------------------------------------- # just to be sure to umount all dirs at the end # --------------------------------------------- for m in $(mount|grep $rsync_dir|cut -d\ -f3); do logger -t $myname "umount: $m" umount $m done for m in $(mount|grep $crypt_dir|cut -d\ -f3); do logger -t $myname "umount: $m" umount $m done # -------------------------------- # umount $webdav_dir, work is done # -------------------------------- if mount | cut -d\ -f3 | grep -q $webdav_dir; then umount $webdav_dir logger -t $myname "umount: $webdav_dir" fi