Sauvegarde distante dans le Cloud IBM avec restic

Bien que n’ayant subi aucune perte de données, à proprement parler (grâce à la sauvegarde, le serveur étant tout de même parti en fumé), dans l’incendie du datacenter OVH de Strasbourg, je me suis tout de même interrogé sur la pertinence du processus en place. Le système en place est-il suffisant ? Quelles sont ses limites ? Ses faiblesses ? Quels scénarios pourrait mettre en échec toute la politique de sauvegarde: corruption des données, échec silencieux du processus, problème technique chez les deux hébergeurs, etc ?

Au détour d’une lecture, je découvre peu après l’événement, l’existence d’un cloud IBM et surtout, son plafond d’utilisation gratuite pour la partie Object Storage, à savoir 25Go. Parfait pour mettre en place un deuxième point de sauvegarde distant et découvrir la plateforme !

Je liste à la suite les étapes qui m’ont permis de configurer un espace de stockage et d’obtenir les identifiants nécessaires à un accès distant, en espérant ne pas en avoir oublié (et que l’interface n’ai pas changé entre le moment d’écriture de ces lignes et le moment où vous les lisez).

Étapes de mise en place

  • Créer un compte sur https://cloud.ibm.com/registration.
  • Cliquer sur « Créer une ressource ».
  • Chercher et cliquer sur Object Storage.
  • Créer l’objet en lui donnant un nom de service.
  • Créer un compartiment (= bucket).
  • Donnez-lui un nom (correspondra à BUCKET_NAME).
  • Choisissez une classe de stockage (Standard par exemple).
  • Aller dans « Configuration » pour trouver les informations de points d’extrémité.
  • C’est le point d’extrémité public qui nous intéresse, s3.eu-de.cloud-object-storage.appdomain.cloud dans mon cas. C’est notre PUBLIC_ENDPOINT_LOCATION .
  • Ensuite, direction « Données d’identification pour le service ».
  • Cliquer sur « Nouvelles données d’identification ».
  • Activer l’option « Inclure un identifiant HMAC ».
  • Cliquer sur « Ajouter ».
  • Copier les données d’identification ainsi créées.
  • Parmi ces données, celles qui nous intéressent sont access_key_id et secret_access_key.

Configuration restic

Pour la configuration de restic, je reste bref et vous renvoi à la documentation. J’utilise cet outil de sauvegarde depuis au moins deux années sans aucun problème et je me rends compte que je ne lui ai jamais consacré d’article, ce qu’il faudra que je corrige. Pour faire simple, je configure les paramètres de connexion comme variables d’environnement. Ensuite, j’initialise le dépôt distant avec la commande d’initialisation restic, afin de pouvoir ensuite y envoyer des données. L’URL du stockage distant est de la forme: s3:http://PUBLIC_ENDPOINT_LOCATION/BUCKET_NAME.

$ export AWS_ACCESS_KEY_ID=access_key_id
$ export AWS_SECRET_ACCESS_KEY=secret_access_key
restic -r s3:http://PUBLIC_ENDPOINT_LOCATION/BUCKET_NAME init

Conclusion

Après quelques heures de tests et de configuration, je dispose maintenant d’un deuxième emplacement de sauvegarde distant, sans coûts supplémentaires autre que le temps de mise en place. Avec déjà quelques semaines d’utilisation, je n’ai pas rencontré d’erreurs lors de l’exécution de mes sauvegardes journalières, ce qui est toujours agréable. Un petit bémol tout de même, il ne semble pas possible de créer plusieurs buckets différents en version gratuite. Tous mes essais se sont soldés par une erreur dans l’interface de création, malheureusement sans information sur la raison précise de l’erreur. N’hésitez donc pas à refaire une tentative pour vérifier si le problème était temporaire ou non. De mon côté, je me suis accommodé d’un bucket unique.

Cette deuxième sauvegarde accroît encore davantage mon niveau de sérénité et ma certitude d’avoir peu de risque de subir une perte de données. Il me reste encore à trouver une bonne solution pour effectuer périodiquement un déploiement local à partir de la sauvegarde et ainsi vérifier son intégrité.

Inspirations

Synapse: Procédure de mise à jour

J’effectue quelques essais autour de l’hébergement d’un serveur synapse pour le système de messagerie matrix. Petit pense-bête pour retrouver facilement la procédure de mise à jour du serveur synapse, sans avoir à la (re)chercher à chaque fois dans la documentation.

