Piloter les vulnérabilités avec DefectDojo et VEX¶
Objectifs pédagogiques¶
À la fin de ce TP, vous devez être capables de :
- déployer DefectDojo et y donner à la CI un accès au moindre privilège
- modéliser une application, ses environnements et ses SLA de remédiation
- importer automatiquement les rapports de tous les scanners depuis la pipeline
- distinguer import et réimport, et observer la déduplication
- qualifier les constats : faux positif justifié, acceptation de risque datée
- déclarer une CVE non exploitable avec un document VEX et l'appliquer en CI
- produire les indicateurs qui pilotent la remédiation
Prérequis¶
- La pipeline de
securepay-apiproduisant des rapports en artefacts : Gitleaks (JSON), Semgrep (JSON), Trivy (JSON), ZAP (XML) - Docker sur le poste et au moins 4 Go de RAM disponibles, ou une instance DefectDojo partagée fournie par l'intervenant
- Un runner GitLab capable de joindre DefectDojo
- Facultatif : OpenBao (voir le TP secrets) pour ranger le jeton d'API
Contexte¶
Les pipelines de SecurePay produisent chaque jour des rapports que personne ne lit : plus de 1 400 constats s'accumulent dans les artefacts. Une CVE critique publiée après un déploiement a été découverte par un client. Vous mettez en place un véritable programme de gestion des vulnérabilités.
flowchart LR
G["Gitleaks"] --> CI["Job defectdojo"]
S["Semgrep"] --> CI
T["Trivy"] --> CI
Z["ZAP"] --> CI
CI -->|"reimport-scan"| D["DefectDojo\nSecurePay API"]
D --> TRI["Triage\nfaux positifs, risques acceptés"]
D --> KPI["Indicateurs\nSLA, backlog"]
V["vex/*.openvex.json"] --> T Atelier 1 : Déployer DefectDojo¶
Travail demandé¶
- Déployez DefectDojo sur le poste (et non dans la VM de production, déjà chargée) :
git clone https://github.com/DefectDojo/django-DefectDojo
cd django-DefectDojo
docker compose up -d
docker compose logs initializer | grep "Admin password:"
L'initialisation peut prendre quelques minutes. L'interface est servie sur http://localhost:8080.
-
Connectez-vous en
admin, changez le mot de passe et rangez-le dans votre gestionnaire de secrets : c'est un compte d'administration, pas un compte de travail. -
Créez un utilisateur
ci-securepay, non administrateur. Vous l'autoriserez sur le seul produit SecurePay à l'atelier 2.
Droits dans l'édition open source
Dans les versions récentes de l'édition open source, les rôles fins par produit (dont API Importer) et les groupes relèvent de l'offre commerciale. L'édition open source autorise un utilisateur sur un produit ou un type de produit : c'est suffisant pour limiter la CI au seul produit qu'elle alimente.
- Récupérez le jeton d'API v2 de
ci-securepay(menu du profil, ouPOST /api/v2/api-token-auth/). Rangez-le dans OpenBao, ou à défaut dans une variable GitLab Masked et Protected.
Critère de réussite¶
DefectDojo répond, et la CI disposera d'un jeton qui n'est pas celui d'un administrateur.
Atelier 2 : Produit, engagements et SLA¶
Travail demandé¶
-
Créez le type de produit
Paiementet le produitSecurePay API, puis autorisezci-securepaysur ce produit. -
Créez une configuration de SLA (Configuration → SLA Configuration) alignée sur votre politique, par exemple :
| Sévérité | Délai |
|---|---|
| Critical | 7 jours |
| High | 30 jours |
| Medium | 90 jours |
| Low | 180 jours |
Associez-la au produit : chaque constat porte désormais une échéance.
- Prévoyez un engagement par contexte :
CI staging,CI production, puisPentestpour les analyses manuelles. Les engagements CI seront créés automatiquement par le premier import.
Critère de réussite¶
Le produit existe, porte une configuration de SLA, et seul ci-securepay (en plus des administrateurs) y est autorisé.
Atelier 3 : Import automatique depuis la CI¶
Travail demandé¶
- Vérifiez que chaque job de scan publie son rapport en artefact, au format attendu par DefectDojo :
| Outil | Commande de rapport | scan_type |
|---|---|---|
| Gitleaks | gitleaks git --report-format json --report-path gitleaks.json . | Gitleaks Scan |
| Semgrep | semgrep scan --config auto --json --output semgrep.json | Semgrep JSON Report |
| Trivy | trivy image -f json -o trivy-image.json "$IMAGE" | Trivy Scan |
| ZAP | zap-baseline.py -t "$TARGET" -x zap.xml | ZAP Scan |
Les valeurs de scan_type sont strictes : une faute de frappe fait échouer l'import.
- Ajoutez un job qui réimporte tous les rapports, même si un scan a échoué :
defectdojo:
stage: report
image:
name: curlimages/curl:<version>
entrypoint: [""]
when: always
needs:
- job: secrets:gitleaks
artifacts: true
- job: sast:semgrep
artifacts: true
- job: scan:trivy
artifacts: true
- job: dast:zap
artifacts: true
variables:
DOJO_ENGAGEMENT: "CI staging"
DOJO_ENVIRONMENT: "Staging"
script:
- |
dojo_reimport() {
[ -s "$2" ] || { echo "rapport absent : $2"; return 0; }
curl -sf -X POST "$DOJO_URL/api/v2/reimport-scan/" \
-H "Authorization: Token $DOJO_TOKEN" \
-F scan_type="$1" -F file=@"$2" -F test_title="$3" \
-F product_type_name="Paiement" -F product_name="SecurePay API" \
-F engagement_name="$DOJO_ENGAGEMENT" -F environment="$DOJO_ENVIRONMENT" \
-F auto_create_context=true -F close_old_findings=true -F minimum_severity=Low \
> /dev/null && echo "importé : $3"
}
dojo_reimport "Gitleaks Scan" gitleaks.json "Gitleaks"
dojo_reimport "Semgrep JSON Report" semgrep.json "Semgrep"
dojo_reimport "Trivy Scan" trivy-image.json "Trivy image"
dojo_reimport "ZAP Scan" zap.xml "ZAP staging"
- Dupliquez le job pour la production (
DOJO_ENGAGEMENT: "CI production"), déclenché surmain.
Ce que vous devez observer¶
- Le premier passage crée l'engagement et un test par outil
- Chaque test liste ses constats, avec une échéance calculée à partir du SLA
Critère de réussite¶
Une pipeline complète alimente DefectDojo sans aucune action manuelle.
Atelier 4 : Réimport et déduplication¶
Travail demandé¶
-
Relancez la pipeline sans rien modifier : le nombre de constats actifs ne doit pas bouger.
-
Pour comprendre la différence, importez deux fois le même rapport Trivy avec
import-scandans un engagement de testBac à sable, puis comparez avec le comportement du réimport. -
Corrigez une vulnérabilité (mise à jour d'une dépendance ou de l'image de base), relancez la pipeline et retrouvez le constat clôturé comme corrigé (Mitigated).
Ce que vous devez observer¶
import-scancrée un nouveau test à chaque appel, et les doublons sont rattachés à l'originalreimport-scanmet à jour le test existant et conserve l'historique de triageclose_old_findingsclôture automatiquement ce qui a disparu du rapport
Critère de réussite¶
Vous savez expliquer, chiffres à l'appui, pourquoi la CI doit réimporter.
Atelier 5 : Triage et exceptions¶
Travail demandé¶
-
Qualifiez les constats critiques et hauts : vrai positif à corriger, faux positif ou hors périmètre.
-
Pour un faux positif :
- rédigez une justification technique vérifiable dans le constat ;
- faites-la valider par un coéquipier, qui le note dans le commentaire ;
- activez l'historique des faux positifs (paramètres système) et vérifiez qu'une réapparition du même constat est marquée automatiquement.
-
Pour un vrai positif impossible à corriger tout de suite, créez une acceptation de risque :
- propriétaire du risque ;
- décision (accepter, atténuer, transférer) et mesures compensatoires ;
- date d'expiration proche (le lendemain) ;
- réactivation à l'expiration laissée active.
-
Le lendemain, constatez que le constat est redevenu actif.
Critère de réussite¶
Aucune exception n'existe sans justification, sans valideur ni sans date d'expiration.
Atelier 6 : VEX, déclarer ce qui n'est pas exploitable¶
Un constat peut être réel dans l'image, mais inexploitable dans le contexte de SecurePay. Plutôt que de le masquer en silence, vous le déclarez.
Travail demandé¶
-
Choisissez une CVE de l'image dont vous pouvez démontrer qu'elle n'est pas exploitable (code jamais appelé, composant non utilisé). Relevez le PURL du paquet dans le rapport Trivy (
PkgIdentifier.PURL). -
Rédigez
vex/securepay.openvex.json:
{
"@context": "https://openvex.dev/ns/v0.2.0",
"@id": "https://securepay.local/vex/2026-001",
"author": "SecurePay Security",
"timestamp": "2026-11-02T00:00:00Z",
"version": 1,
"statements": [
{
"vulnerability": { "name": "CVE-AAAA-NNNNN" },
"products": [{ "@id": "<PURL du paquet relevé à l'étape 1>" }],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path",
"impact_statement": "Explication vérifiable de la non-exploitabilité"
}
]
}
- Appliquez-le dans le job Trivy :
trivy image --vex vex/securepay.openvex.json --show-suppressed \
-f json -o trivy-image.json "$IMAGE"
-
Comparez les rapports avec et sans VEX.
-
Faites passer toute modification de
vex/par une MR approuvée par le profil sécurité : c'est une décision de sécurité, pas un contournement.
Ce que vous devez observer¶
- La CVE déclarée
not_affecteddisparaît des vulnérabilités du rapport - Avec
--show-suppressed, elle reste visible dans la section des constats modifiés, avec le statut, la justification et le fichier VEX source
Testé
Ce comportement a été vérifié avec Trivy 0.76.0 : la CVE visée par son PURL est retirée des vulnérabilités et listée comme supprimée avec sa justification.
Critère de réussite¶
Le VEX est versionné, revu, appliqué en CI, et chaque déclaration est justifiée.
Atelier 7 : Prioriser et piloter¶
Travail demandé¶
-
Rédigez
docs/vulnerabilites.md:- la matrice de priorité (sévérité, présence dans le catalogue KEV, score EPSS quand le scanner le fournit, exposition du composant) ;
- la politique de SLA et la procédure d'exception ;
- les rôles : qui qualifie, qui corrige, qui accepte un risque.
-
Construisez le tableau des indicateurs à partir de DefectDojo (tableaux de bord et métriques du produit) :
| Indicateur | Valeur | Tendance |
|---|---|---|
| Délai moyen de remédiation (critiques) | ||
| Constats hors SLA | ||
| Backlog actif par sévérité | ||
| Taux de faux positifs par outil | ||
| Acceptations de risque expirées |
- Animez un triage de 15 minutes avec votre équipe et consignez les décisions.
Critère de réussite¶
Les indicateurs sont relevés, commentés, et permettent de répondre à la question : « sommes-nous plus sûrs que la semaine dernière ? »
Bonus : Dependency-Track¶
-
Déployez Dependency-Track avec son fichier docker compose officiel (voir sa documentation) ; son serveur d'API demande plusieurs gigaoctets de RAM.
-
Envoyez le SBOM de chaque image promue depuis la CI :
curl -sf -X POST "$DTRACK_URL/api/v1/bom" \
-H "X-Api-Key: $DTRACK_API_KEY" \
-F autoCreate=true \
-F projectName="securepay-api" \
-F projectVersion="$CI_COMMIT_SHORT_SHA" \
-F bom=@sbom.cdx.json
- Configurez une notification
NEW_VULNERABILITYvers votre canal d'alerte, et vérifiez qu'une CVE nouvellement publiée sur un composant d'une version en production vous est signalée.
Questions de synthèse¶
- Pourquoi le score CVSS seul conduit-il à une mauvaise priorisation ?
- Quelle différence entre
import-scanetreimport-scan, et pourquoi la CI doit-elle réimporter ? - Pourquoi une acceptation de risque doit-elle avoir une date d'expiration ?
- En quoi un document VEX diffère-t-il d'un fichier d'exclusion comme
.trivyignore? - Quels indicateurs présenteriez-vous au comité sécurité, et lesquels refuseriez-vous ?