Aller au contenu

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-api produisant 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é

  1. 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.

  1. 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.

  2. 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.

  1. Récupérez le jeton d'API v2 de ci-securepay (menu du profil, ou POST /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é

  1. Créez le type de produit Paiement et le produit SecurePay API, puis autorisez ci-securepay sur ce produit.

  2. 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.

  1. Prévoyez un engagement par contexte : CI staging, CI production, puis Pentest pour 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é

  1. 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.

  1. 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"
  1. Dupliquez le job pour la production (DOJO_ENGAGEMENT: "CI production"), déclenché sur main.

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é

  1. Relancez la pipeline sans rien modifier : le nombre de constats actifs ne doit pas bouger.

  2. Pour comprendre la différence, importez deux fois le même rapport Trivy avec import-scan dans un engagement de test Bac à sable, puis comparez avec le comportement du réimport.

  3. 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-scan crée un nouveau test à chaque appel, et les doublons sont rattachés à l'original
  • reimport-scan met à jour le test existant et conserve l'historique de triage
  • close_old_findings clô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é

  1. Qualifiez les constats critiques et hauts : vrai positif à corriger, faux positif ou hors périmètre.

  2. 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.
  3. 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.
  4. 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é

  1. 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).

  2. 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é"
    }
  ]
}
  1. Appliquez-le dans le job Trivy :
trivy image --vex vex/securepay.openvex.json --show-suppressed \
  -f json -o trivy-image.json "$IMAGE"
  1. Comparez les rapports avec et sans VEX.

  2. 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_affected disparaî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é

  1. 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.
  2. 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
  1. 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

  1. Déployez Dependency-Track avec son fichier docker compose officiel (voir sa documentation) ; son serveur d'API demande plusieurs gigaoctets de RAM.

  2. 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
  1. Configurez une notification NEW_VULNERABILITY vers 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-scan et reimport-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 ?