$ sudo systemctl stop matrix-synapse.service
$ sudo su synapse
$ cd ~/synapse
$ source env/bin/activate
$ pip install --upgrade matrix-synapse
$ pip install --upgrade pip
$ exit
$ sudo systemctl start matrix-synapse.service

Chiffrement d’une image de container

Le chiffrement d’une image de container est un sujet assez peu évoqué. Les premiers exemples de solution n’apparaissant qu’à partir de 2019, poussé en particulier par Brandon Lum de IBM.

Le support de ces fonctionnalités ne semble pas très répandu pour le moment. Du côté des outils, nous parlons actuellement de buildah et skopeo pour la partie build, containerd et cri-o pour la partie runtime et enfin docker distribution pour la partie registry. A priori, il ne semble pas y avoir de support dans docker, mais un commentaire au détour d’une issue GitHub semblait indiquer que cela sera le cas. Je n’ai en revanche pas trouvé d’annonce ou de roadmap permettant de valider ce point.

Voici à la suite, les étapes de mon test de chiffrement d’une image de container, réalisé sous ArchLinux, mais transposable à toute autre distribution, à condition de pouvoir installer l’ensemble de dépendances.

Préparation

Installation des Dépendances

Sous ArchLinux, installation des composants:

$ sudo pacman -S docker containerd buildah podman minikube kubectl helm
$ sudo systemctl start docker
$ sudo gpasswd -a <user> docker

Registre d’image local

Démarrage d’un registre local pour y stocker l’image chiffrée que je vais générer.

$ sudo systemctl start docker  
$ sudo systemctl status docker
$ sudo docker run -d -p 5000:5000 --restart=always --name registry registry:2

Génération du couple de clé

$ openssl genrsa -out testKey.pem 2048
$ openssl rsa -in testKey.pem -pubout -out testKey.pub.pem

Création d’une image basique

$ mkdir app
$ cd app
$ nano Dockerfile
$ nano secret-file

Avec pour contenu du Dockerfile:

FROM nginx:latest
COPY secret-file /secret-file

Et secret-file, un simple fichier texte contenant une chaîne de caractère aléatoire.

Construction de l’image

$ sudo buildah bud -t encrypted-test .

Export

Export vers le registre local.

$ sudo buildah push --tls-verify=false --encryption-key jwe:../testKey.pub.pem encrypted-test localhost:5000/vvision/encrypted-test:latest

Export local dans une archive oci.

$ sudo buildah push --encryption-key jwe:../testKey.pub.pem  encrypted-test oci-archive:encrypted-test:latest

Nettoyage

Suppression de toutes les images locales.

$ sudo buildah rmi --all

Récupération de l’image

$ sudo buildah pull --tls-verify=false --decryption-key keys/testKey.pem localhost:5000/vvision/encrypted-test

Exécution

$ sudo podman run -it localhost:5000/vvision/encrypted-test
/bin/bashroot@3e3c1ccde93c:/# cat secret-file
ASecretSecret

Vérification

Tentative de récupération de l’image depuis le registre local, sans préciser la clef.

$ sudo buildah pull --tls-verify=false localhost:5000/vvision/encrypted-test Getting image source signatures
Copying blob 302a0c0a162e [--------------------------------------] 0.0b / 914.0b
Copying blob 83e2b8dcdf4b [--------------------------------------] 0.0b / 27.1MiB
Copying blob 9b30e9f5d77e [--------------------------------------] 0.0b / 617.0b
Copying blob 0f9f80250abf [--------------------------------------] 0.0b / 134.0b
Copying blob 7c9b3bc4d85b [--------------------------------------] 0.0b / 26.3MiB
Copying blob 538a8875d492 [--------------------------------------] 0.0b / 678.0b
Error decrypting layer sha256:83e2b8dcdf4bfca8bea0b23e771e84074e4cc308bf892bd6d63b3a0c9dab0564: missing private key needed for decryption

Utilisation dans Kubernetes

Démarrage d’un cluster Kubernetes avec minikube.

$ minikube start --network-plugin=cni --enable-default-cni --container-runtime=cri-o --bootstrapper=kubeadm --insecure-registry="<local_ip_addr>:5000"
$ minikube dashboard

Mécanisme de synchronisation des clefs

Installation du composant responsable déploiement de la clef de déchiffrement.

$ git clone https://github.com/IBM/k8s-enc-image-operator.git
$ cd k8s-enc-image-operator
$ kubectl create namespace enc-key-sync
$ helm install --namespace=enc-key-sync k8s-enc-image-operator ./helm-operator/helm-charts/enckeysync/

Ajout de la clé dans Kubernetes Secret

