Sauvegardes via Internet, automatisées et auto-hébergées avec Backintime et Docker

Faire des sauvegardes régulières est crucial. Mais rares sont les personnes qui le font sérieusement. Il faut donc les automatiser et les rendre faciles d’utilisation.

Pour résister à un cambriolage, incendie ou autre gros pépin, le mieux est que cette sauvegarde soit externalisée. Dans ce cas, il est important que les données soient chiffrées, et que le flux réseau ne soit pas excessif.

Ca faisait longtemps que j’avais l’idée de faire des sauvegardes croisées et auto-hébergées : je stocke les sauvegardes d’un copain, et il stocke les miennes. Et je pourrais ainsi fournir une solution de sauvegarde simple et automatique à toutes les personnes pour qui j’ai installé Linux.

C’est BackInTime que j’ai choisi pour faire ça côté client, et Docker m’a permis de mieux sécuriser les choses côté serveur.

Pourquoi Backintime ?

En termes de fonctionnalité, c’est l’outil qui m’a paru le plus complet et le plus ergonomique. Il garde plusieurs versions (« snapshots »), que l’on peut facilement parcourir. On peut restaurer un fichier individuel, comparer plusieurs versions etc.

Les sauvegardes sont incrémentales, chiffrées, lançables automatiquement à intervalle régulier etc.

J’avais testé un peu DejaDup mais il avait moins de fonctionnalités (notamment la possibilité de parcourir les éléments d’une sauvegarde donnée), et surtout je suis tombé sur beaucoup de bugs qui m’ont paru bloquants quand je l’avais testé.

D’un autre côté, il n’y a pas beaucoup d’activité sur Backintime depuis quelques mois, j’espère que le projet va repartir.

Pré-requis

Il faut un serveur avec Docker, hébergé si possible chez un copain, et avec de l’espace disque pour stocker tout ce qu’il y aura à sauvegarder.

