Activer le chiffrement après l'installation

Vaks PM · Chiffrement jour 2 · août 2026

Objet de ce guide. Une instance Vaks PM peut être installée sans chiffrement, puis chiffrée plus tard, une couche à la fois, sur un cluster déjà en service qui porte de vraies données. C'est le cas courant : une organisation démarre en clair, puis un audit (DORA, ISO 27001) ou une exigence contractuelle impose le chiffrement. Ce guide décrit l'activation a posteriori, de deux façons au choix. Pour une nouvelle installation née déjà chiffrée, voir Déploiement Docker Swarm : couches de chiffrement.

Vue d'ensemble

Chaque couche de chiffrement s'active indépendamment, avec sa propre procédure, son propre niveau de risque et son propre impact sur le service. Deux couches se pilotent depuis l'application (elles ne sont pas traitées ici comme des opérations d'hôte) :

Les couches d'infrastructure (disque LUKS, TLS interne PostgreSQL, overlay IPsec, etcd) touchent aux hôtes et à leurs services système. Ce sont elles que les deux méthodes ci-dessous adressent.

Les couches, par niveau de risque

CoucheNiveauInterruptionCe qu'elle protège
Rotation des clés (DEK)SûrAucuneSecrets applicatifs au repos (MFA, SMTP, SSO, connecteurs).
Sauvegardes (age)SûrAucuneArtefacts de sauvegarde au repos et en transit.
TLS PostgreSQLSûrAucune (redémarrage progressif)Trafic base de données interne.
Disque au repos (LUKS)SûrAucune (nœud par nœud)Volumes de données (base + stockage objet) au repos.
Réseau overlay (IPsec)Fenêtre~1 à 2 minTout le trafic inter-conteneurs (cache, S3, HTTP/WS interne).
TLS etcdFenêtreBrève (DCS)Trafic du magasin de coordination du cluster.
Ne jamais toucher aux données sans point de restauration. Avant toute couche qui déplace des données (LUKS surtout), confirmer une sauvegarde récente et vérifiée et vérifier que le cluster est sain — patronictl -c /etc/patroni.yml list doit montrer un seul primaire et des répliques à jour (lag 0), et le stockage objet doit avoir son quorum.

Quelle méthode choisir ?

Les deux méthodes produisent exactement le même résultat. Elles diffèrent par l'on tape les commandes.

Méthode A — orchestréeMéthode B — manuelle
Depuis une station de contrôle (une seule machine, avec accès SSH par clé aux hôtes).En SSH sur chaque hôte concerné, tour à tour.
CommentUne commande par couche ; l'orchestrateur se connecte lui-même aux hôtes, gère le séquencement et vérifie.Les commandes système réelles (cryptsetup, clevis, patronictl, docker…), copiées-collées hôte par hôte.
PrérequisLe dépôt infra/crypto/ et le .env.local d'accès SSH présents sur la station.Aucun outillage ; un accès SSH root à chaque hôte.
Idéal pourRépétabilité, plusieurs clusters, opérateur qui a déjà le dépôt de déploiement.Transparence totale, environnements verrouillés sans la station de déploiement, compréhension pas à pas.

Choisir l'une ou l'autre ; il n'est pas nécessaire de faire les deux. TLS etcd et Rotation des clés sont identiques quelle que soit la méthode et sont décrites une fois, à la fin.

Méthode A — orchestrée depuis une station de contrôle

L'outillage infra/crypto/retrofit.mjs tourne sur la machine d'administration (le même poste, avec le même accès SSH par clé, que le déploiement). Il se connecte lui-même à chaque hôte, applique la couche, gère le séquencement nœud par nœud et vérifie. Vous ne faites aucun SSH manuel. Toutes les commandes de cette section se lancent depuis la station de contrôle.

Séquencement : retrofit.mjs all n'exécute que les couches sûres, sans interruption (sauvegardes, TLS PostgreSQL, LUKS). Les couches à fenêtre de maintenance (overlay, etcd) exigent un drapeau explicite. Options communes : --dry-run (n'applique rien), --force (non interactif), --hosts a,b,c.