$ kubectl create -n enc-key-sync secret generic --type=key --from-file=testKey.pem test-decryption-key

Déploiement du container

$ kubectl run test-enc --image=localhost:5000/vvision/encrypted-test

Conclusion

Au moment de mes premiers tests, je ne crois pas avoir rencontré de difficulté à récupérer l’image dans le registre local. Néanmoins, lors de tests plus récents, je n’ai pas réussi à reproduire ce fonctionnement sur d’autres plateformes. En revanche, une récupération depuis le registre docker, ou celui proposé dans la Google Cloud Plateform fonctionne.

Il me reste maintenant à reproduire ce mécanisme sur un cas réel complet, c’est-à-dire, de l’intégration au processus et aux outils de CI/CD, à la configuration du cluster Kubernetes cible, avec pour objectif un déploiement de la solution en production.

Sources

RackPi : Mise en place

Ayant fait l’acquisition d’un rack de taille moyenne l’an dernier, dans le but d’organiser et de ranger correctement tous mes appareils informatiques: switch, NAS, routeur, Raspberry Pi, onduleur, je cherchais une bonne manière d’organiser les Raspberry Pi. Après quelques recherches parmi les modèles 3D existants sur Thingiverse et Prusa Printers, j’ai trouvé le projet RackPi: nombreuses photos, documentation plutôt complètes, étapes d’assemblage claires. Il ne restait plus qu’à me lancer dans la construction de mon exemplaire.

Assemblage

Quelques notes concernant l’assemblage. Premièrement, les résistances, celle de 1k ohm est à utiliser avec la led et celle de 10k ohm sert pour le bouton poussoir. Pour les vis, j’utilise du côté des headers, une M2.5 de 14mm de longueur, afin de pouvoir sécuriser par la suite un PoE hat. De l’autre côté, j’ai fait le choix d’une M2.5 de longueur 10mm. Les différents composants sont maintenus en place à coup de pistolet à colle. Étape incontournable avant l’encollage: tester, tester et tester ! C’est du bon sens, mais cela évite de devoir tout démonter après coup. Illustration: dans le deuxième exemplaire que j’ai assemblé, ma partie bouton ne fonctionnait pas et j’ai donc pu en refaire une nouvelle avant de procéder à l’assemblage.

Gros plan sur le circuit du bouton poussoir.

Câblage

En suivant le câblage et en m’aidant du document de référence concernant les GPIO du Raspberry Pi, j’obtiens la configuration suivante.

LED

(+) -> Fil jaune -> Pin 16 -> GPIO 23
(-) -> Fil orange -> Pin 14 -> GROUND

Bouton marche/arrêt

Fil violet -> Pin 39 -> GROUND
Fil bleu -> GLOBAL_EN

Bouton poussoir

Fil violet -> Pin 17 -> POWER 3V3
Fil gris -> Pin 38 -> GPIO 20
Fil blanc -> Pin 25 -> GROUND

Écran

SDA -> Fil vert -> Pin 3 -> GPIO 2
SCL -> Fil jaune -> Pin 5 -> GPIO 3
VCC -> Fil rouge -> Pin 1 -> POWER 3V3
GND -> Fil noir -> Pin 6 -> GROUND

Câblé et prêt à coller.

Configuration du pi

Activation de I2C et SPI via raspi-config:

$ sudo raspi-config
-> Interfacing Options
-> SPI
-> I2C

Installation des dépendances nécessaires:

$ sudo apt-get install python3-pip
$ sudo pip3 install adafruit-circuitpython-ssd1306
$ sudo apt-get install python3-pil
$ sudo apt-get install -y python-smbus
$ sudo apt-get install -y i2c-tools
$ sudo pip3 install psutil

Vérification du câblage de l’écran. Celui-ci doit apparaître à l’adresse 3c.

sudo i2cdetect -y 1

Ensuite, récupération du script d’affichage des informations sur l’écran. Deux URL possibles, celle du fichier sur Thingiverse, ou celle pointant vers le même script dans un dépôt sur Github que je contrôle. De cette manière, je peux m’assurer que le code exécuté et bien celui que j’ai relu et qu’il n’a pas été modifié ultérieurement sur Thingiverse, dans le cas d’une installation ou d’une réinstallation future.

# Utilisation du fichier "officiel".
$ wget https://www.thingiverse.com/download:8343823 -O infoscreen.py
# Utilisation d'une copie du fichier dans un dépôt que je contrôle.
$ wget https://raw.githubusercontent.com/vvision/rack-pi/main/infoscreen.py
$ sudo chmod 755 infoscreen.py

