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

Nouveau disque chiffré dédié à la sauvegarde

Ayant fait l’acquisition début d’année, d’un nouveau disque vierge d’une capacité de stockage de 1To, je me suis enfin occupé de sa configuration, afin de pouvoir y déposer les données numériques dont je souhaite absolument éviter la perte.

Mon choix s’est porté sur un disque de marque Seagate au format 2.5″, et j’ai choisi un boîtier « Mobility Disk S8 » de la marque Advance, pour protéger le disque et simplifier sa connexion comme disque externe. Esthétiquement parlant, le boîtier est très bien, sobre, en aluminium et simple dans l’installation du disque. Ce sera désormais mon choix par défaut pour les prochains disques externes dont je pourrais avoir besoin. En cas de problème sur le disque ou l’interface de connexion, il est en effet bien plus simple de changer l’un ou l’autre. Chose qui est impossible sur les disques externes tout assemblés comme ceux de Western Digital, où toute l’électronique est solidaire du disque.

Mon disque externe le plus récent de marque Western Digital. Le premier de mes disques à faire défaut.

Place ensuite à l’étape d’initialisation du disque chiffré et direction mon article « Chiffrer un support » de 2018, pour suivre la procédure et vérifier que celle-ci est toujours valable; ce qui est le cas.

Au passage, après avoir généré aléatoirement et stocké le mot de passe de la partition chiffrée dans pass, sous une arborescence spécifique crypt/usb/ et sauvegarder dans une entrée nommée suivant l’UUID du disque, j’en profite pour mettre à jour ma configuration de udiskie. udiskie, programme qui s’occupe de la gestion des médias amovibles sur mon système.

Je modifie donc le fichier .config/udiskie/config.yml pour y ajouter le contenu suivant:

program_options:
  tray: auto
  automount: false
  notify: true
  password_prompt: ["pass", "crypt/usb/{id_uuid}"]

Avec ces quelques lignes, j’indique au programme d’exécuter la commande pass pour accèder au mot de passe stocké dans crypt/usb et identifié par son UUID, d’où le choix du nommage au paragraphe précédent. UUID du disque qu’il est possible de connaître de plusieurs manières (décrites dans le wiki ArchLinux, partie Persistent block device naming – by-uuid), dont celle-ci:

$ ls -l /dev/disk/by-uuid/

On effectue ensuite un petit test pour vérifier le bon fonctionnement de la configuration via la commande:

$ udiskie-mount -r /dev/sdX

Cela fonctionne correctement. Après un redémarrage, je constate néanmoins que c’est la fenêtre de demande de mot de passe par défaut qui s’affiche lorsque j’utilise PCmanFM comme explorateur de fichier. Il me semble avoir résolu le problème en désactivant le montage automatique configuré dans PCmanFM. C’est alors bien udiskie qui prend la main.

Enfin, dernière étape incontournable pour conclure, la sauvegarde des entêtes du disque chiffré, afin de pouvoir restaurer un accès aux données chiffrées en cas de corruption des entêtes présentes sur le support. Une corruption des entêtes d’un disque chiffré entraîne en effet une impossibilité totale d’accéder à l’ensemble du contenu, et cela, même si cette partie du disque est parfaitement saine. C’est donc une opération à faire absolument, lorsqu’on décide de chiffrer un disque et on s’évitera ainsi des sueurs froides en cas de problème sur les entêtes.

Sauvegarde des entêtes LUKS

Petit pense-bête pour noter ici la procédure de sauvegarde des entêtes d’une partition chiffrée luks pour un disque nommé carbone, reconnu sur /dev/sde et stocker les informations dans une entrée de mon gestionnaire de mot de passe pass. Le tout, de la façon la plus sécurisée possible, avec l’utilisation d’un dossier monté en ram via ramfs, afin que l’information ne soit jamais écrite en clair sur le disque.

$ sudo mkdir /mnt/tmp
$ sudo mount ramfs /mnt/tmp -t ramfs
$ sudo cryptsetup luksHeaderBackup /dev/sde --header-backup-file /mnt/tmp/dump
$ sudo chown vvision:vvision /mnt/tmp/dump
$ pass insert -m crypt/luksheader/carbone < /mnt/tmp/dump
$ sudo umount /mnt/tmp
$ sudo rmdir /mnt/tmp

Procédure trouvée sur l’excellent blog de P. Hogg. Article LUKS Header Backup.

Réinitialisation du jeton cryptographique Gnuk

Après avoir réalisé quelques tests du jeton cryptographique fraîchement créé à partir d’un ST-Linkv2 et de Gnuk, j’ai cherché à nettoyer les clefs stockées sur ce jeton de test. Mes recherches m’ont guidé vers le dossier tool du dépôt gnuk, qui contient plusieurs scripts permettant de réaliser divers opérations sur le jeton. L’instruction factory-reset ne fonctionnant pas sur les jetons Gnuk, je me suis tourné vers le script gnuk_remove_keys_libusb.py. Script qui nécessite d’arrêter gpg-connect-agent avant d’être exécuté. Je n’ai au final pas pu valider son fonctionnement, pour la raison que vous découvrirez au paragraphe suivant.

gpg-connect-agent "SCD KILLSCD" "SCD BYE" /bye
./tool/gnuk_remove_keys_libusb.py -p

Lors de mes premières tentatives, je n’avais pas utilisé le paramètre p de la commande et celle-ci utilisait donc le PIN par défaut à savoir 12345678 pour le PIN admin. Ayant changé ce dernier, j’ai très vite atteint la limite d’essai autorisé et ai rendu mon jeton inopérant. Il a donc fallu s’intéresser à la façon de réinstaller Gnuk sur un jeton bloqué.