Il faut installer Backintime sur chacune des machines à sauvegarder. Pour Ubuntu, je conseille d’utiliser le PPA (voir https://github.com/bit-team/backintime/blob/master/README.md). L’objectif est d’utiliser le mode « SSH encrypted » : https://backintime.readthedocs.io/en/latest/settings.html#ssh-encrypted. Pour ce mode, il est également nécessaire d’installer les packages sshfs et encfs sur chaque machine à sauvegarder. Et il faut y générer une clé SSH (via ssh-keygen).

Il est conseillé d’avoir une bonne bande passante en upload (côté machines à sauvegarder) pour que les sauvegardes ne prennent pas des heures. J’ai 5 Mbps et ça me parait acceptable : avec le 1 Mbps d’une connexion ADSL, il faudra probablement se limiter sur les données à sauvegarder.

Mettre le serveur SSH dans un conteneur Docker

Pour faire une sauvegarde à distance, Backintime nécessite un accès SSH (et pas uniquement du SFTP). Or un accès SSH sur une machine ouvre beaucoup de possibilités, même si on n’est pas root.

Pour limiter cette problématique, j’ai pensé à mettre le serveur SSH dans un conteneur Docker. Ca met une couche d’isolation par rapport à la machine hôte, et limite fortement la surface d’attaque (il n’y a dans le conteneur que le strict minimum).

Ca permet en outre de lancer une instance par utilisateur, chacun n’ayant accès qu’à ses propres données.

J’en ai parlé sur le github de backintime, mais ça n’a pas soulevé les foules : https://github.com/bit-team/backintime/issues/879. Donc je me suis lancé pour le faire moi-même :

Création de l’image

Première version du fichier Dockerfile :

(inspiré de https://docs.docker.com/engine/examples/running_ssh_service/, il faut adapter l’architecture arm32v7 à la machine sur laquelle vous hébergez. Dans mon cas, c’est adapté à un Olinuxino A20. Sur un A64, on peut utiliser arm64v8)

Créer l’image via :

Et si besoin la supprimer via :

Externalisation de la configuration et des données

Il faut externaliser du conteneur :

  • l’emplacement des données bien sûr
  • la configuration du serveur OpenSSH (/etc/ssh, pour qu’elle ne change pas à chaque fois qu’on reconstruira le conteneur). J’y ai copié le /etc/ssh d’une machine debian stretch temporaire
  • la configuration ssh du compte avec lequel on se connecte (~/.ssh), notamment pour pouvoir alimenter le authorized_keys avec la clé publique de la machine à sauvegarder (par défaut .ssh/id_rsa.pub)

Concernant les données, plutôt que de les mettre sur la machine hôte, je compte à terme les mettre sur un partage réseau distant. Via quel protocole ? SMB ? SSHFS ? NFS ? iSCSI ? D’après https://backintime.readthedocs.io/en/latest/settings.html , SMB est supporté (et mes tests semblent le confirmer), même s’il nécessite de la conf côté serveur pour supporter les symlinks, et SSHFS ne serait pas supporté.

Démarrage du conteneur

(le –cap-add=NET_ADMIN est nécessaire pour faire fonctionner iptables : voir plus bas. Adapter le port 12345 et les emplacements sur la machine hôte en fonction de vos préférences)

Arrêter via :

Un peu de sécurisation des accès réseau

Dans un conteneur même minimaliste, on a accès au réseau, ce qui peut permettre des attaques sur le réseau local.

Pour restreindre cela, l’idéal serait de mettre en place des règles iptables sur l’hôte (voir par exemple https://serverfault.com/questions/615372/restricting-the-network-access-of-docker-container). Le problème, c’est que docker modifie lui-même les règles iptables, donc il faudrait lui demander de ne plus le faire, et les gérer intégralement nous-mêmes… Ca m’a paru un peu compliqué, d’autant que la gestion de Docker peut évoluer avec le temps.

J’ai donc préféré mettre des règles iptables dans le conteneur lui-même. C’est plus facile, mais un peu moins sécurisé (si le user arrivait à devenir root dans le conteneur, il pourrait les désactiver). J’ai donc fait une deuxième version du Dockerfile :

Avec un fichier entrypoint.sh avec ce contenu :

J’ai mis un nice au lancement d’openssh, pour que les processus de l’hôte soient considérés comme plus prioritaires que ceux lancés via SSH dans les conteneurs (il ne s’agit que de sauvegardes…)

Quotas d’espace disque

Au départ, j’avais pensé à acheter un gros disque dur, et mettre des quotas dessus pour chaque utilisateur. Malheureusement, BackInTime ne sait pas prendre en compte ces quotas : https://github.com/bit-team/backintime/issues/555. D’autre part, ça me posait des problématiques d’organisation : un gros disque dur coûte cher, il aurait fallu répartir les coûts sur les utilisateurs.

Au final, un ami m’a suggéré une meilleure idée : utiliser un disque physique par utilisateur (ou groupe d’utilisateurs) : plus besoin de quotas, chacun achète (ou réutilise) un disque externe de la taille de son choix.

Évidemment, il faut les configurer (avec hdparm) pour qu’ils s’arrêtent de tourner quand ils ne sont pas utilisés. Et il faut prévoir un peu plus de place physique (pour mettre les boîtiers), et si besoin un switch USB (alimenté).

Configuration d’un nouvel utilisateur

L’idée est d’avoir un conteneur par utilisateur. Il faut donc lancer une nouvelle instance Docker pour chaque utilisateur : sur un port dédié, et avec des points de montage dédiés (au moins pour les données et le ~/.ssh).

Sur la machine cliente, on génère une clé SSH (ssh-keygen), et on copie le contenu de ~/.ssh/id_rsa.pub dans le fichier authorized_keys correspondant côté serveur.

Puis on configure backintime sur la (ou les) machine(s) à sauvegarder, en utilisant le user backintime, le chemin /donnees-backintime, la clé privée correspondant à la clé publique qu’on vient de configurer côté serveur, et mettre un mot de passe de chiffrement.

Pour la planification, je conseille d’utiliser anacron, à la fréquence qui vous convient.

Il faut ensuite configurer les dossiers/fichiers à inclure ou exclure (attention à avoir un volume raisonnable par rapport à votre bande passante : commencez petit, pour en rajouter ensuite), et les options de purge (je conseille d’activer la « suppression intelligente » pour définir combien de versions conserver). Pour le reste, se référer à la doc de Backintime.

Performances

Sur mes petits serveurs Olinuxino A20, la vitesse de transfert plafonne entre 5 et 9 Mo/s (en montant comme en descendant), et occupe presque complètement les 2 CPUs du serveur.

Étant donné que l’Ethernet du Olinuxino A20 n’est qu’à 100Mbps, on ne pourra pas aller bien au-delà de toutes façons. Le Olinuxino A64 (que je viens de recevoir) est en Gigabit et a un CPU quadri-core plus véloce : il tourne autour de 20 Mo/s.

Optimisations si les temps de transfert sont trop longs

Si votre bande passante est faible et/ou si vous avez beaucoup de données à sauvegarder/restaurer, passer par le réseau peut parfois être très long.

Pour gagner un peu de temps, il peut y avoir quelques astuces :

Initialisation des données hors ligne

La première fois qu’on lance la sauvegarde, ça peut être très long.

Si le volume à sauvegarder est important mais bouge peu, et qu’on a une bande passante limitée, il est probablement possible d’initialiser la sauvegarde côté serveur sans passer par Internet.

Je n’ai pas encore eu l’occasion de le tester, mais sur le principe, il devrait suffire de faire une sauvegarde sur un disque local (en mode « Local encrypted » : https://backintime.readthedocs.io/en/latest/settings.html#local-encrypted), puis de copier ces données sur le serveur.

Les sauvegardes suivantes étant incrémentales, seules les fichiers ajoutés ou modifiés seront transférés.

Restauration hors ligne

Là aussi, on peut a priori copier le répertoire de sauvegarde du serveur sur un disque externe, et le lire en mode « Local encrypted » pour que la restauration aille plus vite.

Pistes d’amélioration

C’est une première version fonctionnelle, mais qui mériterait d’être améliorée.

J’aimerais que le chiffrement soit mieux blindé. Backintime utilise actuellement encfs, qui est probablement faillible. Il semblerait qu’une implémentation avec gocryptfs soit en cours de test : https://github.com/bit-team/backintime/issues/644 mais ça n’avance pas beaucoup.

Quand Backintime est configuré avec anacron, il ne donne pas beaucoup d’informations à l’utilisateur : il n’affiche pas d’icône dans Ubuntu 18.04 (cf https://github.com/bit-team/backintime/issues/916), et ne donne pas de moyen simple d’interrompre une sauvegarde en cours.

La sécurisation du conteneur est probablement encore améliorable. Quelques pistes à creuser :

  • mettre fail2ban pour détecter certaines tentatives d’attaque
  • mettre des systèmes d’alerte si l’utilisateur essaie de devenir root, ou s’il fait d’autres choses louches
  • limiter la consommation de mémoire, d’I/O disque, d’espace disque dans le /home
  • enlever des « capabilities » Docker ?

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *