Aller au contenu

Sondes

Une sonde est un conteneur jetable. Le runner le démarre après la restauration, lui monte l'espace de travail en lecture seule sur /workspace, lui passe sa configuration en variables d'environnement, puis lit le fichier /out/result.json qu'il a écrit. Le conteneur est ensuite détruit.

Il existe dix sondes.

Sonde Ce qu'elle prouve
archive-integrity L'archive sauvegardée se décompresse sans erreur.
filesystem-canary Les fichiers attendus sont là, avec le bon contenu et la bonne taille.
certificate Le certificat TLS restauré est valide et c'est le bon.
postgres La base PostgreSQL restaurée répond et contient les données.
mysql Idem pour MySQL / MariaDB.
mongodb Idem pour MongoDB.
sqlite Le fichier SQLite s'ouvre, n'est pas corrompu et contient les données.
pocketbase L'instance PocketBase redémarre sur les données restaurées.
ldap L'annuaire restauré répond, contient les entrées, et un compte peut s'y authentifier.
http Le service remonté sur les données restaurées répond ce qu'on attend.

Ce qui est commun à toutes

L'image

Par défaut ghcr.io/kolapsis/restoreproof/probe-<type>:latest, où <type> est le nom de la sonde dans le tableau ci-dessus.

Les variables injectées par le runner

Vous n'avez pas à les fournir : le runner les pose sur chaque conteneur de sonde.

Variable Valeur
RESTOREPROOF_WORKSPACE /workspace — l'espace de travail, en lecture seule.
RESTOREPROOF_OUTPUT /out — où la sonde écrit result.json.
RESTOREPROOF_TIMEOUT_SECONDS Le délai configuré pour cette sonde dans le plan.

Le conteneur est limité à 512 Mo de mémoire et 1 CPU, et tué au bout du délai de la sonde (5 minutes par défaut).

Le résultat

{
  "status": "PASS",
  "message": "...",
  "metrics": { },
  "artifacts": []
}

FAIL fait sortir le conteneur en code 1. PASS et WARN sortent en 0.

Le contrat d'assertion

Cinq sondes partagent le même vocabulaire de comparaison, dans une variable ..._ASSERT :