Pour effectuer la reprogrammation, il faut connecter le jeton à notre ST-Link comme dans l’article précédent. Afin de déverrouiller le jeton, il est également nécessaire de connecter les pins 7 (NRST) et 8 (VSSA) du micro-contrôleur STM8F103. Une fois les deux pins connectés, avec la pointe de mesure d’un multimètre par exemple, il faut lancer openocd comme précédemment. Une fois le programme en cours de fonctionnement, sans erreur ni tentative permanente de reconnexion, on peut arrêter de maintenir la connexion entre les deux pins.

Pour repérer les deux pins concernés, voir le schéma ci-dessous.

Pins d’un STM32F103C8T6

L’opération n’étant pas des plus évidentes, j’ai dû effectuer plusieurs tentatives avant de pouvoir reprendre la main pour reprogrammer le micro-contrôleur. Par ailleurs, impossible de me reconnecter correctement aux pins du ST-Link, j’ai donc été obligé de sortir le fer à souder, d’autant plus que le STM32 est positionné de l’autre côté de la carte et donc impossible d’y accéder pour connecter les deux pins en utilisant le précédent système de connexion.

Le montage après reprogrammation et juste avant de retirer les fils soudés.

Pas de changement dans les instructions de programmation à part l’ajout du mass_erase pour supprimer tout le contenu de la mémoire.

halt
stm32f1x unlock 0
reset halt
stm32f1x mass_erase 0
flash write_bank 0 ./src/build/gnuk-vidpid.bin 0
stm32f1x lock 0
reset halt

Après reprogrammation, le jeton est à nouveau vierge de toutes informations et prêt à recevoir de nouvelles clés!

Utilisation du jeton cryptographique Gnuk de secours

Dans un précédent article, j’avais décrit la manière de reprogrammer un STLinkv2 pour en faire un jeton cryptographique Gnuk. J’aurais pu m’arrêter là dans mes tests, mais alors, je n’aurais pas eu la garantie que ma solution de secours est valable. J’ai donc vérifié ma procédure, pas à pas, en reprenant si besoin les différents articles faisant offices de documentation. Voici quelques éléments à considérer.

Pour mener à bien la création d’un nouveau jeton cryptographique contenant les mêmes clés que mon jeton source, je commence par réinitialiser l’environnement sur le poste sécurisé ayant servi à la création des clés, après avoir pris soin de déchiffrer le dossier de sauvegarde contenant un exemplaire des clés :

export GNUPGHOME=/path/to/working/directory
cp dir-backup-mastersubkeys/* $GNUPGHOME/*

Pour la suite, l’export des clés sur le jeton s’effectue comme décrit dans les parties « Configuration de la cible » et « Export des sous-clefs vers la Yubikey » de mon article intitulé « GnuPG, clefs, YubiKey : c’est parti« . Par ailleurs, avant de commencer l’export des clés et pour compléter la configuration du jeton, on exécutera la commande kdf-setup dans l’éditeur de cartes GnuPG en mode admin, le but étant de renforcer la sécurité des clefs (Pour plus de détails à ce sujet, lire la partie « Protection des clefs » de l’article « Gnuk, NeuG, FST-01 : entre cryptographie et matériel libre« ). Après import des clefs, on dispose alors d’un nouveau jeton cryptographique prêt à être utilisé.

Afin de faire fonctionner ma clé Gnuk avec gpg –card-status sans utiliser sudo, ajout d’une règle udev dans le fichier /lib/udev/rules.d/60-gnuk.rules.

ACTION=="add", SUBSYSTEM=="usb", ENV{ID_VENDOR_ID}=="0000", ENV{ID_MODEL_ID}=="0000", MODE="660", GROUP="users"

Puis application des changements.

# rechargement des règles
sudo udevadm control --reload-rules
# Éventuellement redémarrage du système
sudo reboot

Sur le poste cible, sauvegarde du dossier .gnupg/private-keys-v1.d. Puis suppression de son contenu.

cp -r .gnupg/private-keys-v1.d .gnupg/private-keys-v1.d-save
rm .gnupg/private-keys-v1.d/*
gpg --card-status

La commande gpg –card-status a pour effet de réimporter les informations des clefs présentes sur la carte. Ainsi, si j’effectue une commande pass pour déchiffrer l’un de mes mots de passe, c’est bien la nouvelle clé Gnuk qui est attendue et non ma Yubikey. Pour repasser à la Yubikey, il suffit de supprimer à nouveau le contenu du dossier private-keys-v1.d et le tour est joué (et d’inverser jeton Gnuk et YubiKey).

Je note qu’il n’est pas possible en l’état d’utiliser deux supports différents pour les mêmes clefs, sans une intervention de l’utilisateur ou une automatisation des changements à effectuer en fonction du support branché. Pour utiliser indifféremment deux supports différents, un internaute proposait de créer trois sous-clefs supplémentaires, soit six en tout et de répartir les trois nouvelles sous-clefs sur le support supplémentaire.

Effectuer ce que je peux qualifier de « test grandeur nature » était ici indispensable, afin de s’assurer de la robustesse de la solution retenue pour la sécurisation de mon environnement informatique. En effet, s’appuyer sur une solution sans jamais avoir vérifié les procédures de restauration, le retour à un fonctionnement normal, me semble bien périlleux. C’est donc un pas de plus sur le chemin de ma résilience informatique !

Source:
GnuPG mailing list – GnuPG card && using the backup secret key