Aller au contenu

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 un compiled_script optionnel)
  • compliance_profile_controls — les contrôles appartenant à un profil. Un contrôle dont le check_script est NULL signifie 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éeDRIFT_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_access qui 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_RESULT vs COMPLIANCE_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.