Aller au contenu

Activer le dual-scan (contre-vérification Trivy)

Le dual-scan exécute Trivy comme second moteur de vulnérabilités indépendant sur les paquets déjà publiés par Grype, afin de mettre en évidence ce que le scan initial de Grype aurait manqué. Ce guide couvre son activation et la lecture de ses résultats — pour comprendre pourquoi il existe comme un balayage périodique séparé plutôt que comme un second scan bloquant au moment de l'upload, voir la docstring du module services/dual_scan.py, résumée ci-dessous.

SaaS uniquement

Le dual-scan n'est disponible que dans Repod SaaS. Il nécessite le conteneur sidecar depot-trivy, qui n'existe que dans docker-compose.saas.yml — il n'existe pas d'overlay équivalent pour l'EE on-premise ou la CE, et la fonctionnalité n'a aucun effet si elle est activée ailleurs. services/dual_scan.py:is_enabled() vérifie d'abord DEPLOYMENT_MODE=saas et renvoie False de façon inconditionnelle sinon, quoi que dise settings.json.


1. Prérequis

  • Le mode de déploiement doit être saas (DEPLOYMENT_MODE=saas), avec le service depot-trivy en cours d'exécution (docker-compose.saas.yml le démarre automatiquement — pas de script de configuration séparé).
  • Le rôle admin, pour modifier settings.json["dual_scan"].
  • La fonctionnalité de licence dual_scan doit être incluse dans le plan du tenant (voir PLANS dans services/tenant_manager.py).

2. Ce que ça fait — et ne fait pas

  • Trivy ne s'exécute jamais dans le chemin d'upload/import. Grype seul continue de décider accepter / pending_review / bloquer au moment de l'upload — inchangé. Exécuter deux scans complets de façon synchrone par upload doublerait la latence d'upload sans bénéfice.
  • Trivy ne s'exécute que dans un balayage périodique en arrière-plan (cron dual_scan_daily) sur les paquets déjà à status = "validated" — deb, rpm, apk, artefacts Maven, PyPI et npm (OCI est exclu ; ses images sont déjà scannées intégralement par Grype via oci-dir:, il n'y a donc pas d'écart équivalent propre à Trivy pour lui).
  • Chaque CVE dans le cve_results d'un paquet scanné gagne un champ detected_by : ["grype"], ["trivy"], ou ["grype", "trivy"]. Les résultats de Grype ne sont jamais écartés ni écrasés.
  • Seule une CVE avec detected_by == ["trivy"] — une information réellement nouvelle que Grype a manquée — peut changer le sort d'un paquet. Une CVE que les deux moteurs reconnaissent déjà ne redéclenche jamais une décision, même si elle est Critical.
  • Un résultat ["trivy"] uniquement qui enfreint cve_policy fait basculer le paquet à pending_review et l'achemine vers la même file de décision RSSI que toute autre décision CVE (POST /security/packages/{name}/{version}/decide) — aucun nouveau mécanisme de revue. block est ici rétrogradé en review, jamais un rejet pur et simple à nouveau : une correspondance Trivy uniquement est basée sur le nom/la version et plus bruitée que les résultats propres à Grype, donc bloquer strictement un paquet déjà publié sur cette base serait disproportionné.

3. L'activer

curl -s -X PATCH http://localhost:8000/api/v1/settings/ \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "dual_scan": {
      "enabled": true,
      "hour": 5,
      "minute": 15,
      "max_artifacts_per_run": 50,
      "max_runtime_minutes": 30
    }
  }'
Champ Défaut Signification
enabled false Doit être explicitement mis à true — opt-in, même convention que mirror/backup/drift.
hour / minute 5 / 15 Heure UTC à laquelle s'exécute le job cron dual_scan_daily.
max_artifacts_per_run 50 Plafonne le nombre de paquets validated re-scannés par exécution, les plus anciennement re-scannés en premier.
max_runtime_minutes 30 Le balayage arrête de prendre en charge de nouveaux artefacts au-delà de ce budget en temps réel, même si le plafond d'artefacts n'a pas été atteint.

