Profils de conformité et dérive de configuration¶
Repod scanne les machines de votre parc d'inventaire de deux manières distinctes, faciles à confondre mais répondant à des questions différentes :
- Les profils de conformité (CIS, DISA STIG) demandent « cette machine respecte-t-elle un référentiel de sécurité publié ? » — la réponse à chaque contrôle est un pass/fail par rapport à une référence externe fixe.
- La dérive de configuration demande « cette machine correspond-elle toujours à l'état que j'ai déclaré ? » — la réponse est un diff entre une valeur attendue et la valeur réellement observée, par rapport à une baseline que vous rédigez vous-même.
Les deux s'exécutent via SSH contre des machines déjà enregistrées dans l'inventaire du parc, et les deux sont en détection seule — aucun des deux ne modifie jamais quoi que ce soit sur une machine scannée. Cette page explique ce qu'est chacun des deux, et surtout, pourquoi il s'agit de deux systèmes séparés plutôt que d'un seul. Pour la mécanique pas à pas de rédaction et d'attribution d'une baseline de dérive, voir Baselines de dérive de configuration.
Partie 1 — Profils de conformité (CIS / STIG)¶
Ce qu'est un profil¶
Un profil de conformité est un ensemble nommé de contrôles
appartenant à un framework (cis ou stig) et scopé à un système
d'exploitation via os_match. Trois tables le pilotent :
compliance_profiles— une ligne par profil (id, framework, nom,os_match, s'il est intégré ou non, et uncompiled_scriptoptionnel)compliance_profile_controls— les contrôles appartenant à un profil. Un contrôle dont lecheck_scriptestNULLsignifie que les métadonnées du contrôle (titre, sévérité, catégorie, référence officielle) ont été importées, mais qu'aucun script de vérification n'a encore été rédigé pour lui — il est rapporténot_implemented, jamais silencieusement écartécompliance_profile_assignment— quelles machines reçoivent quels profils, attribué par tag ou par client
Les profils applicables à une machine sont résolus depuis cette table
d'attribution : une attribution au niveau client remplace toute
attribution dérivée de tag pour cette machine (elle ne s'y ajoute pas) ;
sans surcharge client, les profils de chaque tag correspondant
s'appliquent ensemble, dédupliqués. Une machine sans aucune
attribution — aucune correspondance de tag, aucune surcharge client —
reçoit tout de même un profil : le profil CIS intégré
(cis-builtin-linux) s'applique par défaut. Un scan de conformité
évalue donc toujours au moins CIS ; ceci diffère délibérément des
baselines de dérive, couvertes en Partie 2.
Au sein de l'ensemble résolu de profils, chacun est en outre filtré par
os_match par rapport à la distribution détectée de la machine cible.
Un profil qui ne correspond pas au système d'exploitation de la machine
n'est jamais exécuté — il est rapporté not_applicable dans le résultat
du scan, de sorte qu'il reste visible (vous voyez qu'il a été considéré)
sans risquer un scan exécutant des contrôles rédigés pour Debian contre
un hôte RHEL.
Deux modes d'exécution¶
Un profil s'exécute selon l'un de deux modes, décidé par profil, pas par scan :
compiled_script — utilisé uniquement par le profil CIS intégré.
Il s'agit du script bash monolithique original à 41 contrôles, envoyé à
la machine et interprété exactement comme avant l'existence des profils.
Le conserver comme un script opaque unique — plutôt que de le scinder en
scripts par contrôle comme tout profil importé par la suite — garantit
que le résultat du scan CIS intégré est identique bit à bit à son
comportement pré-profils. Rien dans l'introduction du support STIG n'a
été autorisé à changer ce qu'un déploiement CIS existant rapporte déjà.
check_script par contrôle — utilisé par chaque profil importé
(STIG aujourd'hui). Chaque contrôle du profil dont le check_script
n'est pas NULL est concaténé en un unique script, enveloppé dans un
harnais commun qui émet une ligne
COMPLIANCE_RESULT|CR|{id}|CR|{status}|CR|{detail} par contrôle, et
envoyé à la machine en un seul aller-retour SSH. Les contrôles avec un
check_script NULL ne touchent jamais le réseau — ils sont rapportés
not_implemented directement en Python. C'est ce qui rend abordable un
grand profil importé : un profil STIG fraîchement importé peut porter
des centaines de contrôles sans script de vérification encore écrit, et
aucun d'eux ne coûte quoi que ce soit à « exécuter » tant qu'un vrai
script n'est pas rédigé pour lui.
Une connexion SSH par scan¶
run_compliance_scan() ouvre exactement une connexion SSH par
machine, quel que soit le nombre de profils qui s'appliquent à elle.
Chaque profil applicable ajoute au plus une exécution de script sur
cette même connexion — jamais une seconde connexion. Une machine à
laquelle sont attribués à la fois le profil CIS intégré et un profil
STIG importé est scannée en une seule session SSH couvrant les deux.
flowchart TD
A["Scan déclenché\n(manuel ou cron quotidien)"] --> B["Résoudre l'attribution\nsurcharge client > union de tags > repli CIS"]
B --> C{"Pour chaque\nprofil résolu"}
C -->|"os_match échoue"| D["not_applicable\n(non exécuté)"]
C -->|"os_match correspond"| E{"Mode d'exécution ?"}
E -->|"compiled_script\n(CIS intégré)"| F["Script monolithique historique\nsur la session SSH partagée"]
E -->|"check_script par contrôle\n(importé, ex. STIG)"| G["Script harnais concaténé\nsur la session SSH partagée"]
G --> H["Contrôles avec check_script NULL :\nrapportés not_implemented,\naucun appel réseau"]
F --> I["Résultats analysés, étiquetés\navec profile_id + framework"]
G --> I
H --> I
D --> J["Résumé du scan :\nresults[] + profiles[] (executed | not_applicable)"]
I --> J
Provenance du contenu : jamais rédigé à la main¶
Le contenu des référentiels de conformité — les scripts de
vérification, les sévérités, les titres et les références officielles —
n'est chargé que via
backend/scripts/import_compliance_profile.py, qui importe un document
de référentiel réellement publié. Personne n'écrit ni n'approxime de
contenu de contrôle CIS/STIG de mémoire, et réimporter un profil pour
rafraîchir ses métadonnées (titre, sévérité, catégorie) n'écrase jamais
un check_script déjà validé — un contrôle validé par un humain n'est
jamais silencieusement remplacé par une synchronisation de métadonnées.
C'est ce qui permet à un rapport de scan de remonter jusqu'à une source
réelle et citable, plutôt qu'une interprétation inventée d'un
référentiel.
Partie 2 — Dérive de configuration¶
Ce que signifie « dérive »¶
La détection de dérive de configuration compare la configuration réelle, actuellement observée d'une machine à un état désiré que vous déclarez vous-même, dans une baseline — inspirée de la même idée qu'Ansible ou Puppet, mais strictement en lecture seule : Repod ne fait que rapporter une différence, il n'en applique jamais une.
Une baseline est composée de règles de quatre types :
| Type de règle | Comparé à |
|---|---|
package |
Nom/version du paquet installé |
service |
État systemctl is-active / is-enabled |
file |
Hash SHA-256 du contenu d'un fichier |
permission |
Mode, propriétaire et/ou groupe du fichier |
Chaque résultat de scan de dérive porte un status (match, drift,
error, ou not_applicable), avec la expected_value de la règle et
l'actual_value observée sur la machine. Voir
Baselines de dérive de configuration
pour la syntaxe YAML et le parcours API complet pour rédiger, attribuer
et scanner des baselines.
Pourquoi il s'agit d'un système séparé de la conformité¶
C'est le point le plus important à comprendre au sujet de la dérive : c'est une bifurcation architecturale délibérée par rapport au moteur de conformité, pas un oubli faisant que deux fonctionnalités aux noms similaires existent séparément.
Le modèle check_script du moteur de conformité produit un pass/fail
opaque : c'est le script shell lui-même qui décide pass/fail/
warn/skip, et qui renvoie un statut plus une chaîne detail en
texte libre. Le backend ne voit jamais la valeur réellement mesurée —
seulement le verdict du script lui-même. Cela suffit entièrement pour
répondre à « cette machine est-elle conforme au contrôle X ? », mais ne
peut pas répondre à « qu'est-ce qui a changé, et vers quoi ? » — il n'y
a pas de paire structurée ancienne-valeur/nouvelle-valeur à diffuser, ni
aucun moyen de suivre une valeur spécifique dans le temps.
La dérive a précisément besoin de cette valeur structurée. Son format
sur le fil est donc différent par conception : le script SSH d'un scan
de dérive n'émet jamais que la valeur brute observée —
DRIFT_RESULT|DR|{rule_key}|DR|{observed} — et ne décide jamais lui-même
d'un pass ou d'un fail. La comparaison par rapport à la valeur attendue
déclarée se fait entièrement en Python, côté backend, à partir de la
définition propre de la règle. C'est ce qui rend possible un diff
structuré attendu-vs-réel et un historique de dérive interrogeable, ce
qu'un pass/fail opaque de script de vérification ne peut fondamentalement
pas offrir.
Ce qui est réutilisé, et ce qui est réellement nouveau¶
Étant donné la proximité étroite des deux systèmes, la dérive réutilise délibérément plusieurs motifs exacts du moteur de conformité plutôt que de les réinventer :
- Une connexion SSH par machine, quel que soit le nombre de règles —
le même motif utilisé par
run_compliance_scan(); toutes les règles éligibles SSH sont concaténées en un unique script et envoyées sur une seule connexion. - Résolution d'attribution tag/client — une surcharge au niveau
client sur une baseline remplace entièrement ses baselines
dérivées de tag, exactement la même convention qu'utilise
compliance_profile_assignment. - Verrouillage RBAC et accès machine — les mêmes vérifications
machine_accessqui verrouillent chaque autre endpoint scopé à un client dans l'inventaire.
Ce qui est réellement nouveau dans la dérive, et non repris de la conformité :
- Le format sur le fil en valeur brute décrit ci-dessus
(
DRIFT_RESULTvsCOMPLIANCE_RESULT) — une exigence structurelle, pas un choix stylistique. - Aucune baseline de repli obligatoire. Contrairement à la
conformité, qui retombe toujours sur le profil CIS intégré quand rien
n'est attribué, la fonction
resolve_baselines_for_client()de la dérive peut légitimement retourner une liste vide. Une baseline est votre propre état désiré déclaré, pas une politique de sécurité universelle comme l'est CIS — une machine sans correspondance de tag et sans surcharge client n'a tout simplement aucune règle de dérive à vérifier. C'est un résultat normal et attendu, pas une erreur ni une lacune à combler silencieusement.
Les règles de paquet fonctionnent sans SSH ; les règles de service/fichier/permission non¶
Les règles de paquet sont comparées directement à la liste de paquets que Repod possède déjà en dossier pour cette machine — issue d'un scan SSH antérieur ou d'un check-in d'agent — sans aller-retour réseau supplémentaire. C'est aussi le seul type de règle de dérive qui fonctionne pour les clients en mode agent : puisqu'il ne nécessite aucune exécution de commande en direct, juste une lecture de données déjà collectées, il fonctionne de manière identique que le client rapporte via SSH ou via le protocole d'agent push-only.
Les règles de service, de fichier et de permission sont SSH
uniquement, par contrainte architecturale forte, pas par omission.
Les clients en mode agent sont strictement push-only — ils appellent
POST /inventory/agent/checkin et rien ne les rappelle en retour. Il
n'existe aucun canal par lequel le backend pourrait demander à un
client agent d'exécuter une commande arbitraire, et aucun n'est prévu ;
accepter une commande depuis un agent romprait la garantie de sécurité
push-only sur laquelle repose le protocole agent. Une baseline
mélangeant des règles de paquet avec des règles de service/fichier/
permission fonctionne toujours pour un client en mode agent — les
règles de paquet s'évaluent normalement, et les règles non-paquet
rapportent simplement not_applicable pour ce client, avec un message
de détail explicite, jamais silencieusement ignorées sans trace.
Les résultats de dérive sont en append-only¶
Contrairement aux scans de conformité (qui suppriment et réinsèrent les
résultats à chaque scan), les lignes de drift_results ne sont jamais
supprimées ni écrasées par un nouveau scan — chaque scan ajoute un
nouveau lot de lignes partageant un même horodatage scanned_at. C'est
délibéré : la dérive est censée être traçable dans le temps (cette
machine dérive-t-elle sur la même règle depuis deux semaines, ou
cela vient-il de commencer ?), ce qu'une table remplacée à chaque scan
ne peut pas révéler. Une baseline modifiée ou supprimée par la suite ne
corrompt ni ne masque les résultats historiques — chaque ligne de
résultat conserve son propre type de règle, sa cible et sa sévérité
dénormalisés au moment où elle a été enregistrée.
Aucune auto-remédiation¶
Détection et alerte seulement. Un résultat drift ne déclenche jamais
aucun changement sur la machine scannée — il n'existe nulle part dans
cette fonctionnalité d'action « corriger pour moi ». C'est un choix de
conception explicite et délibéré pour un premier déploiement : « corriger »
automatiquement une machine que l'on vient tout juste de commencer à
observer comporte un risque réel de dommage non intentionnel, et rien
dans ce système n'écrit sur une machine scannée, en aucune circonstance.
Où chacun se situe dans la vue d'ensemble¶
| Conformité (CIS/STIG) | Dérive de configuration | Scan CVE | |
|---|---|---|---|
| Question posée | Cette machine respecte-t-elle un référentiel de sécurité publié ? | Cette machine correspond-elle toujours à l'état que j'ai déclaré ? | Cette machine présente-t-elle des vulnérabilités connues ? |
| Référence | Un profil CIS/STIG publié | Une baseline que vous rédigez | La base de données CVE (NVD/Grype) |
| Forme du résultat | Pass/fail par contrôle | Valeur attendue vs valeur réelle par règle | Liste de CVE avec sévérité/EPSS/KEV |
| Défaut si non configuré | Le profil CIS intégré s'exécute toujours | Aucune baseline, aucune règle — normal, pas une erreur | N/A — s'exécute à chaque upload/import |
| Historique | Le dernier scan remplace le précédent | Append-only — historique complet conservé | Snapshot remplacé à chaque scan d'inventaire |
Voir Conformité & Sécurité pour la manière dont cela s'inscrit dans les exigences NIS2/SecNumCloud/ISO 27001, et Baselines de dérive de configuration pour les étapes pratiques de rédaction et d'attribution d'une baseline.