Tutoriel : Sauvegarde Linux et Windows

Configuration du serveur

Note : toutes les commandes doivent être passées en tant que root !

Tous les scripts et fichiers de configuration nécessaires seront placés dans une arborescence dont la racine sera /opt/btrbackup (un choix discutable qui combine btrfs et backup, le lecteur est libre d’en changer s’il trouve mieux !) :

mkdir /opt/btrbackup
cd /opt/btrbackup
mkdir bin etc lib log

Préparation de l’espace disque qui hébergera les sauvegardes

On commence par créer un système de fichiers de type BTRFS sur une partition libre ou sur un volume logique spécialement créé pour l’occasion. Attention à ne pas passer cette commande sur une partition existante, car elle détruit irrémédiablement toutes les données qui s’y trouvent ! Je décline toute responsabilité en cas de perte de données résultant de cette manipulationEn cas de doute, renseignez-vous ou mieux encore, laissez une question dans les commentaires !

mkfs -t btrfs /dev/vgdata1/btrfs

Ajouter ensuite la ligne suivante dans le fichier /etc/fstab :

/dev/vgdata1/btrfs      /data/btrfs     btrfs           compress=zlib,noatime           0 1

L’option compress=zlib est importante. D’autres algorithmes de compression seront proposés par BTRFS à l’avenir, qu’il ne faudra pas hésiter à utiliser car le gain de place obtenu est substantiel tandis que la latence qui en résulte n’a pas d’importance pour des données de type sauvegarde.

Puis créer le point de montage et monter votre système de fichiers tout neuf :

mkdir /data/btrfs
mount /data/btrfs

On crée à l’intérieur de ce système de fichiers un répertoire par client, qui contiendra le subvolume BTRFS hébergeant la copie courante des données sauvegardées ainsi qu’un sous-répertoire archives destiné à recevoir les snapshots. Les permissions sont positionnées pour éviter que ssh se plaigne qu’elles sont trop permissives :