Confirmer que la planification a bien pris effet :

curl -s http://localhost:8000/api/v1/settings/ \
  -H "Authorization: Bearer $TOKEN" | jq .dual_scan

4. Lire detected_by sur la page Sécurité

Ouvrez la vue de détail des CVE d'un paquet (page Sécurité → paquet → liste des CVE). Chaque ligne de CVE porte un petit badge une fois qu'elle est passée par un cycle de dual-scan :

  • « Grype + Trivy » (gris, discret) — les deux moteurs ont trouvé cette CVE indépendamment. C'est une confirmation, pas une information nouvelle — délibérément stylisé comme le badge le moins accrocheur.
  • « Trivy uniquement » (violet) — trouvée uniquement par le scan de seconde opinion, absente du résultat initial de Grype. C'est le signal actionnable : c'est ce qui déclenche une réévaluation de la politique et, si c'est suffisamment sévère au regard de cve_policy, un état pending_review.

Un paquet jamais touché par un dual-scan (pas encore atteint par le balayage, ou fonctionnalité désactivée au moment de sa publication) n'affiche aucun champ detected_by et aucun badge — c'est attendu, pas une erreur, pour tout paquet publié avant l'activation du dual-scan ou encore en attente de son tour dans le balayage.

La sortie brute de Trivy et l'horodatage du dernier scan sont conservés séparément sur le manifeste (trivy_scan.matches, trivy_scan.scanned_at) à des fins d'audit, aux côtés de trivy_scan.divergent_cve_ids — la liste exacte des identifiants CVE qui étaient Trivy-uniquement lors du dernier passage.


5. Exemple détaillé

  1. Un .deb est uploadé et publié normalement — Grype ne trouve rien au-dessus du seuil de cve_policy configuré, le paquet passe en validated.
  2. Le lendemain, la base de données de vulnérabilités Trivy récupère une CVE pour l'une des bibliothèques embarquées du paquet que la base de Grype n'a pas encore appariée.
  3. À 05:15 UTC, dual_scan_daily atteint ce paquet dans son balayage (en supposant qu'il soit dans les limites de max_artifacts_per_run pour cette exécution — les paquets les plus anciens et non scannés depuis longtemps sont priorisés).
  4. Trivy rapporte la CVE ; elle est absente du cve_results existant du paquet, elle atterrit donc dans trivy_only.
  5. Si la sévérité de la CVE correspond à review ou block dans cve_policy, le paquet bascule en pending_review, une étape de validation dual_scan est ajoutée au manifeste, et une entrée de journal d'audit (DUAL_SCAN, PENDING_REVIEW) est écrite.
  6. Le paquet apparaît désormais dans la file de revue RSSI exactement comme tout paquet pending_review au moment de l'upload, avec un badge ["trivy"] uniquement sur la CVE signalée dans la page Sécurité.

Vérifier que ça a fonctionné

  1. GET /settings/ affiche dual_scan.enabled: true avec la planification que vous avez définie.
  2. Attendez (ou déclenchez manuellement, via une fenêtre de maintenance) la prochaine exécution de dual_scan_daily, puis vérifiez les logs backend :
    docker compose logs backend-api | grep dual_scan
    
  3. Choisissez un paquet qui était validated avant l'exécution du balayage et confirmez que son instantané de manifeste possède désormais une section trivy_scan :
    curl -s "http://localhost:8000/api/v1/artifacts/mypackage/versions/1.2.3/snapshot?arch=amd64" \
      -H "Authorization: Bearer $TOKEN" | jq '.trivy_scan, .cve_results[].detected_by'
    
  4. Si Trivy était injoignable pendant une exécution, le manifeste obtient trivy_scan.error = "trivy indisponible" et cve_results reste inchangé — c'est le chemin fail-soft, pas un bug : l'indisponibilité temporaire d'un second moteur ne bloque ni ne corrompt jamais le résultat existant basé sur Grype.