Lancer un scan de conformité CIS/STIG sur une machine¶
Repod note les machines de votre inventaire de parc par rapport à des référentiels de sécurité publiés (CIS, DISA STIG). Ce guide couvre l'assignation d'un profil à des machines et le lancement d'un scan. Pour le modèle sous-jacent — comment se résout l'assignation de profil, pourquoi le profil CIS intégré s'exécute toujours, et en quoi cela diffère de la dérive de configuration — voir Profils de conformité et dérive de configuration.
1. Prérequis¶
- La machine est déjà enregistrée dans l'inventaire de parc avec
connection_type: ssh. Les scans de conformité nécessitent une connexion SSH — les clients en mode agent ne peuvent pas être scannés (POST /clients/{id}/compliance/scanrenvoie400pour eux). - La lecture des profils/résultats nécessite le rôle
auditor,maintainerouadmin. L'assignation d'un profil à un tag ou un client nécessiteadmin. - Le déclenchement d'un scan nécessite un rôle authentifié disposant d'un
accès machine sur ce client, et le replica
backend doit actuellement être le leader HA (le
POSTrenvoie503sur un replica passif dans un déploiement multi-replica — voirrequire_leaderdans les notes d'architecture backend). - Si vous prévoyez d'utiliser un profil STIG (ou tout autre profil non intégré), il doit déjà avoir été importé — voir étape 2.
Le profil CIS intégré s'exécute toujours
Chaque scan évalue le profil CIS intégré (cis-builtin-linux) même si
une machine n'a aucune assignation de profil — il n'existe pas d'état
« aucune couverture de conformité ». Assigner STIG ou un autre profil
importé s'ajoute à cela, cela ne remplace jamais le contrôle CIS.
2. Importer un profil (optionnel, STIG uniquement)¶
Le contenu des référentiels de conformité — scripts de contrôle, sévérités,
titres, références officielles — n'est jamais écrit à la main ni approximé
de mémoire. Il est uniquement chargé depuis un document de référentiel
réellement publié, via backend/scripts/import_compliance_profile.py. Il
n'existe aucun chemin dans l'interface pour rédiger du contenu de contrôle.
docker compose exec backend-api python3 scripts/import_compliance_profile.py \
data/compliance/stig_rhel9_disa.json \
stig-rhel9-disa stig "DISA STIG — Red Hat Enterprise Linux 9" \
--os-match rhel9 \
--source-ref "RedHatOfficial/ansible-role-rhel9-stig (GitHub)"
Le script est idempotent : le relancer avec un fichier JSON mis à jour
rafraîchit les métadonnées de contrôle (titre, sévérité, catégorie,
référence) mais ne remplace jamais un check_script déjà écrit — un
contrôle validé par un humain n'est jamais remplacé silencieusement par un
rafraîchissement de métadonnées. Les contrôles sans check_script dans le
fichier source sont quand même importés et rapportés not_implemented lors
d'un scan, plutôt que d'être abandonnés silencieusement.
Lister les profils désormais disponibles :
{
"profiles": [
{ "id": "cis-builtin-linux", "framework": "cis", "name": "CIS Benchmark (built-in)", "os_match": null, "is_builtin": true },
{ "id": "stig-rhel9-disa", "framework": "stig", "name": "DISA STIG — Red Hat Enterprise Linux 9", "os_match": "rhel9", "is_builtin": false }
]
}
3. Assigner un profil à des machines¶
L'assignation se fait par tag ou par client (une machine spécifique). Une assignation au niveau client est une surcharge : elle remplace entièrement les assignations dérivées des tags de cette machine, au lieu de s'y ajouter. En l'absence de surcharge, les assignations de chaque tag correspondant s'appliquent ensemble. C'est la même convention « surcharge remplace, ne fusionne jamais » utilisée dans tout le RBAC de parc de Repod — voir Profils de conformité et dérive de configuration pour les règles de résolution complètes.
Assigner à toutes les machines taguées rhel9-prod :
curl -s -X POST http://localhost:8000/api/v1/compliance/profiles/stig-rhel9-disa/assignments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"principal_type": "tag", "principal_id": "rhel9-prod"}' | jq .
Assigner à un client spécifique, en surchargeant ses assignations dérivées des tags :
curl -s -X POST http://localhost:8000/api/v1/compliance/profiles/stig-rhel9-disa/assignments \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"principal_type": "client", "principal_id": "3f2b1a90-..."}' | jq .
Réponse (201) :
{
"assignment": {
"id": "a1b2c3d4-...",
"profile_id": "stig-rhel9-disa",
"principal_type": "tag",
"principal_id": "rhel9-prod",
"assigned_by": "admin",
"assigned_at": "2026-08-20T09:00:00Z"
}
}
Lister les assignations actuelles d'un profil :
curl -s http://localhost:8000/api/v1/compliance/profiles/stig-rhel9-disa/assignments \
-H "Authorization: Bearer $TOKEN" | jq .
Supprimer une assignation :
curl -s -X DELETE http://localhost:8000/api/v1/compliance/profiles/assignments/a1b2c3d4-... \
-H "Authorization: Bearer $TOKEN"
4. Déclencher un scan¶
curl -s -X POST http://localhost:8000/api/v1/inventory/clients/3f2b1a90-.../compliance/scan \
-H "Authorization: Bearer $TOKEN"
Le scan s'exécute en arrière-plan (202 Accepted) sur une seule connexion
SSH, quel que soit le nombre de profils applicables — le script CIS intégré
et les scripts de contrôle concaténés de tout profil importé s'exécutent
tous sur cette même session.
Forcer un sous-ensemble spécifique de profils au lieu de la résolution
automatique par tag/client, en passant profile_ids dans le corps de la
requête :
curl -s -X POST http://localhost:8000/api/v1/inventory/clients/3f2b1a90-.../compliance/scan \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"profile_ids": ["cis-builtin-linux", "stig-rhel9-disa"]}'
Scanner toutes les machines correspondant à un tag en un seul appel :
curl -s -X POST http://localhost:8000/api/v1/inventory/compliance/scan-by-tag \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"tags": ["rhel9-prod"], "match": "any"}'
Cela résout tous les clients accessibles connectés en SSH correspondant
au(x) tag(s) et déclenche le même scan par client pour chacun, en
best-effort — les clients en mode agent présents dans l'ensemble résolu sont
silencieusement ignorés (compteur skipped dans la réponse), jamais mis en
erreur.
5. Lire les résultats¶
Vue historique, CIS uniquement (forme de réponse inchangée, comportement historique) :
curl -s http://localhost:8000/api/v1/inventory/clients/3f2b1a90-.../compliance \
-H "Authorization: Bearer $TOKEN" | jq .
{
"client_id": "3f2b1a90-...",
"results": [ { "control_id": "1.1.1.1", "status": "pass", "detail": "..." } ],
"score": { "passed": 38, "failed": 2, "not_applicable": 1, "percent": 95.0 },
"scanned_at": "2026-08-20T09:03:41Z"
}
Vue complète multi-profils — chaque profil applicable à cette machine, avec son propre score :
curl -s http://localhost:8000/api/v1/inventory/clients/3f2b1a90-.../compliance/profiles \
-H "Authorization: Bearer $TOKEN" | jq .
{
"client_id": "3f2b1a90-...",
"profiles": [
{
"profile_id": "cis-builtin-linux",
"profile_name": "CIS Benchmark (built-in)",
"framework": "cis",
"results": [ "..." ],
"score": { "passed": 38, "failed": 2, "not_applicable": 1, "percent": 95.0 },
"scanned_at": "2026-08-20T09:03:41Z"
},
{
"profile_id": "stig-rhel9-disa",
"profile_name": "DISA STIG — Red Hat Enterprise Linux 9",
"framework": "stig",
"results": [ "..." ],
"score": { "passed": 120, "failed": 5, "not_applicable": 287, "percent": 96.0 },
"scanned_at": "2026-08-20T09:03:41Z"
}
]
}
Il est normal qu'une grande partie des contrôles d'un profil importé
affiche not_applicable : tout contrôle sans check_script écrit est
rapporté ainsi plutôt qu'exécuté. Ce n'est pas un échec de scan.
Vérifier que ça a fonctionné¶
POST .../compliance/scana renvoyé202avec leclient_idattendu.GET .../compliance/profilesaffiche un horodatagescanned_atrécent pour chaque profil que vous attendez avoir été exécuté (CIS intégré plus tout profil assigné dont l'os_matchcorrespond à la distribution de cette machine — un profil dont l'os_matchne correspond pas est rapporténot_applicableet jamais exécuté, ce qui est également attendu, pas une erreur).- Les logs backend montrent l'achèvement du scan :
- Pour un profil importé, vérifiez ponctuellement qu'un contrôle dont vous
savez qu'il possède un véritable
check_scriptrapportepass/fail, et nonnot_implemented— un profil entier bloqué surnot_implementedsignifie généralement que le fichier JSON source ne contenait pas encore de valeurscheck_scriptpour lui, pas un bug du scan.