mkdir /data/btrfs/client1
mkdir /data/btrfs/client1/archives
btrfs subvolume create /data/btrfs/client1/current
mkdir /data/btrfs/client2
mkdir /data/btrfs/client2/archives
btrfs subvolume create /data/btrfs/client2/current
chmod -R 755 /data/btrfs/*

Considérations générales

On va utiliser rsync pour le transfert de fichiers incrémental entre le client et le serveur. Celui-ci se présente le plus souvent sous la forme d’un daemon/service auquel les clients peuvent se connecter pour effectuer leurs transferts. Mais la sécurité native offerte par rsync dans ce mode de fonctionnement n’est pas satisfaisante : les flux ne sont pas cryptés, et l’authentification par mot de passe est faiblarde et ne repoussera aucun attaquant décidé. On va donc configurer rsync de manière à ce qu’il utilise ssh pour établir un tunnel crypté entre le client et le serveur, dans lequel il encapsulera ses données.  Dans ce mode, l’authentification aura lieu au niveau de ssh au moyen d’un échange de clés sécurisé.

Chaque client utilisera donc pour se connecter au serveur un identifiant et une clé privée uniques, qui lui donneront un accès aussi restreint que possible aux ressources du serveur. Il faudra s’assurer qu’il possède des droits suffisants pour effectuer ses sauvegardes (sinon quel intérêt ?), mais rien de plus. En particulier, il ne doit pas être possible pour le client :

  • d’accéder à autre chose que ses propres données, notamment les sauvegardes des autres clients ou des données locales au serveur
  • de modifier ou supprimer ses propres sauvegardes : cela donnerait à un attaquant qui a infiltré le client la possibilité de détruire à la fois les données originales et toutes leurs sauvegardes

Configuration SSH

Commençons par ajouter les directives suivantes à la fin du fichier /etc/ssh/sshd_config :

Port 222
Port 223

Match LocalPort 22
        DenyGroups btrbackup

Match LocalPort 222 Group *,!btrbackup
        DenyUsers *

Match LocalPort 223 Group *,!btrbackup
        DenyUsers *

Match Group btrbackup LocalPort 222
        AuthorizedKeysFile /opt/btrbackup/etc/%u/sshkey.pub
        X11Forwarding no
        AllowTcpForwarding no
        AllowAgentForwarding no
        PermitTunnel no
        PasswordAuthentication no
        PubkeyAuthentication yes
        # ForceCommand doesn't allow variables ($USER) nor substitutions like %u, so we use a wrapper
        ForceCommand /opt/btrbackup/bin/rsync-wrapper.sh

Match Group btrbackup LocalPort 223
        AuthorizedKeysFile /opt/btrbackup/etc/%u/sshkey.pub
        X11Forwarding no
        AllowTcpForwarding no
        AllowAgentForwarding no
        PermitTunnel no
        PasswordAuthentication no
        PubkeyAuthentication yes
        ForceCommand internal-sftp -R
        ChrootDirectory /data/btrfs/%u/archives

Les premières directives indiquent au démon sshd qu’il doit écouter sur les ports 222 et 223 en plus du port 22 (son port par défaut). Chaque client va en effet se présenter au serveur SSH avec un identifiant et une clé uniques pour solliciter deux services différents : rsync et sftp. Le serveur pourra donc distinguer entre les deux en fonction du port par lequel la connexion arrive. Si les deux requêtes arrivaient par le même port, il faudrait faire la distinction en utilisant deux identifiants ou deux clés pour chaque client, ce qui n’est pas très élégant. En outre cela permet d’avoir une notion de ports dédiés au service de sauvegarde, pour lesquels on pourra éventuellement créer des règles de firewall spécifiques, voire utiliser un serveur SSH dédié.

Les directives suivantes refusent l’accès aux utilisateurs membres du groupe btrbackup sauf s’ils se connectent sur l’un des ports dédiés à la sauvegarde/restauration. Ils ne peuvent alors se connecter qu’à l’aide d’une clé privée qui doit correspondre à la clé publique stockée dans le sous-répertoire de /opt/btrbackup/etc correspondant à ce client. Une fois la connexion établie, le serveur ignorera toutes les commandes envoyées par le client et exécutera selon le cas :

  • le script /opt/btrbackup/bin/rsync-wrapper.sh (qu’on détaillera plus loin) s’il s’agit d’un accès pour sauvegarde
  • le serveur sftp interne en lecture seule, chrooté dans le répertoire contenant les archives du client, s’il s’agit d’un accès pour restauration

Redémarrer SSH pour prendre en compte les modifications de son fichier de configuration.

Création des comptes clients

On crée pour chaque client un utilisateur qui sera membre du groupe btrbackup :

groupadd btrbackup
useradd --comment "Backup user for client1" --non-unique --uid 0 --gid btrbackup --no-create-home --no-user-group --home-dir /opt/btrbackup --shell /bin/bash client1
passwd -l client1
useradd --comment "Backup user for client2" --non-unique --uid 0 --gid btrbackup --no-create-home --no-user-group --home-dir /opt/btrbackup --shell /bin/bash client2
passwd -l client2

Le lecteur attentif ne manquera pas de remarquer les points suivants :

  • l’utilisateur possède un uid égal à 0, il a donc les droits root. N’est-ce pas dangereux ? C’est indispensable car il doit pouvoir écrire des fichiers appartenant à des utilisateurs différents dans le cadre d’une sauvegarde complète du client, voire créer des devices dans le cas d’une sauvegarde système. En théorie on pourrait utiliser un compte non privilégié en combinaison avec l’option fake super de rsync pour stocker dans des attributs étendus (extended attributes) les informations accessibles uniquement à root. Dans la pratique cela nous obligerait à relire les sauvegardes exclusivement via rsync (ce qui n’est pas l’option que nous avons retenue), à monter le système de fichiers hébergeant les sauvegardes avec des options spécifiques (en général on y pensera, mais quid des supports amovibles – sur lesquels on ne manquera pas de répliquer les sauvegardes – qui seront montés automatiquement par le système ?), et enfin les données sur disque ne seraient plus directement lisibles sur le serveur, ce qui est contraire au cahier des charges. Les droits administrateur sont également nécessaires pour pouvoir déclencher les snapshots BTRFS à la fin de la sauvegarde, et pour exécuter rsync avec la directive chroot. Il est naturellement indispensable de limiter au strict minimum les possibilités d’action d’un tel utilisateur.
  • l’utilisateur dispose d’un vrai shell (ici /bin/bash). Le serveur ssh en aura besoin pour exécuter sa commande forcée. L’important est de s’assurer que cette dernière est sécurisée et restreint les actions possibles au strict minimum.
  • on verrouille le compte de manière à ce qu’il ne puisse pas être utilisé localement au cas où un hostile aurait un accès physique au serveur. Noter que le verrouillage ne gêne pas le fonctionnement de ssh.

Générons ensuite une paire de clés privée/publique pour ces utilisateurs, dans le répertoire où ssh s’attend à les trouver :

mkdir /opt/btrbackup/etc/client1
chmod g-w,o-w /opt/btrbackup/etc/client1
ssh-keygen -q -t dsa -N "" -C "client1@$(hostname)" -f /opt/btrbackup/etc/client1/sshkey
mkdir /opt/btrbackup/etc/client2
chmod g-w,o-w /opt/btrbackup/etc/client2
ssh-keygen -q -t dsa -N "" -C "client2@$(hostname)" -f /opt/btrbackup/etc/client2/sshkey

Noter que les répertoires contenant les clés SSH ne doivent être accessibles en écriture que pour leur propriétaire, sans quoi le serveur refusera d’en tenir compte par mesure de sécurité.

Configuration de rsync

Nous avons donc maintenant pour chaque client un utilisateur qui pour exécuter sa sauvegarde ne peut se connecter au serveur qu’en ssh au moyen d’une clé privée, et ne peut lancer que la seule commande /opt/btrbackup/bin/rsync-wrapper.sh. Comme le but du jeu est de faire transiter le trafic rsync par la connexion ssh, ce script aura pour seule ambition d’exécuter la version serveur de rsync, avec un fichier de configuration spécifique à chaque client.

/opt/btrbackup/bin/rsync-wrapper.sh :

#!/bin/bash
exec /usr/bin/rsync --daemon --config=/opt/btrbackup/etc/${USER}/rsyncd.conf --server .

Il est un peu insolite d’utiliser l’option --daemon dans cette situation puisque cette instance de rsync est à usage unique (elle se terminera avec la connexion ssh en cours). Elle est cependant requise ici car je tiens à ce que l’accès aux sauvegardes soit chrooté, ce qu’on ne peut spécifier que dans le fichier de configuration, qui n’est pris en compte que lorsque rsync est exécuté en mode daemon (et par root).

/opt/btrbackup/etc/client1/rsyncd.conf :

[backup]
 path = /data/btrfs/client1/current
 comment = client1 backup (write only)
 use chroot = yes
 # module is read only by default
 read only = false
 # Read and write files to the module as root. When rsyncd is run as root, this is 'nobody' by default.
 uid = 0
 gid = 0
 pre-xfer exec = /opt/btrbackup/bin/rsync-handler.sh

Le fichier de configuration utilisé crée un module « backup » accessible en écriture par le client pour qu’il puisse y envoyer ses sauvegardes. On voit que l’option use chroot est activée de manière à empêcher le client de sortir du répertoire de sauvegarde, ce qui pourrait s’avérer gênant car les droits root qu’on lui a accordés lui permettraient d’accéder à tout le contenu du serveur.

L’astuce réside néanmoins pour l’essentiel dans la directive pre-xfer exec. Le serveur rsync va en effet exécuter ce script avant tout transfert de fichier, ce qui nous permet deux choses importantes :

  • s’il s’agit d’un accès en lecture, le script renverra un code non nul, empêchant ainsi le transfert de se produire. Le module est donc de ce fait write-only
  • comme l’accès en lecture n’est donc pas autorisé (du moins pas via rsync), on va interpréter ce type d’accès comme signifiant de la part du client « j’ai fini de t’envoyer mes données, prend un snapshot pour les mettre de côté ». Le script va donc déclencher un snapshot BTRFS du subvolume « current » avant de renvoyer son code retour non nul.

J’entends déjà la question : pourquoi ne pas éviter ce hack et utiliser plutôt un script post-xfer ? A cela deux raisons :

  • il n’est intéressant de prendre un snapshot que si l’on est certain que la sauvegarde a réussi, or pour cela le code retour que l’on peut récupérer côté serveur n’est pas fiable (il peut y avoir des circonstances dans lesquelles le transfert est interrompu sans que le serveur prenne cela pour une erreur : timeout réseau, arrêt du client, etc…) Bref, seul le client sait réellement si sa sauvegarde est bonne et c’est donc à lui de prendre la décision.
  • il peut être nécessaire de faire la sauvegarde en plusieurs fois, pour des raisons applicatives (sauvegarde à chaud d’une base de données par exemple, qui nécessite de sauvegarder successivement les données et les archivelogs) ou à cause de contraintes de bande passante, de consommation CPU, de fenêtre de tir… Si un snapshot est pris après chaque transfert on n’aura jamais une sauvegarde consistante !

/opt/btrbackup/bin/rsync-handler.sh :

#!/bin/bash

# Look for --sender in the rsync args, which would indicate a read access
index=0
varvalue=""
sender=0
while [ "$varvalue" != "." ]; do
 varname=RSYNC_ARG$index
 varvalue=${!varname}
 [ "$varvalue" = "--sender" ] && sender=1
 index=$((index + 1))
done

if [ $sender -ne 0 ]; then
 # Read access :
 # The client does this after updating the module (write access) to indicate the end of the backup
 # and trigger the snapshot server-side
 btrfs subvolume snapshot "$RSYNC_MODULE_PATH" $(dirname "$RSYNC_MODULE_PATH")/archives/$(date +"%Y-%m-%d@%Hh%M")
fi

# Return 0 to allow receiving data when write access
# Return 1 to prevent read access: we are supposed to be write-only so the client does not really want its data back
# (and he probably requested a bogus file anyway, just to trigger the snapshot...)
exit $sender

Notre serveur est maintenant prêt à recevoir les sauvegardes envoyées par ses clients. Avant de voir comment configurer ces derniers, une petite mise au point sur le fonctionnement de rsync nous évitera de grosses prises de tête par la suite…

Bien comprendre rsync et ses options

rsync est un outil extrêmement puissant qui propose une pléthore d’options destinées à gérer au mieux les différents cas de figure qui peuvent se présenter lors d’un transfert de fichiers. Nous allons ici détailler celles qui nous seront utiles ainsi que les raisons pour lesquelles nous allons les utiliser.

Les options de base

Les options de base de rsync sont :

-r : copier récursivement les répertoires
-l : préserver les liens symboliques
-p : préserver les permissions
-t : préserver les dates de modification
-g : préserver le groupe
-o : préserver le propriétaire
--devices : copier les fichiers de type « device »
--specials : copier les fichiers spéciaux (FIFO, sockets)
-D : équivalent à --devices --specials
-a : archive, équivalent à -rlptgoD

On constate immédiatement d’une part que rsync est très proche des concepts Unix (propriétaire, groupe, fichiers spéciaux), ce qui nécessitera certaines adaptations lorsqu’on abordera les clients Windows. D’autre part que l’utilisation de rsync à des fins de sauvegarde est tellement courante que ses auteurs ont implémenté une option (-a) regroupant toutes celles nécessaires à une copie fidèle des données.

Supposons que l’on désire sauvegarder le répertoire toto de notre client vers notre serveur appelé server. La syntaxe de base pour effectuer la synchronisation de toto vers le module backup de server sera :
rsync -a /chemin/vers/toto server::backup

Options avancées

Deux options supplémentaires sont essentielles pour que rsync gère correctement l’accès au serveur tel que nous l’avons défini :
--inplace : écrire les modifications directement dans le fichier destination
-e : permet le tunneling du trafic rsync au travers d’une connexion ssh

Lorsqu’il met à jour un fichier existant dont la source a été modifiée, rsync écrit dans un nouveau fichier qu’il renomme ensuite. Ainsi en cas d’interruption du transfert seul le fichier temporaire est tronqué, l’ancien fichier restant intact et utilisable. Ce comportement (par ailleurs très sain) est contre-productif quand on a côté serveur un système de fichiers de type copy-on-write tel que BTRFS. Lorsqu’un fichier est modifié, celui-ci ne stocke en effet que les blocs qui ont changé par rapport au snapshot précédent, ainsi si la source est peu modifiée une nouvelle sauvegarde prend une place quasi nulle. Cette optimisation est perdue si on écrit dans un nouveau fichier, car il est impossible de relier celui-ci aux snapshots précédents. L’option --inplace force rsync à écrire directement les modifications dans l’ancien fichier. Le problème des fichiers tronqués en cas d’interruption n’en est pas vraiment un pour nous : la copie courante sera certes invalide, mais le client averti par un code d’erreur ne déclenchera pas de snapshot, et le fichier sera ré-écrit correctement au prochain passage de la sauvegarde.

La commande ssh invoquée par l’option -e de rsync peut elle-même s’accompagner d’options. Nous utiliserons notamment -l (le compte pour s’identifier auprès du serveur SSH), -p (le port auquel se connecter) et -i (la clé privée à utiliser pour l’authentification).

Notre commande s’enrichit donc pour devenir :
rsync -e "ssh -l mon_user -p 222 -i ma_clé" --inplace -a /chemin/vers/toto server::backup

Note : la clé privée spécifiée avec l’option -i de ssh doit au préalable avoir été copiée depuis le serveur vers le client. Si l’on est vraiment parano, la paire clé publique/clé privée devrait théoriquement être générée sur le client et seule la clé publique envoyée sur le serveur (une clé privée ne devrait jamais quitter la machine qui l’a générée). Disons qu’elle a été copiée depuis le serveur de manière sécurisée (via ssh ou par clé USB).

Tous les fichiers n’ont pas besoin d’être sauvegardés. Si notre répertoire source contient des fichiers temporaires ou que l’on peut facilement télécharger sur Internet, autant les exclure de la sauvegarde et gagner ainsi du temps et de la place :
--exclude : exclut certains fichiers ou répertoires de la sauvegarde (voir le manuel rsync pour la syntaxe exacte)

rsync est (à raison) conservateur quand il s’agit d’effacer des données. Dans notre cas les données issues de sauvegardes précédentes sont protégées par les snapshots BTRFS côté serveur. On va donc lui forcer la main pour que la sauvegarde courante corresponde exactement à ce qu’on lui demande :
--delete : supprime côté serveur les fichiers qui n’existent plus côté client (par défaut rsync les conserve)
--delete-excluded : supprime les fichiers qui existent toujours côté client mais qui sont désormais exclus de la sauvegarde par --exclude (ce cas se présente lorsqu’on modifie les exclusions entre deux sauvegardes)

Enfin, si l’on veut des précisions sur ce que fait rsync (ça peut toujours servir d’avoir des logs parlants quand on cherche d’où vient un problème) on peut ajouter :
--stats : donne des statistiques sur le transfert effectué
-v : détails pour chaque fichier transféré

La syntaxe de base pour nos sauvegardes sera donc au final :
rsync -e "ssh -l mon_user -p 222 -i ma_clé" -a --inplace --delete --exclude "/chemin/vers/toto/tmp/**" --delete-excluded -v --stats /chemin/vers/toto server::backup

Finalisation : déclenchement du snapshot

Si la commande précédente ne renvoie pas d’erreur, on exécutera ensuite un second rsync – cette fois-ci en lecture, avec des arguments bidons – ce qui aura pour effet de déclencher le snapshot sur le serveur :
rsync -e "ssh -l mon_user -p 222 -i ma_clé" server::backup/bogus .

Note : il est important de tester ces commandes « à la main » avant de les automatiser. En effet, à la première connexion au serveur, ssh demande une confirmation de l’identité du serveur qu’il faut accepter manuellement. Ce ne sera plus nécessaire par la suite car il se souviendra de la réponse. Il est possible de désactiver cette confirmation, mais c’est une fausse bonne idée car elle constitue une sécurité contre les attaques de type man-in-the-middle. Si un attaquant tente de se faire passer pour votre serveur pour intercepter vos sauvegarde et mettre la main sur vos secrets, ssh vous redemandera une confirmation, ce qui bloquera l’exécution automatique du script.

2 thoughts on “Tutoriel : Sauvegarde Linux et Windows”

  1. Merci pour ce très mon tutoriel :-).

    j’ai juste un soucis au niveau de la configuration de ssh : je n’arrive pas utiliser l’attribut loaclport dans les MATCH du fichier sshd_config,et d’après le man et les recherche que j’ai fait sur le net cet attribut n’existe pas !

    Pouvez vous me dire comment vous avez fait ?

    Merci

    PS : je suis sous debian 7.1.0 avec ssh 6.0p1

  2. Bonjour,

    merci pour ce tutoriel, sur mon client, on me demande une passphrase qui est a blanc dans ton tuto. je l’ai changé par autre chose et rien n’y fait.

    J’ai transfere le .pub de mon serveur de sauvegarde vers mon client mais ca ne marche pas.

    je suis débutant sur Linux mais bon je trouve ce tutoriel vraiment bien.

Les commentaires sont clos.