Disque au repos (LUKS)

Prérequis Tang. Le déverrouillage réseau LUKS s'appuie sur des serveurs Tang sur les hôtes pairs, qu'une installation née en clair n'a pas. Les installer d'abord (voir la préparation Tang de la méthode manuelle, à faire sur chaque hôte de base).
# station de contrôle
node infra/crypto/retrofit.mjs enable-luks --pg --force     # ajouter --garage pour le stockage objet

L'orchestrateur chiffre les répliques d'abord, bascule le primaire vers une réplique chiffrée, puis chiffre l'ancien primaire — sans interruption. L'opération est idempotente (la relancer saute les nœuds déjà chiffrés).

TLS interne PostgreSQL

# station de contrôle
node infra/crypto/retrofit.mjs enable-pg-tls --force

Pose ssl=on dans la configuration du cluster et redémarre les membres un par un (répliques d'abord). L'application reste disponible (PostgreSQL accepte encore les connexions non-TLS). Le passage de l'application en TLS est une seconde passe séparée.

Sauvegardes chiffrées (age)

Prérequis : age installé sur les hôtes de sauvegarde et une clé générée (voir la méthode manuelle pour age-keygen) ; garder la clé privée en séquestre hors cluster.

# station de contrôle
node infra/crypto/retrofit.mjs enable-backup-encryption --recipient age1votreclépublique --force

Chiffrement du réseau overlay (IPsec)

Opération fenêtre de maintenance (~1 à 2 min) : le réseau est recréé. La base native n'est pas affectée.

S'assurer d'abord que le fichier de pile déclare l'overlay chiffré, puis :

# station de contrôle
node infra/crypto/retrofit.mjs enable-overlay-ipsec --window --force

Méthode B — manuelle, hôte par hôte

Sans outillage : l'administrateur se connecte en SSH sur les hôtes concernés et exécute les commandes en root (sudo -i, ou sudo devant chaque commande). Chaque bloc de commandes ci-dessous indique sur quel hôte se connecter. Les « hôtes de base » sont les nœuds portant le rôle « db » ; les hôtes « manager » pilotent l'orchestrateur de conteneurs.

Disque au repos (LUKS)

Migration un nœud à la fois, sans interruption : chiffrer les répliques d'abord, basculer le primaire vers une réplique déjà chiffrée, chiffrer l'ancien primaire en dernier. Sur chaque nœud, le clair est mis de côté, le volume chiffré est monté vide, la base se re-synchronise dessus.

Étape 1 — préparer les serveurs de clés Tang (une fois, sur chaque hôte de base) :

# SSH sur CHAQUE hôte de base, en root
apt-get install -y tang clevis clevis-luks cryptsetup
mkdir -p /etc/systemd/system/tangd.socket.d
printf '[Socket]\nListenStream=\nListenStream=7500\n' > /etc/systemd/system/tangd.socket.d/override.conf
systemctl daemon-reload
systemctl enable --now tangd.socket

Étape 2 — repérer les rôles. Ne jamais commencer par le primaire.

# SSH sur n'importe quel hôte de base
patronictl -c /etc/patroni.yml list        # colonne "Role" : Leader = primaire, Replica = réplique

Étape 3 — chiffrer une réplique. Remplacer PAIR1/PAIR2 par les IP des deux autres hôtes de base.

# SSH sur la réplique à chiffrer, en root
systemctl stop patroni

# a) mettre le clair de côté et préparer un point de montage vide
mv /var/lib/postgresql/17/main /var/lib/postgresql/17/main.preluks
mkdir -p /var/lib/postgresql/17/main

# b) créer le conteneur LUKS2 et le lier aux serveurs Tang des pairs (Clevis SSS, seuil 1)
fallocate -l 4G /var/lib/vaks-pgdata.luks
PASS=$(openssl rand -base64 32)
printf '%s' "$PASS" | cryptsetup luksFormat --type luks2 --batch-mode /var/lib/vaks-pgdata.luks -
printf '%s' "$PASS" | clevis luks bind -d /var/lib/vaks-pgdata.luks -k - -y sss \
  '{"t":1,"pins":{"tang":[{"url":"http://PAIR1:7500"},{"url":"http://PAIR2:7500"}]}}'
printf '%s\n' "$PASS" > /root/vaks-pgdata.luks.recovery.key   # clé de SECOURS : exfiltrer hors cluster, puis supprimer d'ici
chmod 600 /root/vaks-pgdata.luks.recovery.key

# c) formater, monter, rendre la propriété à postgres
clevis luks unlock -d /var/lib/vaks-pgdata.luks -n pgdata_crypt
mkfs.ext4 -q /dev/mapper/pgdata_crypt
mount /dev/mapper/pgdata_crypt /var/lib/postgresql/17/main
rm -rf /var/lib/postgresql/17/main/lost+found
chown -R postgres:postgres /var/lib/postgresql/17/main
chmod 700 /var/lib/postgresql/17/main

Étape 4 — déverrouillage automatique au démarrage (même réplique). Sans cette unité, le volume resterait verrouillé après un redémarrage.

# SSH sur la même réplique, en root
cat > /usr/local/sbin/vaks-cryptmount.sh <<'EOF'
#!/bin/bash
set -e
F="$1"; N="$2"; M="$3"
for i in $(seq 1 30); do
  [ -e "/dev/mapper/$N" ] && break
  clevis luks unlock -d "$F" -n "$N" 2>/dev/null && break || true
  sleep 2
done
mountpoint -q "$M" || mount "/dev/mapper/$N" "$M"
EOF
chmod 755 /usr/local/sbin/vaks-cryptmount.sh

cat > /etc/systemd/system/vaks-cryptmount-pgdata.service <<'EOF'
[Unit]
Description=Unlock+mount pgdata (Clevis/Tang)
DefaultDependencies=no
Wants=network-online.target
After=network-online.target
Before=patroni.service
ConditionPathExists=/var/lib/vaks-pgdata.luks
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/sbin/vaks-cryptmount.sh /var/lib/vaks-pgdata.luks pgdata_crypt /var/lib/postgresql/17/main
[Install]
WantedBy=multi-user.target
EOF

mkdir -p /etc/systemd/system/patroni.service.d
printf '[Unit]\nRequires=vaks-cryptmount-pgdata.service\nAfter=vaks-cryptmount-pgdata.service\n' \
  > /etc/systemd/system/patroni.service.d/10-cryptmount.conf

systemctl daemon-reload
systemctl enable vaks-cryptmount-pgdata.service

# relancer Patroni : la réplique se re-clone (basebackup) sur le volume chiffré
systemctl start patroni
watch patronictl -c /etc/patroni.yml list   # attendre l'état "streaming", lag 0, puis Ctrl-C

Étape 5 — répéter les étapes 3-4 sur chaque réplique. Puis basculer le primaire et chiffrer l'ancien primaire en dernier :

# SSH sur n'importe quel hôte de base
patronictl -c /etc/patroni.yml switchover --candidate <réplique-déjà-chiffrée> --force

Étape 6 — vérifier puis nettoyer le clair une fois la validation faite.

# SSH sur chaque hôte de base
mount | grep pgdata_crypt                                  # volume chiffré monté
cryptsetup luksDump /var/lib/vaks-pgdata.luks | grep -i cipher   # aes-xts-plain64
patronictl -c /etc/patroni.yml list                        # 1 primaire + répliques à jour
rm -rf /var/lib/postgresql/17/main.preluks                 # SUPPRIMER le clair APRÈS validation
Au redémarrage d'un nœud, le volume se déverrouille via Clevis/Tang et Patroni rejoint le cluster. Pour le stockage objet, appliquer la même méthode à /var/lib/vaks-garage.luks monté sur /srv/vaks/garage — un nœud à la fois, en vérifiant le quorum entre chaque.

TLS interne PostgreSQL

Étape 1 — installer le certificat serveur, sur chaque hôte de base :

# SSH sur CHAQUE hôte de base, en root
install -D -o postgres -g postgres -m 644 /etc/vaks/server.crt /etc/vaks/pg/server.crt
install -D -o postgres -g postgres -m 600 /etc/vaks/server.key /etc/vaks/pg/server.key

Étape 2 — activer ssl=on (une fois) puis redémarrer les membres un par un. Remplacer vakspm-cluster par le scope: de /etc/patroni.yml.

# SSH sur n'importe quel hôte de base, en root
patronictl -c /etc/patroni.yml edit-config --force \
  -s postgresql.parameters.ssl=on \
  -s postgresql.parameters.ssl_cert_file=/etc/vaks/pg/server.crt \
  -s postgresql.parameters.ssl_key_file=/etc/vaks/pg/server.key

# redémarrage un par un — répliques d'abord, primaire en DERNIER
patronictl -c /etc/patroni.yml restart vakspm-cluster <réplique>  --force
patronictl -c /etc/patroni.yml restart vakspm-cluster <primaire>  --force

# vérifier (sur le primaire)
sudo -u postgres psql -p 5432 -tAc 'SHOW ssl'    # doit renvoyer : on

Sauvegardes chiffrées (age)

Additif et réversible. Prérequis : les sauvegardes hôte déjà installées (/etc/vaks-backup.env existe).

# SSH sur CHAQUE hôte de sauvegarde, en root
apt-get install -y age
age-keygen -o /root/vaks-backup-age.key       # affiche la clé PUBLIQUE age1… ; garde la PRIVÉE en séquestre hors cluster
echo 'BACKUP_AGE_RECIPIENT=age1votreclépublique' >> /etc/vaks-backup.env
Garde des clés. Sans la clé privée age séquestrée hors cluster, une sauvegarde chiffrée est irrécupérable.

Chiffrement du réseau overlay (IPsec)

Opération fenêtre de maintenance (~1 à 2 min).

Étape 1 — déclarer l'overlay chiffré dans le fichier de pile déployé (sur un hôte manager), sous le réseau vakspm :

# SSH sur un hôte manager — éditer docker-stack.gen.yml
#   networks:
#     vakspm:
#       driver: overlay
#       attachable: true
#       driver_opts:            <-- ajouter
#         encrypted: "true"     <-- ajouter

Étape 2 — recréer le réseau (drapeau non modifiable à chaud) :

# SSH sur un hôte manager, en root
docker stack rm vaks-pm                       # arrête les services (~1-2 min)
sleep 20                                       # laisser le réseau overlay disparaître
docker network ls | grep vaks-pm_vakspm || echo "réseau supprimé, prêt à redéployer"
docker stack deploy -c ~/vakspm-cluster/docker-stack.gen.yml vaks-pm

docker network inspect vaks-pm_vakspm -f '{{index .Options "encrypted"}}'   # true

TLS etcd (commun aux deux méthodes)

Chiffre le trafic du magasin de coordination du cluster. Cette couche est la plus délicate — elle se fait toujours par reconstruction manuelle, quelle que soit la méthode choisie pour les autres couches.

Ne jamais faire de retrofit etcd « à chaud ». Modifier les membres etcd un à un (etcdctl member update) sur un cluster vivant a déjà provoqué une panne (magasin de coordination hors service, base en lecture seule ~15 min). La seule méthode supportée est la reconstruction complète : la donnée etcd est jetable, Patroni la reconstruit.

Sous fenêtre de maintenance (la base passe brièvement en lecture seule). Sur chaque hôte etcd :

# SSH sur CHAQUE hôte etcd, en root — fenêtre de maintenance
systemctl stop patroni
systemctl stop etcd
mv /var/lib/etcd /var/lib/etcd.old && mkdir -p /var/lib/etcd

# re-amorcer etcd EN TLS d'emblée à partir du certificat interne /etc/vaks/server.*
ETCD_INTERNAL_TLS=1 bash ~/vakspm-cluster/bootstrap/10-etcd.sh

# une fois les 3 nœuds etcd repartis en https, relancer Patroni ; il recrée son état
systemctl start patroni
patronictl -c /etc/patroni.yml list          # cluster reformé, ssl re-capturé
Tant que les trois nœuds ne sont pas tous repartis en TLS, le cluster reste incomplet — c'est normal : enchaîner les nœuds, puis vérifier patronictl list à la fin.

Garde de la KEK — trousseau local, KMS ou HSM

Rappel du modèle à enveloppe : chaque secret est chiffré par une DEK (par organisation), et chaque DEK est enveloppée (wrap) par la clé maître, la KEK. Par défaut la KEK vit dans un trousseau de fichiers sur les hôtes. On peut déplacer sa garde vers un KMS ou un HSM on-premise, à chaud et sans re-chiffrer les données — seules les DEK sont re-enveloppées.

Tang ≠ HSM. Ne pas confondre : Tang (couche disque) déverrouille le volume LUKS au démarrage via le réseau ; un HSM/KMS garde la KEK des secrets applicatifs. Ce sont deux couches indépendantes — on peut avoir l'une, l'autre, ou les deux.

HSM on-premise (PKCS#11). L'application ne parle jamais PKCS#11 directement : un sidecar « key-broker » (image infra/hsm-broker/) isole la bibliothèque du fournisseur et le PIN, et n'expose que /wrap et /unwrap sur le réseau interne. La KEK ne quitte jamais le HSM (le wrap/unwrap s'exécute dedans).

  1. Déployer le sidecar broker sur l'overlay interne, avec la bibliothèque PKCS#11 du fournisseur montée en lecture seule et le PIN + un jeton partagé montés en secrets (voir infra/hsm-broker/README.md).
  2. Sur les services api, worker et controlplane, poser les variables puis faire un rolling update :
    # sur un hôte manager — env des services applicatifs
    CRYPTO_HSM_BROKER_URL=http://hsm-broker:8081
    CRYPTO_HSM_KEY_LABEL=<label de la KEK dans le HSM>
    CRYPTO_HSM_BROKER_TOKEN=<jeton partagé>
    CRYPTO_KEY_PROVIDER=hsm          # les NOUVELLES DEK sont désormais wrappées par le HSM
  3. Re-envelopper les DEK existantes sous la KEK du HSM (par organisation), depuis Admin → Sécurité et conformité → Clés de chiffrement → Rotation KEK, ou par l'API :
    POST /api/v1/admin/crypto/rewrap-keks
  4. Vérifier : GET /api/v1/admin/crypto/status renvoie chaque DEK en hsm:<label> ("activeKek":"hsm:…").
En ligne et réversible — aucune donnée n'est re-chiffrée, seules les DEK sont re-enveloppées. Rollback = CRYPTO_KEY_PROVIDER=local + rewrap-keks ; garder le trousseau local monté jusqu'à validation. ⚠️ Si le HSM et sa sauvegarde de clé sont perdus, les secrets sont irrécupérables par design — suivre la procédure de sauvegarde/escrow de la KEK du fournisseur.

Un KMS (AWS KMS ou endpoint compatible) suit exactement le même schéma, avec CRYPTO_KEY_PROVIDER=kms et CRYPTO_KMS_KEY_ID / _REGION / _ENDPOINT. Les blobs local, kms et hsm coexistent (routage par schéma de kekRef) : un état mixte pendant la migration est normal.

Rotation des clés de données (DEK)

Indépendant de l'infrastructure et identique aux deux méthodes. Vaks PM chiffre les secrets sensibles au repos (secrets multifacteurs, mot de passe SMTP, secrets d'authentification unique et de connecteurs) avec une clé de données (DEK) par organisation, elle-même enveloppée par la clé maître (KEK) — voir le modèle sous Sécurité. La rotation de la DEK re-chiffre l'ensemble de ces secrets sous une clé fraîche, sans interruption.


Voir aussi : Chiffrement à l'installation (Docker Swarm) · Sécurité, clés & DORA · Exploitation (jour 2) · Accueil de la documentation