Opérateur Vérifie
gte observé attendu
lte observé attendu
eq observé = attendu
zero observé = 0 (l'attendu vaut 0 s'il n'est pas fourni)

Deux règles de commodité, identiques pour postgres, mysql, mongodb, sqlite et pocketbase :

  • un seuil fourni sans opérateur vaut gte ;
  • zero sans seuil vaut « zéro ».

zero existe séparément de eq 0 parce que « ceci ne doit pas exister » (lignes orphelines, clés nulles) est la vérification qu'on écrit de travers.

Sans assertion, le comportement n'est pas le même partout

Ce n'est pas uniforme, et c'est délibéré.

Sonde Sans aucune assertion configurée
postgres, mysql, mongodb, sqlite, pocketbase PASS, en disant dans le message qu'aucune assertion n'était configurée.
ldap FAIL si aucun des trois contrôles n'est demandé. Avec un base DN mais pas d'opérateur : PASS, en indiquant le nombre observé.
filesystem-canary FAIL. Le message vous rend les chiffres de l'arborescence pour que vous en fassiez des seuils.
archive-integrity FAIL s'il n'a validé aucune archive.
certificate FAIL si aucun chemin n'est donné.
http Vérifie toujours le code de statut (200 par défaut).

Un PASS qui n'a rien vérifié est pire qu'un FAIL : c'est une preuve qui ment. Là où la sonde peut le dire, elle échoue plutôt que de passer en silence.


archive-integrity

Décompresse une archive de bout en bout pour prouver qu'elle est lisible.

Elle ne sert que dans un plan qui ne décompresse pas

Si votre plan contient une étape unpack, l'archive a déjà disparu de l'espace de travail quand la sonde s'exécute : elle ne trouvera rien à valider et échouera. Utilisez-la pour un plan « je télécharge et je vérifie que ça s'ouvre », pas pour un plan qui restaure une base.

Variable Défaut Rôle
RESTOREPROOF_ARCHIVE_PATH (vide) Le ou les fichiers à valider, séparés par des virgules. Chemins relatifs à /workspace, les jokers (*, ?) sont acceptés.

Si la variable est vide, la sonde parcourt tout l'espace de travail et valide chaque fichier dont l'extension est .tar, .gz, .tgz ou .zip. Si elle n'en trouve aucun, elle échoue : compter des fichiers ne prouve pas qu'une sauvegarde est lisible.

Si la variable est remplie, chaque nom doit correspondre à au moins un fichier. Un fichier absent, ou dont l'extension n'est pas reconnue, fait échouer la sonde.

Ce qu'elle vérifie, précisément

Extension Ce qui est réellement parcouru
.tar Chaque en-tête d'entrée du tar, jusqu'à la fin.
.zip Chaque entrée est ouverte et décompressée intégralement.
.gz, .tgz La couche gzip seulement, décompressée entièrement.

Ce qu'un .tar.gz valide, et ce qu'il ne valide pas

Un .tar.gz est traité par son extension .gz : la sonde décompresse tout le flux. Le CRC de gzip couvre l'intégralité du flux, donc une troncature ou une corruption sont attrapées — c'est bien une vérification de bout en bout, pas une lecture de l'en-tête.

Mais elle ne parcourt pas le tar qui est à l'intérieur. Un tar structurellement cassé mais correctement compressé passerait. Pour vérifier aussi le contenu, décompressez dans le plan et utilisez filesystem-canary.

Mesures : archive_count, errors_count, et — en mode « tout l'espace de travail » — total_files, total_size.


filesystem-canary

Vérifie que des fichiers précis sont là, et qu'ils contiennent bien quelque chose. Un fichier vide au bon nom se restaure aussi bien que le vrai : seule la vérification du contenu distingue les deux.

Variable Défaut Rôle
RESTOREPROOF_CANARY_PATH (vide) Un ou plusieurs chemins, séparés par des virgules, qui doivent exister après la restauration.
RESTOREPROOF_CANARY_FILES (vide) La même chose, plus ce que chaque fichier doit contenir, peser ou valoir. Voir ci-dessous.
RESTOREPROOF_CANARY_PATTERN (vide) Un motif de nom de fichier (*.sql) qu'au moins un fichier restauré doit satisfaire.
RESTOREPROOF_CANARY_MIN_FILES (vide) Nombre minimal de fichiers dans toute l'arborescence.
RESTOREPROOF_CANARY_MIN_TOTAL_SIZE (vide) Poids cumulé minimal, en octets.

Les cinq contrôles s'additionnent, ils ne se remplacent pas : nommer un fichier ne dit rien de la taille de l'arborescence autour de lui.

RESTOREPROOF_CANARY_FILES

Deux écritures sont acceptées.

Liste simple — les fichiers doivent exister, rien de plus :

data/app.db,etc/config.yaml

JSON — la seule forme qui atteint les vérifications de contenu :

[
  {"path": "config.json", "contains": "db_host", "min_size": 100},
  {"path": "data/app.db", "expected_hash": "9f86d081...", "must_be_file": true},
  {"path": "var/log/app.log", "matches": "^\\[INFO\\] démarrage"}
]
Champ Type Vérifie
path texte Le chemin, relatif à /workspace. Obligatoire.
expected_hash texte Le SHA-256 du fichier. 64 caractères hexadécimaux, sinon la sonde échoue en le disant.
contains texte Le fichier contient cette chaîne, littéralement.
matches texte Le contenu satisfait cette expression régulière (syntaxe Go).
min_size nombre Le fichier pèse au moins tant d'octets.
must_be_file booléen Le chemin est un fichier, pas un répertoire.

Un chemin qui existe et n'est décrit par aucun de ces champs peut être un répertoire. Dès qu'un champ de contenu, de taille ou de hachage est présent, un répertoire est un échec.

Les vérifications contains et matches lisent au plus 8 Mio du fichier. Si la limite est atteinte sans trouver, le message le dit plutôt que d'affirmer que le texte est absent.

Sans assertion, la sonde échoue

C'est voulu, et c'est l'étape de réglage normale : lancez le plan une première fois, lisez les chiffres que le message vous rend, et servez-vous-en comme seuils.

This probe was asked to prove nothing, so it proves nothing. Walking the restored tree found 1 842 file(s) in 96 directories, 412 998 231 bytes in total, but no check was configured…

Ces trois nombres sont exactement ce qu'il faut mettre dans RESTOREPROOF_CANARY_MIN_FILES et RESTOREPROOF_CANARY_MIN_TOTAL_SIZE.

Verdict

  • FAIL dès qu'un contrôle échoue, ou qu'un seuil n'est pas un entier positif. Un seuil ignoré en silence vous ferait croire à un plancher qui n'existe pas.
  • WARN si RESTOREPROOF_CANARY_PATTERN ne correspond à rien, ou si un chemin de l'arborescence n'a pas pu être lu.
  • PASS sinon.

Mesures : files, directories, total_size, errors, et selon les contrôles canary_files, verified, pattern, matches_found, min_files, min_total_size.


certificate

Lit un certificat TLS restauré et répond à deux questions : est-il encore utilisable, et est-ce bien le bon ?

Variable Défaut Rôle
RESTOREPROOF_CERT_PATH (obligatoire) Le ou les fichiers PEM, séparés par des virgules. Jokers acceptés. Un joker qui ne correspond à rien est un échec.
RESTOREPROOF_CERT_MIN_DAYS (vide) Nombre de jours de validité restants exigé. Les jours sont arrondis vers le bas : 29,9 jours ne satisfont pas 30.
RESTOREPROOF_CERT_EXPECT_CN (vide) Le common name attendu, comparé sans tenir compte de la casse.
RESTOREPROOF_CERT_EXPECT_SAN (vide) Un ou plusieurs SAN, séparés par des virgules, qui doivent tous être présents.
RESTOREPROOF_CERT_EXPECT_ISSUER (vide) Fragment que le nom de l'émetteur doit contenir.
RESTOREPROOF_CERT_FINGERPRINT (vide) L'empreinte SHA-256 du DER. 64 caractères hexadécimaux ; les :, espaces et - sont tolérés, la casse aussi.
RESTOREPROOF_CERT_REQUIRE_KEY false Exige qu'un bloc de clé privée soit présent dans le fichier. true, 1, yes ou on.

Pourquoi elle n'a rien de calibrable

Les autres sondes comparent un nombre observé à un seuil que vous ajustez après un premier essai. Ici il n'y a rien à ajuster : une date d'expiration, un common name, une empreinte sont des valeurs absolues, connues d'avance. Un certificat est valide ou expiré ; c'est le vôtre ou ce n'est pas le vôtre. RESTOREPROOF_CERT_MIN_DAYS est le seul chiffre, et il vient de votre politique de renouvellement, pas d'une mesure.

Ce qu'elle fait de la clé privée

Si le fichier contient un bloc de clé privée, la sonde le reconnaît par son type de bloc PEM et n'y touche pas. Ses octets ne sont ni lus, ni hachés, ni nommés. Le rapport dit seulement has_private_key: true.

Verdict : FAIL si le fichier est illisible, ne contient aucun bloc PEM, aucun bloc CERTIFICATE, si le certificat n'est pas analysable, pas encore valide, expiré, ou si l'une des attentes ci-dessus n'est pas satisfaite. PASS sinon.

Mesures (celles du certificat qui expire le plus tôt) : certificates, subject, issuer, not_before, not_after, days_remaining, sans, fingerprint_sha256, is_ca, chain_length. Avec plusieurs fichiers, le détail par fichier est ajouté sous files.


postgres

Se connecte à la base restaurée dans le bac à sable, relève sa forme, et compare un nombre à un seuil.

Variable Alias prioritaire Défaut
RESTOREPROOF_PG_HOST PGHOST localhost
RESTOREPROOF_PG_PORT PGPORT 5432
RESTOREPROOF_PG_USER PGUSER postgres
RESTOREPROOF_PG_PASSWORD PGPASSWORD (vide)
RESTOREPROOF_PG_DATABASE PGDATABASE postgres
RESTOREPROOF_PG_SSLMODE PGSSLMODE disable
RESTOREPROOF_PG_TABLE (vide)
RESTOREPROOF_PG_QUERY (vide)
RESTOREPROOF_PG_ASSERT (vide)
RESTOREPROOF_PG_EXPECTED_ROWS (vide)

L'alias est lu en premier : si PGHOST est défini, RESTOREPROOF_PG_HOST est ignoré. C'est ce que l'éditeur de plan utilise.

Ce qui est compté. Une requête l'emporte sur une table : qui a écrit du SQL le pensait.

  • RESTOREPROOF_PG_QUERY — une requête qui rend une seule valeur. Si elle échoue, la sonde échoue. Si elle rend autre chose qu'un nombre et qu'une assertion est configurée, la sonde échoue en le disant.
  • RESTOREPROOF_PG_TABLE — la sonde fait un SELECT COUNT(*) dessus. La valeur sert aussi de fragment SQL : users WHERE akey <> '' est un compte conditionnel valide. Un identifiant simple est mis entre guillemets pour que les mots réservés fonctionnent.

Verdict : FAIL si la connexion échoue, si la requête échoue, si le seuil n'est pas un entier, si une assertion est configurée mais que rien ne fournit de valeur, ou si la comparaison est fausse. WARN si la connexion réussit mais que SELECT version() échoue. PASS sinon.

Mesures : connect_time_ms, host, port, database, version, database_size_bytes, table_count, assertion, et selon le cas actual_rows, custom_query_result, assert_source, expected_rows.


mysql

Même structure que postgres, pour MySQL et MariaDB.

Variable Alias prioritaire Défaut
RESTOREPROOF_MYSQL_HOST MYSQL_HOST localhost
RESTOREPROOF_MYSQL_PORT MYSQL_PORT 3306
RESTOREPROOF_MYSQL_USER MYSQL_USER root
RESTOREPROOF_MYSQL_PASSWORD MYSQL_PASSWORD (vide)
RESTOREPROOF_MYSQL_DATABASE MYSQL_DATABASE mysql
RESTOREPROOF_MYSQL_CHARSET MYSQL_CHARSET utf8mb4
RESTOREPROOF_MYSQL_TLS MYSQL_TLS false
RESTOREPROOF_MYSQL_TABLE (vide)
RESTOREPROOF_MYSQL_QUERY (vide)
RESTOREPROOF_MYSQL_ASSERT (vide)
RESTOREPROOF_MYSQL_EXPECTED_ROWS (vide)

RESTOREPROOF_MYSQL_TABLE accepte, comme sous PostgreSQL, un fragment SQL : users WHERE deleted_at IS NULL. Un identifiant simple est entouré de backquotes.

Mesures : connect_time_ms, host, port, database, version, database_size_bytes, table_count, assertion, et selon le cas actual_rows, custom_query_result, assert_source, expected_rows.


mongodb

Variable Alias prioritaire Défaut
RESTOREPROOF_MONGO_HOST MONGO_HOST localhost
RESTOREPROOF_MONGO_PORT MONGO_PORT 27017
RESTOREPROOF_MONGO_USER MONGO_USER (vide — connexion anonyme)
RESTOREPROOF_MONGO_PASSWORD MONGO_PASSWORD (vide)
RESTOREPROOF_MONGO_DATABASE MONGO_DATABASE obligatoire
RESTOREPROOF_MONGO_AUTH_DATABASE MONGO_AUTH_DATABASE admin
RESTOREPROOF_MONGO_COLLECTION (vide)
RESTOREPROOF_MONGO_FILTER (vide)
RESTOREPROOF_MONGO_ASSERT (vide)
RESTOREPROOF_MONGO_EXPECTED_ROWS (vide)

La base est obligatoire : un comptage de documents doit nommer la base dans laquelle il compte.

RESTOREPROOF_MONGO_FILTER est un filtre en JSON étendu MongoDB ({"status":"active"}). Un filtre invalide fait échouer la sonde.

Il n'y a pas de mode « requête libre » : le nombre observé est toujours le compte de documents d'une collection, éventuellement filtré.

Verdict : FAIL si la base n'est pas nommée, si la connexion échoue, si le filtre est invalide, si le comptage échoue, si une assertion est configurée sans collection, ou si la comparaison est fausse. WARN si la connexion réussit mais que la liste des collections échoue. PASS sinon.

Mesures : connect_time_ms, host, port, database, server_version, collection_count, assertion, et selon le cas actual_rows, assert_source, expected_rows.


sqlite

Lit un fichier de base SQLite dans l'espace de travail. Contrairement aux précédentes, elle ne parle à aucun bac à sable.

Variable Défaut Rôle
RESTOREPROOF_SQLITE_PATH (obligatoire) Le fichier, relatif à /workspace. Un joker est accepté (daily/*.db) ; s'il correspond à plusieurs fichiers, le premier par ordre alphabétique est retenu.
RESTOREPROOF_SQLITE_INTEGRITY quick quick (PRAGMA quick_check), full (PRAGMA integrity_check), ou off / none. Toute autre valeur fait échouer la sonde.
RESTOREPROOF_SQLITE_TARGET (vide) Nom d'une table ou d'une vue à compter. C'est un nom, jamais un fragment SQL.
RESTOREPROOF_SQLITE_QUERY (vide) Une requête rendant une seule valeur. L'emporte sur TARGET.
RESTOREPROOF_SQLITE_ASSERT (vide) gte, lte, eq, zero.
RESTOREPROOF_SQLITE_EXPECTED_ROWS (vide) Le seuil.

Deux détails qui comptent

L'en-tête est vérifié avant tout le reste. Un téléchargement tronqué, un dump gzippé ou une page d'erreur HTML enregistrée en .db produisent un message qui nomme le vrai problème, au lieu du file is not a database du pilote.

La base est copiée à côté avant d'être ouverte. L'espace de travail est monté en lecture seule, et SQLite a besoin d'écrire : il rejoue et intègre le -wal à l'ouverture. La sonde copie le fichier et ses compagnons -wal et -shm dans un répertoire temporaire. C'est ce qui fait que les dernières transactions d'une base copiée à chaud font partie de la preuve.

Si TARGET nomme une table qui n'existe pas, le message liste les tables et vues réellement présentes.

Mesures : database, database_size_bytes, integrity, tables, page_count, page_size, schema_version, journal_mode, assertion, et selon le cas rows, custom_query_result, assert_source, expected_rows, integrity_report.


pocketbase

Interroge une instance PocketBase en fonctionnement, remontée sur le pb_data restauré. Elle ne lit aucun fichier : il faut donc une étape start_sandbox avant elle.

Variable Défaut Rôle
RESTOREPROOF_PB_URL (obligatoire) L'URL de l'instance, par exemple http://restore-pocketbase:8090.
RESTOREPROOF_PB_TOKEN (vide) Un jeton déjà obtenu. L'emporte sur le couple e-mail / mot de passe.
RESTOREPROOF_PB_ADMIN_EMAIL (vide) Identifiant du super-utilisateur.
RESTOREPROOF_PB_ADMIN_PASSWORD (vide) Son mot de passe.
RESTOREPROOF_PB_COLLECTION (vide) La collection dont on compte les enregistrements.
RESTOREPROOF_PB_FILTER (vide) Un filtre PocketBase, par exemple verified = true.
RESTOREPROOF_PB_ASSERT (vide) gte, lte, eq, zero.
RESTOREPROOF_PB_EXPECTED_ROWS (vide) Le seuil.
RESTOREPROOF_PB_TIMEOUT_SECONDS 30 Délai des appels HTTP. À défaut, RESTOREPROOF_TIMEOUT_SECONDS est utilisé.

Le déroulé. /api/health d'abord : une instance qui ne répond pas là n'a pas démarré sur les données restaurées, ce qui est précisément ce qu'on prouve. Puis l'authentification, si des identifiants sont fournis — /api/collections/_superusers/auth-with-password, avec repli sur /api/admins/auth-with-password pour les versions antérieures à PocketBase 0.23, qui sont exactement celles qu'on retrouve dans une vieille sauvegarde. Puis le comptage.

Les enregistrements ne sont jamais lus. La sonde demande une page d'un seul élément et ne retient que totalItems : le contenu est de la donnée client, il ne doit ni être tiré ni figurer au rapport. Un mot de passe glissé dans l'URL est remplacé par redacted avant que celle-ci n'entre dans le message.

Mesures : health, auth, assertion, pb_version, et selon le cas records, collections, expected_rows.


ldap

Trois contrôles indépendants, dont au moins un doit être demandé. Sinon la sonde échoue avant d'envoyer le moindre paquet.

Variable Défaut Rôle
RESTOREPROOF_LDAP_HOST localhost Alias de repli : LDAP_HOST.
RESTOREPROOF_LDAP_PORT 389 Alias de repli : LDAP_PORT.
RESTOREPROOF_LDAP_TLS none none, starttls ou ldaps. Toute autre valeur est un échec. Alias : LDAP_TLS.
RESTOREPROOF_LDAP_INSECURE false Ne pas vérifier le certificat du serveur. Alias : LDAP_INSECURE.
RESTOREPROOF_LDAP_BIND_DN (vide — connexion anonyme) Le DN de service. Alias : LDAP_BIND_DN.
RESTOREPROOF_LDAP_PASSWORD (vide) Son mot de passe. Alias : LDAP_PASSWORD.
RESTOREPROOF_LDAP_TIMEOUT 30 En secondes.

Contrairement aux sondes SQL, c'est ici la variable RESTOREPROOF_* qui est lue en premier, l'alias court ne servant que de repli.

Contrôle 1 — compter les entrées

Variable Défaut
RESTOREPROOF_LDAP_BASE_DN (vide)
RESTOREPROOF_LDAP_FILTER (objectClass=*)
RESTOREPROOF_LDAP_SCOPE sub — ou one, ou base
RESTOREPROOF_LDAP_ASSERT (vide)
RESTOREPROOF_LDAP_EXPECTED_ENTRIES (vide)

La recherche est paginée (500 entrées par page), donc un annuaire plus gros que la limite du serveur — slapd s'arrête à 500 par défaut — est compté en entier au lieu d'être tronqué en silence.

Si le serveur tronque quand même la réponse, la sonde échoue en disant que c'est une limite du serveur, sans rapport avec la restauration. Elle n'affirme jamais que la sauvegarde est incomplète alors qu'elle ne le sait pas.

Avec un base DN mais sans RESTOREPROOF_LDAP_ASSERT, la sonde rapporte le nombre observé et passe. Avec un opérateur mais sans seuil, le seuil vaut 1 (ou 0 pour zero).

Contrôle 2 — un DN doit exister

RESTOREPROOF_LDAP_EXPECT_DN — par exemple uid=jdoe,ou=people,dc=example,dc=org. Absent, c'est un échec.

Contrôle 3 — un compte doit pouvoir s'authentifier

RESTOREPROOF_LDAP_TEST_BIND_DN et RESTOREPROOF_LDAP_TEST_BIND_PASSWORD.

C'est le contrôle qui prouve qu'un userPassword restauré fonctionne encore. Il ouvre une seconde connexion pour ce test, plutôt que de réutiliser celle déjà authentifiée en tant qu'administrateur.

Mesures : connect_time_ms, server_host, expect_dn, bind_test, et selon le cas base_dn, filter, scope, paged, entries, assertion, expected_entries, naming_contexts.

naming_contexts est purement informatif — les suffixes que le serveur sert réellement. C'est la première chose à regarder quand un base DN ne rend aucune entrée.


http

Interroge un service remonté sur les données restaurées.

Variable Défaut Rôle
RESTOREPROOF_HTTP_URL (obligatoire) L'URL à appeler.
RESTOREPROOF_HTTP_METHOD GET Passe automatiquement à POST si un corps est fourni et que la méthode n'est pas définie.
RESTOREPROOF_HTTP_EXPECTED_STATUS 200 Le code attendu. Une valeur non numérique fait échouer la sonde en le disant.
RESTOREPROOF_HTTP_EXPECTED_BODY (vide) Chaîne que la réponse doit contenir.
RESTOREPROOF_HTTP_TIMEOUT 30 En secondes.
RESTOREPROOF_HTTP_INSECURE false true pour ne pas vérifier le certificat.
RESTOREPROOF_HTTP_HEADERS (vide) Cle1:Valeur1,Cle2:Valeur2.
RESTOREPROOF_HTTP_BODY (vide) Le corps de la requête.
RESTOREPROOF_HTTP_CONTENT_TYPE application/json (si un corps est fourni) Le type du corps.

Le code de statut ne prouve que le démarrage du conteneur. RESTOREPROOF_HTTP_EXPECTED_BODY est le champ qui prouve que ce sont bien vos données restaurées qui sont servies : le nom de votre boutique, un titre d'article, un identifiant qui n'existe que chez vous.

La réponse est lue au plus sur 1 Mio.

Mesures : url, method, response_time_ms, status_code, content_length, body_size_bytes, content_type, et body_match si le contenu attendu a été trouvé.