LDAP, MongoDB, certificats¶
Trois cas qui ne rentrent pas dans les autres pages. L'annuaire LDAP, parce que sa restauration réserve un piège que presque personne ne découvre avant le jour J. MongoDB, parce qu'il se restaure sans décompression. Les certificats, parce qu'on n'y vérifie pas un volume mais une identité.
Voir comment adapter le bloc fetch et
comment trouver les seuils.
LDAP¶
La sauvegarde en entrée¶
Un export LDIF de l'annuaire, compressé :
directory_2026-09-02T03-00.ldif.gz. Peu importe qu'il vienne d'un slapcat,
d'un ldapsearch "*" "+" ou d'un db2ldif : ce qui compte, c'est ce qu'il
contient — et, on va y venir, ce qu'il ne contient pas.
Le plan¶
{
"steps": [
{
"type": "fetch",
"name": "Récupérer l'export de l'annuaire",
"config": {
"source": "local",
"path": "/backups/ldap/ldif",
"file_pattern": "directory_*.ldif.gz",
"selection_strategy": "latest_modified",
"max_age": "3h",
"output": "directory.ldif.gz"
}
},
{
"type": "unpack",
"name": "Décompresser",
"config": { "format": "gzip", "input": "directory.ldif.gz", "output": "directory.ldif" }
},
{
"type": "start_sandbox",
"name": "Démarrer OpenLDAP",
"config": {
"image": "bitnamilegacy/openldap:2.6.10-debian-12-r4",
"name": "demo-ldap-restore",
"env": {
"LDAP_ROOT": "dc=demo,dc=kolapsis,dc=com",
"LDAP_ADMIN_USERNAME": "admin",
"LDAP_ADMIN_PASSWORD": "demo-ldap",
"LDAP_SKIP_DEFAULT_TREE": "yes"
},
"healthcheck": {
"cmd": ["ldapsearch", "-x", "-H", "ldap://localhost:1389", "-b", "", "-s", "base", "namingContexts"],
"interval": "5s",
"timeout": "5s",
"retries": 12
}
}
},
{
"type": "restore_ldap",
"name": "Recharger l'annuaire",
"config": {
"host": "localhost",
"port": 1389,
"bind_dn": "cn=admin,dc=demo,dc=kolapsis,dc=com",
"password": "demo-ldap",
"base_dn": "dc=demo,dc=kolapsis,dc=com",
"input": "directory.ldif",
"format": "auto"
}
}
],
"probes": [
{
"name": "comptes_et_authentification",
"type": "ldap",
"timeout": 60,
"config": {
"RESTOREPROOF_LDAP_HOST": "demo-ldap-restore",
"RESTOREPROOF_LDAP_PORT": "1389",
"RESTOREPROOF_LDAP_BIND_DN": "cn=admin,dc=demo,dc=kolapsis,dc=com",
"RESTOREPROOF_LDAP_PASSWORD": "demo-ldap",
"RESTOREPROOF_LDAP_BASE_DN": "ou=people,dc=demo,dc=kolapsis,dc=com",
"RESTOREPROOF_LDAP_FILTER": "(objectClass=inetOrgPerson)",
"RESTOREPROOF_LDAP_SCOPE": "sub",
"RESTOREPROOF_LDAP_ASSERT": "eq",
"RESTOREPROOF_LDAP_EXPECTED_ENTRIES": "6",
"RESTOREPROOF_LDAP_EXPECT_DN": "cn=camille.durand,ou=people,dc=demo,dc=kolapsis,dc=com",
"RESTOREPROOF_LDAP_TEST_BIND_DN": "cn=camille.durand,ou=people,dc=demo,dc=kolapsis,dc=com",
"RESTOREPROOF_LDAP_TEST_BIND_PASSWORD": "demo-user-2026"
}
},
{
"name": "groupes",
"type": "ldap",
"timeout": 60,
"config": {
"RESTOREPROOF_LDAP_HOST": "demo-ldap-restore",
"RESTOREPROOF_LDAP_PORT": "1389",
"RESTOREPROOF_LDAP_BIND_DN": "cn=admin,dc=demo,dc=kolapsis,dc=com",
"RESTOREPROOF_LDAP_PASSWORD": "demo-ldap",
"RESTOREPROOF_LDAP_BASE_DN": "ou=groups,dc=demo,dc=kolapsis,dc=com",
"RESTOREPROOF_LDAP_FILTER": "(objectClass=groupOfNames)",
"RESTOREPROOF_LDAP_SCOPE": "sub",
"RESTOREPROOF_LDAP_ASSERT": "eq",
"RESTOREPROOF_LDAP_EXPECTED_ENTRIES": "2",
"RESTOREPROOF_LDAP_EXPECT_DN": "cn=support,ou=groups,dc=demo,dc=kolapsis,dc=com"
}
}
]
}
Ce que chaque sonde prouve¶
comptes_et_authentification— les six comptes sont revenus, celui decamille.durandexiste, et son mot de passe fonctionne. Le test de connexion (TEST_BIND_DN/TEST_BIND_PASSWORD) est la seule vérification qui prouve qu'après restauration les gens pourront encore entrer. Un LDIF qui recharge tout sauf les empreintes de mots de passe donne un annuaire d'apparence parfaite où personne ne se connecte.groupes— les deux groupes sont là, dontsupport. Les appartenances sont ce qui décide des droits : un annuaire restauré sans ses groupes est un annuaire où tout le monde a perdu ses accès.
RESTOREPROOF_LDAP_EXPECT_DN exige une entrée précise, par son DN complet.
C'est plus fort qu'un décompte : six entrées, ce n'est pas forcément les
six bonnes.
Pourquoi LDAP_SKIP_DEFAULT_TREE¶
Sans cette variable, l'image OpenLDAP crée au premier démarrage son propre
arbre de démonstration sous LDAP_ROOT. Ces entrées entrent alors en collision
avec celles que le LDIF s'apprête à charger sous le même suffixe (« entry
already exists »), et une sauvegarde parfaitement restaurable échoue pour une
raison qui n'a rien à voir avec elle.
Le piège du schéma personnalisé¶
Voici la découverte pour laquelle ce produit existe.
Un annuaire qui utilise un schéma personnalisé — n'importe quel attribut ajouté
au fil des années, par une application métier ou par un greffon — ne se
recharge pas sur un serveur neuf. ldapadd s'arrête sur la première entrée
qui porte un attribut inconnu :
Et il s'arrête à chaque fois sur un seul attribut, sans rien dire des suivants. Un export qui en utilise trois demande donc trois exécutions complètes pour être diagnostiqué — récupération, décompression, démarrage du bac à sable à chaque fois — et vous n'apprenez le manque suivant qu'après avoir corrigé le précédent.
RestoreProof ne vous fait pas vivre ça. Avant d'écrire la première entrée, il demande au serveur du bac à sable ce qu'il sait accepter, le compare à ce que votre export utilise, et vous rend la liste complète en une fois : tous les types d'attributs manquants, toutes les classes d'objets manquantes.
Ce n'est pas un adoucissement. C'est un vrai constat d'échec : un export qui ne se recharge pas sur un serveur neuf n'est pas restaurable, et c'est précisément ce qu'il fallait découvrir aujourd'hui plutôt que pendant une panne.
La correction, en deux temps :
- Mettez le schéma dans la sauvegarde. Exportez-le à côté des données,
dans un
schema.ldifséparé. Un export de données ne le contient jamais : le schéma vit ailleurs, dans la configuration du serveur. - Rejouez-le avant les données, avec une étape
exec_in_sandboxinsérée entrestart_sandboxetrestore_ldap. Le schéma se charge dans l'arbre de configuration,cn=config, pas dans l'arbre de données :
{
"type": "exec_in_sandbox",
"name": "Charger le schéma personnalisé",
"config": {
"cmd": "ldapadd -x -H ldap://localhost:1389 -D cn=admin,cn=config -w demo-ldap -f /workspace/schema.ldif",
"name": "demo-ldap-restore"
}
}
Sur l'image OpenLDAP, ce compte de configuration doit être activé dans le bac à
sable — ajoutez à l'env de l'étape start_sandbox :
L'ordre compte : le schéma d'abord, les données ensuite. C'est aussi l'ordre dans lequel il faudra les rejouer le jour où vous restaurerez pour de vrai.
Ce que ce plan ne prouve pas¶
Le rechargement se fait en ligne, avec ldapadd. Les attributs opérationnels
— entryUUID, entryCSN, dates de création — sont retirés avant l'envoi, et
refabriqués par le serveur du bac à sable : ils appartiennent au serveur, pas
à vos données.
Le plan prouve donc que les entrées, leurs attributs et leurs mots de passe reviennent utilisables. Il ne prouve pas que la base interne du serveur est identique octet pour octet, et c'est très bien : ce qui vous intéresse, c'est que vos utilisateurs se reconnectent.
Les autres annuaires¶
L'interface propose trois modèles, selon votre serveur :
- OpenLDAP — celui ci-dessus ;
- 389 Directory Server / FreeIPA — même principe, mais l'image ne crée pas
d'emplacement pour votre suffixe au démarrage : le modèle insère une étape
exec_in_sandboxqui lancedsconf ... backend create, sans option de création d'entrées, pour que le suffixe existe et reste vide ; - lldap — cas à part : lldap garde tout son annuaire dans un unique fichier
SQLite (
users.db), pas dans un export LDIF. Le plan restaure ce fichier directement et démarre lldap dessus, sans étape de rechargement.
MongoDB¶
La sauvegarde en entrée¶
Une archive mongodump --archive --gzip : demo_2026-09-02T03-00.archive.gz.
Pas d'étape unpack : mongorestore lit l'archive compressée telle quelle. Le
format: "auto" de l'étape reconnaît aussi bien une archive qu'un répertoire
de fichiers BSON.
Le plan¶
{
"steps": [
{
"type": "fetch",
"name": "Récupérer l'archive MongoDB",
"config": {
"source": "local",
"path": "/backups/mongo/mongo",
"file_pattern": "demo_*.archive.gz",
"selection_strategy": "latest_modified",
"max_age": "3h",
"output": "demo.archive.gz"
}
},
{
"type": "start_sandbox",
"name": "Démarrer MongoDB",
"config": {
"image": "mongo:7",
"name": "demo-mongo-restore",
"healthcheck": {
"cmd": ["mongosh", "--quiet", "--eval", "db.adminCommand('ping')"],
"interval": "5s",
"timeout": "5s",
"retries": 12
}
}
},
{
"type": "restore_mongo",
"name": "Restaurer la base",
"config": { "database": "demo", "input": "demo.archive.gz", "format": "auto" }
}
],
"probes": [
{
"name": "produits",
"type": "mongodb",
"timeout": 60,
"config": {
"RESTOREPROOF_MONGO_HOST": "demo-mongo-restore",
"RESTOREPROOF_MONGO_DATABASE": "demo",
"RESTOREPROOF_MONGO_COLLECTION": "products",
"RESTOREPROOF_MONGO_ASSERT": "eq",
"RESTOREPROOF_MONGO_EXPECTED_ROWS": "50"
}
},
{
"name": "commandes_payees",
"type": "mongodb",
"timeout": 60,
"config": {
"RESTOREPROOF_MONGO_HOST": "demo-mongo-restore",
"RESTOREPROOF_MONGO_DATABASE": "demo",
"RESTOREPROOF_MONGO_COLLECTION": "orders",
"RESTOREPROOF_MONGO_FILTER": "{\"status\": \"payée\"}",
"RESTOREPROOF_MONGO_ASSERT": "eq",
"RESTOREPROOF_MONGO_EXPECTED_ROWS": "450"
}
},
{
"name": "amorcage_complet",
"type": "mongodb",
"timeout": 60,
"config": {
"RESTOREPROOF_MONGO_HOST": "demo-mongo-restore",
"RESTOREPROOF_MONGO_DATABASE": "demo",
"RESTOREPROOF_MONGO_COLLECTION": "seed_complete",
"RESTOREPROOF_MONGO_ASSERT": "eq",
"RESTOREPROOF_MONGO_EXPECTED_ROWS": "1"
}
}
]
}
Ce que chaque sonde prouve¶
produits— le catalogue est revenu au complet.commandes_payees—RESTOREPROOF_MONGO_FILTERprend un filtre Mongo en JSON. Ce n'est plus « il y a des commandes », c'est « il y a les 450 commandes payées ». Une restauration qui ramène les documents mais perd un champ ne passe pas ce contrôle.amorcage_complet— un marqueur écrit par le script d'amorçage à la toute fin. Il prouve que la sauvegarde a été prise sur une base complète, pas pendant son remplissage. Le même motif marche chez vous : un document témoin écrit à la fin de votre traitement de nuit.
Si votre serveur demande une authentification, ajoutez
RESTOREPROOF_MONGO_USER, RESTOREPROOF_MONGO_PASSWORD et
RESTOREPROOF_MONGO_AUTH_DATABASE (admin par défaut). Le port se règle avec
RESTOREPROOF_MONGO_PORT.
Certificats TLS¶
Une sauvegarde qui ramène un certificat expiré est une sauvegarde qui ne servira pas de trafic. Ici, on ne compte rien : on vérifie une identité et une date.
La sauvegarde en entrée¶
Une archive des fichiers PEM publics — chaîne, certificat serveur, certificat d'autorité. Pas les clés privées : le plan n'en a pas besoin, et ce qui n'entre pas dans le bac à sable ne peut pas en sortir.
Le plan¶
{
"steps": [
{
"type": "fetch",
"name": "Récupérer l'archive des certificats",
"config": {
"source": "local",
"path": "/backups/pki/files",
"file_pattern": "certs_*.tar.gz",
"selection_strategy": "latest_modified",
"max_age": "3h",
"output": "certs.tar.gz"
}
},
{
"type": "unpack",
"name": "Extraire l'archive",
"config": {
"format": "tar.gz",
"input": "certs.tar.gz",
"output": "."
}
}
],
"probes": [
{
"name": "arborescence_non_vide",
"type": "filesystem-canary",
"timeout": 60,
"config": {
"RESTOREPROOF_CANARY_MIN_FILES": "3",
"RESTOREPROOF_CANARY_MIN_TOTAL_SIZE": "3000"
}
},
{
"name": "certificat_serveur",
"type": "certificate",
"timeout": 60,
"config": {
"RESTOREPROOF_CERT_PATH": "public/fullchain.pem",
"RESTOREPROOF_CERT_MIN_DAYS": "10",
"RESTOREPROOF_CERT_EXPECT_CN": "interne.demo.kolapsis.com",
"RESTOREPROOF_CERT_EXPECT_SAN": "interne.demo.kolapsis.com,pki.demo.kolapsis.com",
"RESTOREPROOF_CERT_EXPECT_ISSUER": "Demo RestoreProof CA"
}
}
]
}
Ce que chaque sonde prouve¶
arborescence_non_vide— les trois PEM sont là, et pèsent quelque chose.certificat_serveur— le certificat restauré est le bon et il est encore valide :RESTOREPROOF_CERT_MIN_DAYS— il lui reste au moins dix jours. Un certificat qui expire dans trois jours se restaure parfaitement et ne servira rien la semaine prochaine ;RESTOREPROOF_CERT_EXPECT_CN— il porte le bon nom ;RESTOREPROOF_CERT_EXPECT_SAN— il couvre bien tous les noms attendus, séparés par des virgules ;RESTOREPROOF_CERT_EXPECT_ISSUER— il vient bien de votre autorité, et pas d'une autre. C'est ce qui distingue le vrai certificat d'un auto-signé fabriqué par un script de secours.
Deux variables complètent la sonde : RESTOREPROOF_CERT_FINGERPRINT, qui exige
une empreinte SHA-256 exacte, et RESTOREPROOF_CERT_REQUIRE_KEY, qui exige
qu'un bloc de clé privée accompagne le certificat.
Sur ce dernier point, RestoreProof reste prudent : la clé privée qui se trouve éventuellement à côté du certificat n'est jamais analysée, jamais hachée, jamais nommée au-delà de « un bloc de clé privée est présent ».
Ici, pas de calibration¶
Il n'y a rien à mesurer sur un certificat. Un nom, une autorité, une empreinte ne sont pas des seuils : ce sont des valeurs que vous connaissez déjà, et RestoreProof ne vous proposera jamais de les déduire d'un essai. Quant aux jours restants, ils diminuent par nature — les figer comme plancher n'aurait aucun sens.