Files
ENI-projet-piscine/docs/adr/0020-chiffrement-au-repos-coffre-luks-et-sse-c.md
Johan LEROY f011fce84e docs(adr): constate que la machine ENI est un conteneur LXC, LUKS y est impossible
La garde de scripts/coffre-luks.sh refuse un conteneur LXC ou l'absence de device-mapper.
L'ADR 0020, 10-infra.md et la vue d'ensemble disent ce qui est en place (SSE-C des archives)
et ce qui relève de l'hôte Proxmox (chiffrement du disque du conteneur).
2026-09-24 10:54:06 +02:00

110 lines
8.4 KiB
Markdown

# 0020 - Chiffrement au repos : coffre LUKS des volumes Docker et SSE-C des archives
- Statut : accepté
- Date : 2026-09-24
## Contexte
L'issue #42 demande que les données de la plateforme soient chiffrées au repos. Tout ce que la
plateforme persiste vit dans les volumes Docker nommés des trois projets Compose de la VM ENI
([ADR 0009](0009-deux-environnements-compose-sur-la-vm-eni.md),
[ADR 0017](0017-environnement-dev-a-la-demande.md)), sous `/var/lib/docker/volumes` : la base
TimescaleDB (`pgdata`, relevés, comptes, audit), les métadonnées et les objets de Garage
(`garage_meta`, `garage_data`, les archives des chunks de `reading` exportées par le DAG
`retention`, [ADR 0019](0019-stockage-objet-garage-et-cycle-de-vie-des-mesures.md)), les journaux et l'état ML d'Airflow, les séries de Prometheus et la base
de Grafana.
Aucun des deux dépôts de données ne chiffre lui-même : Garage n'a pas de chiffrement côté serveur
et sa documentation renvoie à un volume LUKS sous ses données ; PostgreSQL communautaire n'a pas de
chiffrement transparent des données (TDE), et l'image `timescaledb-ha` n'en ajoute pas. La VM est
unique, sur un seul disque virtuel, sans partition libre, sans TPM, et personne n'est devant sa
console au démarrage : tout redémarrage doit aboutir sans saisie.
## Décision
**Constat du 24/09, qui borne tout ce qui suit.** La machine `eadl-2025-nantes-g3` n'est pas une
machine virtuelle mais un conteneur LXC Ubuntu 24.04 sur un hôte Proxmox (`systemd-detect-virt`
répond `lxc`, aucun `/dev/mapper/control`, aucun périphérique loop, pas de `/dev/fuse`, module
`dm_crypt` inaccessible). LUKS, comme tout chiffrement au niveau bloc ou FUSE, y est impossible.
Le chiffrement au repos du disque de ce conteneur ne peut se faire que sur l'hôte Proxmox
(volume LUKS ou ZFS chiffré sous le conteneur), par l'administrateur de l'école : la demande
lui est adressée, et jusqu'à sa réponse la base et les métadonnées Garage sont en clair sur ce
disque. `scripts/coffre-luks.sh` détecte ce cas et refuse de démarrer. Ce qui suit reste la
décision pour toute machine où le device-mapper est disponible (la cible k3s de `10-infra.md`,
ou une vraie VM), et le SSE-C des archives est en place dès aujourd'hui.
**Un coffre LUKS2 sous tous les volumes Docker, posé par `scripts/coffre-luks.sh`.**
- Le coffre est un **fichier image creux** (`/srv/enervision/coffre.img`, 30 Go par défaut)
formaté en **LUKS2**, ouvert par une **clé de 64 octets tirée de `/dev/urandom`**, lisible par
root seulement (`/root/enervision-coffre.key`, `0400`). Un fichier plutôt qu'une partition : la
VM n'en a pas de libre, et l'image se déplace ou se sauvegarde comme un fichier.
- Le mapper `enervision-coffre` porte un ext4 monté sur `/srv/enervision/coffre`, et
`/var/lib/docker/volumes` est **bind-monté** depuis `/srv/enervision/coffre/docker-volumes`.
Docker ne voit qu'un dossier ordinaire : ni `data-root`, ni les fichiers Compose, ni les noms
de volumes ne changent, et les trois environnements sont couverts d'un coup.
- L'ouverture et les montages sont déclarés dans **`/etc/crypttab` et `/etc/fstab`, avec
`nofail`** sur les trois lignes : un coffre absent ne doit jamais envoyer la machine en mode
urgence, où SSH ne répond plus. Sur Debian 13 le générateur crypttab est dans le paquet
`systemd-cryptsetup`, installé par le script s'il existe dans apt.
- Un **drop-in `RequiresMountsFor=/var/lib/docker/volumes`** sur `docker.service` fait la
barrière : sans le bind, Docker ne démarre pas, plutôt que de recréer des volumes vides en
clair et de laisser trois stacks se lever sur des bases neuves.
- Le script est **rejouable** : clé, image, formatage, système de fichiers, crypttab, fstab et
drop-in ne sont posés que s'ils manquent, et il sort sans rien toucher si
`/var/lib/docker/volumes` est déjà servi par le coffre. La **migration à froid** des volumes
existants n'a lieu qu'avec `COFFRE_MIGRER=1` : refus si `live-restore` est actif, arrêt de
`docker.socket` et `docker.service`, `rsync -aHAX --numeric-ids`, comparaison du nombre et de la
taille des fichiers, puis bascule du dossier et redémarrage de Docker. L'ancien dossier reste en
`/var/lib/docker/volumes.avant-coffre` jusqu'à validation par un redémarrage.
- Terraform peut le jouer : `null_resource.coffre`, activé par `coffre_taille` non vide,
s'exécute après Docker et avant `provision-host.sh`. La ressource est optionnelle et absente du
plan tant que la variable est vide.
**SSE-C sur les archives exportées vers Garage.** Le module d'export du DAG `retention` envoie
chaque archive avec une clé client (`GARAGE_SSE_KEY`, générée dans le `.env` par
`provision-host.sh`) ; Garage la chiffre en AES-256-GCM et n'en garde que l'empreinte. Les objets
sont donc chiffrés une seconde fois, avec une clé distincte de celle du coffre, dans le seul
dépôt que l'on pourrait un jour sortir de la VM.
## Alternatives écartées
| Écartée | Raison |
|---|---|
| `pgcrypto`, chiffrement par colonne | Ne couvre ni les index, ni les journaux WAL, ni Garage, ni Airflow ; la clé serait dans l'application, à côté des données, pour un coût de développement et de requête sur chaque lecture d'hypertable. |
| Déplacer le `data-root` de Docker dans le coffre | Chiffre aussi les images et les couches, sans valeur, et impose de recopier tout `/var/lib/docker` : plus long, plus de place, et le démon doit être reconfiguré. Seuls les volumes portent des données. |
| Chiffrer côté client dans le module d'export | Couvre les archives et rien d'autre, avec une bibliothèque cryptographique à porter dans le code métier alors que Garage offre SSE-C. Retenu seulement sous cette forme, en complément du coffre. |
| Volume Docker chiffré par un plugin | Un plugin tiers par volume nommé, à installer et suivre sur la machine, pour huit volumes par environnement ; le coffre les couvre tous d'un bind. |
| Disque ou partition dédiée | La VM n'a qu'un disque virtuel, sans partition libre, et son redimensionnement n'est pas dans les mains de l'équipe. |
| Clé saisie au démarrage | Personne devant la console ; un redémarrage de la VM par l'école laisserait la plateforme arrêtée jusqu'à intervention. |
| Clé scellée dans un TPM | La VM n'en expose pas. |
## Conséquences
- **Ce que le coffre protège, et ce qu'il ne protège pas.** La clé et l'image vivent sur le même
disque. Le coffre protège une copie isolée de l'image ou du disque : snapshot, sauvegarde,
décommissionnement du disque virtuel. Il ne protège ni du vol du disque entier, où la clé se
trouve aussi, ni d'un root sur l'hôte allumé, qui lit le montage en clair. La copie
`.avant-coffre`, supprimée après validation, n'est pas effaçable physiquement sur un disque
virtuel. La clé SSE-C transite en clair sur le réseau Compose interne, entre `airflow-scheduler`
et Garage, à chaque objet envoyé.
- **Perte de la clé, perte de tout.** Sans `/root/enervision-coffre.key`, l'image est illisible
et les trois bases avec elle. La clé est à sauvegarder hors de la VM tout de suite après la
pose, dans un emplacement que seuls les administrateurs lisent.
- **Coupure lors de la migration.** La copie des volumes se fait Docker arrêté : les trois
environnements sont indisponibles une à trois minutes, et le disque doit porter deux fois la
taille des volumes jusqu'à la suppression de `.avant-coffre`.
- **Redémarrage de test obligatoire.** L'ordonnancement crypttab, fstab, drop-in ne se vérifie
qu'en redémarrant : `findmnt /var/lib/docker/volumes` et `docker ps` après le reboot, avant de
supprimer la copie en clair.
- **Docker dépend du coffre.** Si l'image ou la clé disparaît, Docker refuse de démarrer
(`dependency failed`) et la machine reste joignable par SSH ; c'est voulu. Retirer la ligne
fstab du bind retire cette protection sans message.
- **Rotation.** La clé LUKS se change par `cryptsetup luksChangeKey` sans réécrire les données.
`GARAGE_SSE_KEY` ne se change pas sans réécrire chaque objet : Garage n'a pas de re-chiffrement
côté serveur, et un objet écrit avec l'ancienne clé ne se lit qu'avec elle.
- **Terraform interrompt la stack, une fois.** La première pose avec `coffre_taille` est la seule
ressource de `vm-eni` qui arrête Docker, en contradiction assumée avec
l'[ADR 0010](0010-terraform-provisionne-github-actions-deploie.md) pour cette seule occasion ;
les `apply` suivants trouvent le coffre en place et n'y touchent pas.