Exécution du script pour vérifier son fonctionnement.

$ sudo python3 infoscreen.py

Afin de s’assurer que le script est lancé automatiquement au démarrage, on modifie rc.local en ajoutant la ligne suivante, juste avant la ligne exit 0, via sudo nano /etc/rc.local et en modifiant le chemin vers l’emplacement du script sur le disque si besoin.

sudo python3 /home/pi/infoscreen.py &

Côté contrôle, un appui simple sur le bouton permet d’allumer l’écran. Un appui d’environ 8s déclenche un redémarrage et au-dessus de 12s, le pi déclenchera un shutdown. En cas d’arrêt, si vous avez câblez le bouton slider sur le pin central EN/GLOBAL_EN de votre pi 3+ ou de votre pi 4, il est possible de couper l’alimentation du pi grâce à ce bouton une fois le pi éteint.

Conclusion

Quelques mots pour terminer. Le résultat est extrêmement satisfaisant, mon Raspberry Pi est maintenant correctement installé dans le rack (ce sera encore mieux dès que j’aurais trouvé des vis auto-taraudantes correctes). L’écran est utile pour avoir l’adresse IP du Pi, surtout dans les premiers moments de mise en place, le temps de configurer une IP statique au niveau du DNS local. Sinon, les informations d’utilisation du CPU et de la RAM sont toujours bonne à prendre lorsqu’on s’interroge sur la charge du Pi, et deviennent donc facilement accessible. Les deux boutons permettant de contrôler extinction, redémarrage et de maintenir éteint le Pi sont également utiles puisque je peux désormais effectuer ces opérations sans avoir à ouvrir une connexion SSH.

En bref, un projet intéressant, qui ne se limite pas à installer quelques lignes de codes, mais permet de pratiquer mes compétences en soudure, de découvrir la partie GPIO des Raspberry PI et au final d’améliorer l’organisation du rack.

exim4

J’ai effectué courant décembre 2020, une migration de mes services auto-hébergés vers une nouvelle machine. Ayant réorganisé en 2019 l’ensemble de mes roles Ansible, et passant d’une base Debian 9 à Debian 10, de nombreuses corrections ont une nouvelle fois été nécessaires avant que l’ensemble soit à nouveau déployé correctement de manière automatique. La majorité des adaptations provenaient du passage à php7.4, avec un test de la version 8 (encore trop récente pour plusieurs services). Le reste concernait l’envoi de mail depuis le serveur avec exim4.

En commençant l’aventure de l’auto-hébergement, la question de l’envoi des mails ne s’était pas posée, car, dans mes souvenirs en tout cas, WordPress arrivait à me notifier par mail sans avoir de modification à effectuer au niveau du système Debian fournit avec le serveur Kimsufi que j’utilisais à l’époque. En migrant par la suite sur un VPS et en auto-hébergeant certains services à mon domicile, l’envoi de mail avait cessé de fonctionner et j’avais choisi d’ignorer la question jusqu’à la mie 2019, où j’avais réussi à produire une configuration fonctionnelle, en utilisant les serveurs de mail de l’hébergeur. Une nouvelle migration d’Unicoda courant 2020 avait à nouveau rendu l’envoi de mail inopérant. Idem avec la récente migration de ma machine locale.

Afin de continuer de recevoir les notifications liées à l’exécution des sauvegardes journalières de l’ensemble des services, et de bénéficier à nouveau des notifications WordPress, comme celles des nouveaux commentaires, je me suis donc replonger dans les profondeurs de la configuration des services mails sous GNU/Linux, afin de corriger le rôle Ansible responsable de la configuration automatique des services mails.

Voici donc à la suite, le résultat de mes notes de 2019, adaptées et corrigées après les recherches de décembre l’an dernier. J’espère ne pas avoir oublié d’étapes, mais les essais successifs de paramètres différents conduisent parfois à des incohérences, lorsque les notes ne sont pas mises à jour avec les dernières modifications effectuées à l’issue d’une session éreintante de débogage :). Par ailleurs, si mon rôle Ansible a bien été mis à jour, il me reste néanmoins à le tester une nouvelle fois, sur un système vierge, dans l’idéal, afin de garantir son bon fonctionnement. Cela étant dit, voici la configuration qui permet à mes serveurs d’envoyer des mails.

Configuration

Commençons par installer les composants nécessaires:

sudo aptitude install exim4 openssl ca-certificates mailutils

