Backup in die Cloud

Aus Neobiker\\\'s Wiki
Zur Navigation springen Zur Suche springen

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:

  1. Cloud-Speicher mit zugehörigem Account
  2. 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

SSHFS wird als Alternative zu webdav verwendet, um den Cloud-Speicher zu mounten

Es wird rsync direkt mittels SSH zur Synchronisation der Dateien benutzt:

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