2023-10-15
Dans un précédent article, je vous avais présenté BorgBackup et comment l'utiliser pour effectuer des sauvegardes sur un disque local. Aujourd'hui, je vais vous expliquer comment effectuer les sauvegardes sur une machine distante via SSH et comment sécuriser et automatiser tout ça.
Pour cet article, on va considérer que l'on dispose de deux machines sous Linux qui peuvent communiquer entre elles :
Dans cette configuration, Borg fonctionnera en mode client / serveur et devra donc être installé sur les deux machines.
Le client tourne sur la machine à sauvegarder (donc machine1). C'est lui qui s'occupe de la déduplication et de la compression, ce qui permet de réduire la quantité de données qui transitent par le réseau, et c'est également lui qui s'occupe du chiffrement, ce qui est plutôt intéressant du point de vue de la sécurité car la machine distante (backup1) ne verra jamais passer les données en clair.
Le serveur quant à lui ne fera qu'écrire les blocs de données dans le dépôt. On peut également le configurer pour limiter les accès du client pour plus de sécurité.
Avant de pouvoir commencer à sauvegarder quoi que ce soit, il va nous falloir configurer les machines.
Ici, je vais partir du principe que les machines disposent d'une connexion réseau fonctionnelle et qu'elles peuvent se joindre l'une l'autre. Je pars également du principe qu'un client SSH est installé sur la machine cliente (machine1) et qu'un serveur SSH est installé et fonctionnel sur la machine serveur (backup1).
Sur machine1, on va commencer par se connecter en root, puis on va installer BorgBackup. Je vous donne ici la commande pour Debian et Ubuntu ; si vous utilisez une autre distribution il est probablement présent dans vos dépôts également :
apt install borgbackup
Ensuite, toujours en root, on va créer une clef SSH qui nous servira à nous connecter à backup1 :
ssh-keygen -t ed25519 -C "BorgBackup root@machine1.flozz.lan"
Lors de la création de la clef, ssh-keygen va vous poser plein de questions :
Generating public/private ed25519 key pair. Enter file in which to save the key (/root/.ssh/id_ed25519): Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/id_ed25519 Your public key has been saved in /root/.ssh/id_ed25519.pub [...]
Pour plus de facilité, on va le laisser écrire la paire de clefs à l'emplacement par défaut et on ne va pas définir de mot de passe sur la clef (il suffit d'appuyer sur <Entrée> sans rien écrire pour ne pas en mettre).
Passons à présent sur le serveur. Comme pour l'autre machine, on s'y connecte en root et on commence par installer Borg :
apt install borgbackup
On va ensuite créer l'utilisateur borg qui servira à effectuer nos sauvegardes :
adduser borg
Vous pouvez laisser les paramètres par défaut lors de la création du compte.
Une fois que notre utilisateur est créé, on va s'y connecter puis y placer la clef publique que l'on vient de créer sur machine1, et le tout avec les bonnes permissions :
# On se connecte avec l'utilisateur borg su borg # On crée le dossier ".ssh" à la racine de notre home s'il n'existe pas déjà mkdir -p ~/.ssh # On lui met les bonnes permissions (si elles sont trop permissives, OpenSSH # refusera d'utiliser les fichiers qui s'y trouvent) chmod 700 ~/.ssh # On crée le fichier qui va contenir les clefs publiques autorisées... touch .ssh/authorized_keys # ... et on lui défini également ses permissions chmod 600 ~/.ssh/authorized_keys
Il ne reste plus qu'à ouvrir le fichier authorized_keys (avec VIM, Nano ou n'importe quel autre éditeur de votre choix) et d'y placer la clef publique de machine1 (il s'agit de la clef contenue dans le fichier "/root/.ssh/id_ed25519.pub" sur machine1).
Le contenu du fichier devrait alors être similaire à celui-ci :
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAp2Bz0e1GjYGMvLyw4WxXTGHN/Pla9CmJbApNnrlESM BorgBackup root@machine1.flozz.lan
--------------------------------------------------------------------------------
📝️ Note:
--------------------------------------------------------------------------------
NOTE : pour le moment on laisse un accès SSH complet à root@machine1.flozz.lan mais on verra par la suite comment sécuriser tout ça en réduisant ce qu'il peut faire lorsqu'il se connecte.
--------------------------------------------------------------------------------
Maintenant que tout est configuré, on retourne sur machine1 avec l'utilisateur root et on va tester la connexion à backup1 :
ssh borg@backup1.flozz.lan
Si tout s'est bien passé, SSH devrait vous demander de confirmer que vous voulez bien vous connecter à la machine (si c'est la première fois que vous vous y connectez). Il faudra lui répondre "yes", et normalement vous devriez être connectés à backup1.
Vous pouvez à présent vous déconnecter de backup1 et on passe à la suite !
La dernière fois, on avait vu que pour manipuler un dépôt local, il suffisait de donner son chemin à Borg, ce qui donnait par exemple :
/media/fabien/backup-hdd/my-borg-repo
Étant donné qu'on va maintenant travailler sur un dépôt distant à travers SSH, il va falloir fournir le chemin du dépôt sous la forme d'une URL avec un peu plus d'informations. Cette URL prend la forme suivante :
ssh://<USER>@<MACHINE><PATH>
Pour la suite de cet article, je vais créer mes dépôts Borg dans le home de l'utilisateur borg sur bakcup1, l'URL ressemblera donc à ceci :
ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup
On avait également vu que pour référencer une archive dans un dépôt local, il suffisait de rajouter "::<ARCHIVE_NAME>" à la fin du chemin du dépôt... Et bah c'est la même chose pour les dépôts distants :
ssh://<USER>@<MACHINE><PATH>::<ARCHIVE_NAME>
Maintenant qu'on a vu comment sont formées les URLs référençant un dépôt distant, on peut créer notre dépôt exactement comme on l'avait fait dans le précédent article, mais en remplaçant le chemin du dépôt par une URL.
Voici par exemple la commande que je devrais effectuer (avec l'utilisateur root sur la machine machine1) pour créer mon dépôt distant :
borg init --encryption repokey ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup
Pour effectuer une sauvegarde, ça se passe là encore de la même façon qu'avant :
borg create \ --list \ 'ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup::my-archive-{now}' \ /home/www
Ici, on sauvegarde tout le contenu de "/home/www" dans une archive nommée "my-archive-<HEURE_ET_DATE_ACTUELLE>" sur le dépôt distant.
Et pour lister le contenu du dépôt ? C'est toujours la même chose :
borg list ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup
Bref, vous avez compris. 😉️
Actuellement, l'utilisateur root de machine1 à un accès complet à tout ce dont l'utilisateur borg de backup1 a lui-même accès. C'est assez problématique, car si machine1 est compromise, l'attaquant pourrait également s'attaquer backup1.
Mais heureusement, on peut fortement réduire la surface d'attaque !
Premièrement, on peut commencer par restreindre les commandes auxquelles l'utilisateur qui se connecte en SSH a accès (il s'agit d'un mécanisme standard d'OpenSSH). Au lieu de disposer d'un shell complet, permettant de faire tout ce que l'on veut, on peut donc forcer l'utilisation d'une commande précise ; dans notre cas, il s'agira de la commande "borg serve" qui lance la partie serveur de Borg.
Pour ce faire, on va modifier le fichier "~/.ssh/authorized_keys" de l'utilisateur borg sur backup1 pour y rajouter quelques options.
--------------------------------------------------------------------------------
📝️ Note:
--------------------------------------------------------------------------------
Format du fichier authorized_keys
Mais avant de modifier quoi que ce soit, voyons un peu plus en détail comment est composé le fichier authorized_keys.
Ce fichier permet, comme vous le savez, de lister les clefs SSH autorisées à se connecter à la machine. Chaque ligne correspond à une autorisation (donc à une clef). La syntaxe de ces lignes est la suivante :
[OPTIONS] <KEY_TYPE> <SSH_KEY> [COMMENT]
Si vous voulez plus de détail sur le format de ce fichier, je vous invite à lire la documentation suivante :
Documentation du fichier authorized_keys
--------------------------------------------------------------------------------
Maintenant qu'on en sait un peu plus sur le format du fichier authorized_keys, voici comment on va devoir modifier le nôtre pour sécuriser l'accès et forcer la commande que l'on souhaite :
command="borg serve",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAp2Bz0e1GjYGMvLyw4WxXTGHN/Pla9CmJbApNnrlESM BorgBackup root@machine1.flozz.lan
On a donc ajouté deux options :
Bon, avec ça on est déjà pas mal mais on peut aller plus loin. La commande borg serve dispose elle aussi d'options pour restreindre davantage les accès.
La première option fort utile est "--restrict-to-path". Elle permet de n'autoriser l'accès qu'au dossier indiqué (et à l'ensemble de ses sous-dossiers).
Si mon dépôt se trouve dans le dossier /home/borg/my-machine1-backup, je peux donc autoriser la machine distante à n'accéder qu'à ce dossier-là. Voici ce que donnerait mon fichier "authorized_keys" avec cette nouvelle restriction :
command="borg serve --restrict-to-path /home/borg/my-machine1-backup",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAp2Bz0e1GjYGMvLyw4WxXTGHN/Pla9CmJbApNnrlESM BorgBackup root@machine1.flozz.lan
Il existe également une option "--restrict-to-repository" qui permet quant à elle de restreindre l'accès à un dépôt précis (pas d'accès aux sous-dossiers, uniquement au dépôt pointé par le chemin passé en paramètre).
Ces deux options peuvent être passées plusieurs fois à borg serve si l'on veut autoriser l'accès à plusieurs dossiers ou dépôts.
L'un des éléments embêtant si machine1 était compromise, c'est que l'attaquant pourrait supprimer les sauvegardes du dépôt Borg. Il existe heureusement une option pour empêcher ça : "--append-only".
Si on souhaite l'utiliser, il faudra modifier notre fichier "authorized_keys" de la manière suivante :
command="borg serve --append-only",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAp2Bz0e1GjYGMvLyw4WxXTGHN/Pla9CmJbApNnrlESM BorgBackup root@machine1.flozz.lan
Cette option est bien sûr cumulable avec celles vues précédemment. 🙂️
Il y a toutefois un point auquel il faut faire attention avec cette option. Si les commandes borg delete et borg prune ne supprimeront effectivement aucune donnée, elles les taggeront comme étant supprimées. Et ces données taggées seront automatiquement supprimées la prochaine fois que vous écrirez dans le dépôt sans le mode append only. Il faudra donc manipuler le dépôt avec précaution dès lors que vous soupçonnerez une compromission de machine1 !
La commande borg comptact sera quant à elle sans effet dans ce mode (elle échouera silencieusement).
Pour en apprendre davantage sur le mode append only et pour savoir quoi faire en cas de compromission, je vous invite à lire la documentation de Borg qui explique tout ça très bien :
Documentation du mode « append only » de Borg
Il existe enfin une dernière option, permettant de définir une limite maximale à la taille d'un dépôt : --storage-quota. Cette option peut être utile pour maîtriser les coûts lorsque l'on utilise du stockage dans le cloud.
Là encore il suffit d'ajouter l'option dans le fichier authorized_keys pour l'utiliser :
command="borg serve --storage-quota 5G",restrict ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAp2Bz0e1GjYGMvLyw4WxXTGHN/Pla9CmJbApNnrlESM BorgBackup root@machine1.flozz.lan
Ici on limite la quantité de données présentes dans le dépôt à 5 Gio (5G). D'autres suffixes sont bien sûr disponibles pour répondre à tous vos besoins :
À noter que le quota minimum est de 10 Mio (10M).
--------------------------------------------------------------------------------
Voilà, on a fait le tour des options fournies par Borg pour améliorer la sécurité. Ces options ne sont bien sûr que des compléments aux bonnes pratiques de sécurité de base pour un serveur Linux.
Pour en savoir plus sur la commande borg serve, je vous mets ci-dessous le lien vers la documentation officielle :
Maintenant qu'on a vu comment sauvegarder sur une machine distante, on va voir comment automatiser un peu tout ça avec un petit script Bash et Cron.
Le premier problème auquel on est confrontés lorsque l'on veut automatiser les sauvegardes avec Borg, c'est la gestion de la passphrase du dépôt. Ne pas en mettre poserait des problèmes en termes de sécurité, et si on en met une, on ne peut bien évidemment pas la taper à la main lors d'une sauvegarde automatique... Heureusement, Borg nous fournit plusieurs façons alternatives de lui donner ce mot de passe ! 😁️
La première, c'est via la variable d'environnement "BORG_PASSPHRASE". On peut donc écrire la série de commandes suivantes :
export BORG_PASSPHRASE='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' borg list ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup
Comme le mot de passe se trouve dans l'environnement, Borg ne nous le demandera pas lorsque l'on utilisera des commandes qui en ont besoin.
Une seconde méthode est d'indiquer à Borg, toujours via une variable d'environnement, une commande permettant de récupérer ce mot de passe. On peut par exemple stocker le pass dans un fichier et lui indiquer comment le récupérer via la variable d'environnement "BORG_PASSCOMMAND", ce qui nous donnerait ceci :
echo -n 'XXXXXXXXXXXXXXXXXXXXXXXXXX' > /root/ma_passphrase_borg export BORG_PASSCOMMAND='cat /root/ma_passphrase_borg' borg list ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup
Il s'agit ici d'un exemple simpliste, mais il est possible de fournir une commande allant chercher le mot de passe dans un trousseau de clefs, dans un vault, etc. suivant ce que vous avez à votre disposition.
En plus de ces variables pour fournir la passphrase du dépôt, une dernière variable d'environnement va nous être utile : "BORG_REPO". Elle permet de passer l'adresse du dépôt sans avoir besoin de la fournir explicitement à chaque commande.
Exemple d'utilisation :
export BORG_PASSPHRASE='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' export BORG_REPO='ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup' borg list
Ici Borg récupère à la fois le chemin du dépôt et le mot de passe depuis l'environnement, ce qui nous permet de taper des commandes plus courtes.
--------------------------------------------------------------------------------
📝️ Note:
--------------------------------------------------------------------------------
NOTE : Dans le cas de la commande borg create, on souhaite fournir le nom de l'archive, ce qui se fait généralement en passant "<chemin_du_dépôt>::<nom_archive>" en paramètre à Borg. Si l'on a déjà fourni le chemin du dépôt via la variable "BORG_REPO", on peut alors lui donner uniquement le nom de l'archive sous la forme suivante : "::<nom_archive>".
--------------------------------------------------------------------------------
Il y a beaucoup d'autres variables d'environnement disponibles, vous en trouverez la liste complète dans la documentation de Borg :
Liste des variables d'environnement de Borg
Une chose importante à prendre en compte lorsque l'on automatise des sauvegardes, c'est de savoir si elles ont fonctionné ou échoué. Pour cela, Borg se termine avec des codes de retour différents en fonction de la manière dont les opérations se sont déroulées :
Il est également possible que Borg retourne d'autres valeurs dans certaines circonstances particulières. Je vous laisse lire la documentation officielle pour plus d'informations :
Liste des codes de retours de Borg
Maintenant qu'on a vu quelques points importants, on va voir comment écrire un petit script Bash pour automatiser nos sauvegardes.
Les grandes étapes d'une sauvegarde sont généralement les suivantes :
Voici une version très minimaliste d'un script de sauvegarde. Pour des raisons de simplicité je n'ai pas vraiment géré la partie reporting (je me contente d'écrire les erreurs et de quitter), mais il serait important de l'implémenter correctement en production (par exemple en envoyant un email) :
#!/bin/bash # Adresse du dépôt export BORG_REPO='ssh://borg@backup1.flozz.lan/home/borg/my-machine1-backup' # Mot de passe du dépôt export BORG_PASSPHRASE='XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' # Chemin vers l'exécutable de Borg (pratique en cas d'installation # non-standard ou si le script s'exécute dans un contexte ou la variable # $PATH n'est pas correctement définie) BORG=/usr/bin/borg # Préparation ================================================================ # Ici on peut exécuter des commandes pour préparer la sauvegarde, comme par # exemple faire des dumps des bases de données... # # Attention à bien gérer les erreurs ! # Sauvegarde ================================================================= $BORG create \ --verbose \ --list \ --stats \ --show-rc \ --compression zstd \ '::auto-{hostname}-{now}' \ /root \ /home \ /etc \ /var/www # Dans la commande "borg create" ci-dessus, on ne précise que le nom de # l'archive à créer et pas le chemin du dépôt, car celui-ci est déjà défini # dans la variable d'environnement $BORG_REPO # Je tiens également à attirer votre attention sur le nom de cette archive # que je préfixe par "auto-". Cela me permet de distinguer les sauvegardes # automatique d'éventuelles sauvegardes manuelles. On va voir à quoi cela # sert un peu plus tard... :) # On récupère le code de retour de la commande précédente. # 0 = OK, 1 = WARNING, 2 = ERROR BACKUP_STATUS=$? # En cas d'erreur on rapporte l'erreur et on quitte... if [[ $BACKUP_STATUS != 0 && $BACKUP_STATUS != 1 ]] ; then echo "Backup failed!" exit 1 fi # NOTE : ici je vérifie explicitement que Borg ne s'est pas terminé avec # son code de retour OK ou WARNING et pas simplement qu'il s'est terminé par # ERROR. Il est en effet possible que d'autres codes d'erreurs soient # retournés, comme par exemple 127, retourné par Bash s'il ne trouve pas la # commande, ou 137 si le processus de Borg a été tué (sigkill). # Nettoyage ================================================================== # On commence par élaguer le dépôt... $BORG prune \ --verbose \ --list \ --stats \ --show-rc \ --prefix 'auto-{hostname}-' \ --keep-daily 7 \ --keep-weekly 4 \ --keep-monthly 12 \ --keep-yearly 3 # Dans la commande "borg prune" ci-dessus, je lui demande de considérer # uniquement les sauvegardes dont le nom commence par "auto-machine1-". # Cela me permet de faire des sauvegardes manuelles (par exemple en cas de # grosse mise à jour des sites hébergés) sans que ces sauvegardes manuelles # ne soient supprimées automatiquement. PRUNE_STATUS=$? # En cas d'erreur on rapporte l'erreur et on quitte... if [[ $PRUNE_STATUS != 0 && $PRUNE_STATUS != 1 ]] ; then echo "Prune failed!" exit 1 fi # ... puis on libère l'espace disque $BORG compact \ --verbose \ --show-rc COMPACT_STATUS=$? # En cas d'erreur on rapporte l'erreur et on quitte... if [[ $COMPACT_STATUS != 0 && $COMPACT_STATUS != 1 ]] ; then echo "Compaction failed!" exit 1 fi # ATTENTION : Si l'option --append-only est utilisée sur le serveur, les # anciennes archives ne seront pas effectivement supprimées (seulement marquées # comme tel), et l'espace disque ne sera pas récupéré. Il faudra exécuter ces # commande sans le mode append only de temps en temps. # Finition =============================================================== # Ici on peut exécuter des commandes pour nettoyer après la sauvegarde. On peut # par exemple supprimer les dumps de base de données faits avant la sauvegarde. # # Attention à bien gérer les erreurs ! # Reporting ============================================================== echo "Backup finished" exit 0
Dans mon cas je vais enregistrer ce script dans "/opt/my-backup.sh" par ce que je trouve que c'est un emplacement qui s'y prête bien.
Si vous avez été attentifs, vous aurez remarqué que le mot de passe du dépôt est en clair dans le script. On ne peut donc pas le laisser comme ça, accessible en lecture à tout le monde. On va donc s'assurer qu'il appartient bien à l'utilisateur root, et que seul cet utilisateur pourra y accéder. Cela peut se faire à l'aide des deux commandes suivantes :
chown root:root /opt/my-backup.sh chmod 700 /opt/my-backup.sh
Voilà, c'est beaucoup mieux comme ça ! 😃️
Il ne nous reste plus qu'on configurer Cron pour lancer régulièrement le script que l'on vient d'écrire.
On va donc éditer la configuration des tâches planifiées de l'utilisateur root, ce qui se fait à l'aide de la commande suivante (à lancer avec l'utilisateur root donc) :
crontab -e
Si on souhaite, par exemple, que notre sauvegarde se fasse tous les jours à 3h00 du matin, on peut y insérer une ligne similaire à celle-ci :
0 3 * * * /opt/my-backup.sh
Si vous n'êtes pas familiers avec la syntaxe du crontab, je vous invite à lire la page Wikipédia de Cron qui explique tout ça très bien :
Afin d'être vraiment complet, je me dois de mentionner l'existence de BorgMatic.
Si vous avez des besoins un peu plus complexes que simplement sauvegarder quelques fichiers et/ou que les scripts Bash c'est pas trop votre truc, il se pourrait bien que ce logiciel vous intéresse ! BorgMatic simplifie grandement l'automatisation des sauvegardes ; il sait notamment faire des dumps de bases de données et s'intègre à divers outils de monitoring.
Et pour ne rien gâcher, il est également disponible dans les dépôts de la plupart des distributions Linux. Il peut donc s'installer à l'aide d'une simple commande :
apt install borgmatic
Je ne vais pas rentrer plus dans les détails ici car cet article est dédié à Borg lui-même et pas aux outils qui tournent autour. Je vous renvoie donc vers sa documentation officielle qui est plutôt claire et bien écrite si vous voulez en apprendre plus à son sujet :
https://torsion.org/borgmatic/
Comme vous avez pu vous en rendre compte dans ces deux articles, Borg est un outil de sauvegarde puissant et sûr. Il bénéficie d'une importante communauté qui a développé de nombreux outils s'appuyant sur lui, comme BorgMatic que j'ai déjà mentionné, ou Pika Backup, une interface graphique super pratique que j'utilise sur mon PC portable.
Si les outils autour de Borg vous intéressent n'hésitez pas à me le faire savoir, j'essayerai de leur dédier un article. 😉️
--------------------------------------------------------------------------------