Je modifie ensuite /etc/email-addresses pour lier utilisateurs locaux et adresses mails. Ici, le but est de renvoyer tout vers l’adresse example@unicoda.com (adresse fictive pour l’exemple):

root : example@unicoda.com
monUtilisateur : example@unicoda.com
* : example@unicoda.com

Manipulation similaire, cette fois dans /etc/aliases , puis j’exécute la commande newaliases :

root: monUtilisateur
monUtilisateur : example@unicoda.com

Je modifie ensuite le fichier /etc/exim4/passwd.client, pour y ajouter une ligne contenant les informations de connexion au serveur mail choisi pour l’envoi, de la forme target.mail.server.example:login:password , et lui applique un masque 640 pour les autorisations d’accès.

Avant d’aller plus loin dans la configuration, je m’assure également que mon fichier /etc/hosts contient une ligne faisant pointer le hostname de la machine vers elle-même, soit la ligne 127.0.0.1 hostname. Par ailleurs, je modifie également /etc/mailname pour y ajouter le hostname, cette modification m’ayant été particulièrement utile pour le cas d’un message à destination d’un utilisateur de la machine, soit par exemple root@hostname.

Étape suivante, déploiement du fichier /etc/exim4/exim4.conf.localmacros avec le contenu suivant, pour l’utilisation de TLS et du port 465:

MAIN_TLS_ENABLE = 1
REMOTE_SMTP_SMARTHOST_HOSTS_REQUIRE_TLS = *
TLS_ON_CONNECT_PORTS = 465
REQUIRE_PROTOCOL = smtps
IGNORE_SMTP_LINE_LENGTH_LIMIT = true

Avant de continuer plus avant, il faut créer un certificat TLS pour exim4, on peut se baser pour cela sur l’étape 4 de ce tutoriel, qui donne la commande à exécuter. De mon côté, j’utilise les modules openssl_privatekey, openssl_csr et openssl_certificate dans ansible pour réaliser ces opérations.

J’apporte quelques modifications au fichier /etc/exim4/update-exim4.conf.conf, correspondant aux étapes 9 et 10 du tutoriel cité ci-dessus, à savoir, l’ajout du bloc suivant, juste avant la ligne .ifdef REMOTE_SMTP_HEADERS_REWRITE (étape 9):

.ifdef REQUIRE_PROTOCOL
  protocol = REQUIRE_PROTOCOL
.endif

Ainsi que le bloc suivant, juste après la ligne (étape 10):

.ifdef TLS_ON_CONNECT_PORTS
  tls_on_connect_ports = TLS_ON_CONNECT_PORTS
.endif

Enfin, édition du fichier /etc/exim4/update-exim4.conf.conf pour spécifier le comportement d’exim4, qui peut également être généré dynamiquement via sudo dpkg-reconfigure exim4-config. À noter, que le mode satellite ne prend pas en considération l’envoi local, et ne tient donc pas compte des alias configurés (comme précisé dans ce message). Pour être complet, je lui ai donc préféré le mode smarthost. J’ai pour ma part changé le dc_readhost par rapport au tutoriel dont je me suis inspiré, qui proposait localhost comme valeur. Je crois me souvenir que localhost ne fonctionnait pas dans mon cas, mais une nouvelle série de tests avec cette configuration ne serait pas inutile pour être fixé.

dc_eximconfig_configtype='smarthost'
dc_other_hostnames=''
dc_local_interfaces='127.0.0.1 ; ::1'
dc_readhost='unicoda.com'
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets=''
dc_smarthost='target.mail.server.example::465'
CFILEMODE='644'
dc_use_split_config='false'
dc_hide_mailname='true'
dc_mailname_in_oh='true'
dc_localdelivery='mail_spool'

Nous y sommes presque, il suffit à présent de déployer la configuration exim4 via sudo update-exim4.conf, puis de redémarrer le service avec la commande sudo service exim4 restart. On peut ensuite essayer d’envoyer un mail depuis la machine avec echo "test" | mail -s "Test" example@unicoda.com. Si tout va bien, le mail arrive correctement à l’adresse fournie, sinon, il vous faudra, comme moi, parcourir les logs (sudo tail /var/log/exim4/mainlog) pour comprendre la nature du problème de configuration et parcourir les nombreux sujets et documentation autour d’exim4.

Pour finir, avant une liste d’article qui m’avait été utile lors de la configuration, voici encore quelques commandes utiles pour déboguer une configuration exim4 non fonctionnelle.

Commandes

Consulter la liste des messages gelés.

exim4 -bp

Supprimer tous les messages gelés.

exiqgrep -z -i | xargs exim -Mrm

Sources